diff options
author | Brett Chabot <brettchabot@google.com> | 2024-05-15 17:11:00 -0700 |
---|---|---|
committer | Copybara-Service <copybara-worker@google.com> | 2024-05-15 17:11:36 -0700 |
commit | f2460c5d03fdcbfc632915416a616096ba5de639 (patch) | |
tree | 698f26b713f3e83ce4dfa059d422062d3a045308 | |
parent | 4acc9e0e51df537d1ac006b11e30c78fca701f91 (diff) | |
download | robolectric-upstream-google.tar.gz |
Remove legacy resources shadows.upstream-google
These shadows are effectively unused since legacy resources
is unsupported. This follows on previous changes to remove legacy resources
code.
PiperOrigin-RevId: 634127419
14 files changed, 25 insertions, 2228 deletions
diff --git a/robolectric/src/main/java/org/robolectric/android/AttributeSetBuilderImpl.java b/robolectric/src/main/java/org/robolectric/android/AttributeSetBuilderImpl.java index 00a8fcad6..5bbe385f8 100644 --- a/robolectric/src/main/java/org/robolectric/android/AttributeSetBuilderImpl.java +++ b/robolectric/src/main/java/org/robolectric/android/AttributeSetBuilderImpl.java @@ -6,7 +6,7 @@ import static org.robolectric.res.android.ResourceTypes.RES_XML_END_ELEMENT_TYPE import static org.robolectric.res.android.ResourceTypes.RES_XML_RESOURCE_MAP_TYPE; import static org.robolectric.res.android.ResourceTypes.RES_XML_START_ELEMENT_TYPE; import static org.robolectric.res.android.ResourceTypes.ResTable_map.ATTR_TYPE; -import static org.robolectric.shadows.ShadowLegacyAssetManager.ATTRIBUTE_TYPE_PRECIDENCE; +import static org.robolectric.shadows.ShadowAssetManager.ATTRIBUTE_TYPE_PRECIDENCE; import android.content.Context; import android.util.AttributeSet; @@ -17,7 +17,6 @@ import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.Map.Entry; @@ -27,9 +26,6 @@ import org.robolectric.res.AttrData; import org.robolectric.res.AttrData.Pair; import org.robolectric.res.AttributeResource; import org.robolectric.res.ResName; -import org.robolectric.res.ResType; -import org.robolectric.res.ResourceTable; -import org.robolectric.res.TypedResource; import org.robolectric.res.android.DataType; import org.robolectric.res.android.ResTable; import org.robolectric.res.android.ResTable.ResourceName; @@ -42,11 +38,9 @@ import org.robolectric.res.android.ResourceTypes.ResXMLTree_header; import org.robolectric.res.android.ResourceTypes.ResXMLTree_node; import org.robolectric.res.android.ResourceTypes.Res_value; import org.robolectric.shadow.api.Shadow; -import org.robolectric.shadows.Converter; import org.robolectric.shadows.Converter2; import org.robolectric.shadows.ShadowArscAssetManager; import org.robolectric.shadows.ShadowAssetManager; -import org.robolectric.shadows.ShadowLegacyAssetManager; import org.robolectric.util.ReflectionHelpers; import org.robolectric.util.ReflectionHelpers.ClassParameter; @@ -163,72 +157,6 @@ public class AttributeSetBuilderImpl implements AttributeSetBuilder { } } - public static class LegacyResourceResolver implements ResourceResolver { - - private final Context context; - private final ResourceTable resourceTable; - - public LegacyResourceResolver(Context context, ResourceTable compileTimeResourceTable) { - this.context = context; - resourceTable = compileTimeResourceTable; - } - - @Override - public String getPackageName() { - return context.getPackageName(); - } - - @Override - public String getResourceName(Integer attrId) { - return resourceTable.getResName(attrId).getFullyQualifiedName(); - } - - @Override - public Integer getIdentifier(String name, String type, String packageName) { - Integer resourceId = resourceTable.getResourceId(new ResName(packageName, type, name)); - if (resourceId == 0) { - resourceId = resourceTable.getResourceId( - new ResName(packageName, type, name.replace('.', '_'))); - } - return resourceId; - } - - @Override - public void parseValue(Integer attrId, ResName attrResName, AttributeResource attribute, - TypedValue outValue) { - ShadowLegacyAssetManager shadowAssetManager = Shadow - .extract(context.getResources().getAssets()); - TypedResource attrTypeData = shadowAssetManager.getAttrTypeData(attribute.resName); - if (attrTypeData != null) { - AttrData attrData = (AttrData) attrTypeData.getData(); - String format = attrData.getFormat(); - String[] types = format.split("\\|"); - Arrays.sort(types, ATTRIBUTE_TYPE_PRECIDENCE); - for (String type : types) { - if ("reference".equals(type)) continue; // already handled above - Converter2 converter = Converter2.getConverterFor(attrData, type); - - if (converter != null) { - if (converter.fillTypedValue(attribute.value, outValue, true)) { - break; - } - } - - } - // throw new IllegalArgumentException("wha? " + format); - } else { - /* In cases where the runtime framework doesn't know this attribute, e.g: viewportHeight (added in 21) on a - * KitKat runtine, then infer the attribute type from the value. - * - * TODO: When we are able to pass the SDK resources from the build environment then we can remove this - * and replace the NullResourceLoader with simple ResourceProvider that only parses attribute type information. - */ - ResType resType = ResType.inferFromValue(attribute.value); - Converter.getConverter(resType).fillTypedValue(attribute.value, outValue); - } - } - } - protected AttributeSetBuilderImpl(ResourceResolver resourceResolver) { this.resourceResolver = resourceResolver; } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowThemeTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowThemeTest.java index ff07540d8..7c7047d66 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowThemeTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowThemeTest.java @@ -16,7 +16,6 @@ import android.view.View; import android.widget.Button; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -35,11 +34,6 @@ public class ShadowThemeTest { resources = ApplicationProvider.getApplicationContext().getResources(); } - @After - public void tearDown() { - ShadowLegacyAssetManager.strictErrors = false; - } - @Test public void whenExplicitlySetOnActivity_afterSetContentView_activityGetsThemeFromActivityInManifest() { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ResourceModeShadowPicker.java b/shadows/framework/src/main/java/org/robolectric/shadows/ResourceModeShadowPicker.java index 16db9a166..416f34d3d 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ResourceModeShadowPicker.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ResourceModeShadowPicker.java @@ -7,17 +7,13 @@ import org.robolectric.shadow.api.ShadowPicker; public class ResourceModeShadowPicker<T> implements ShadowPicker<T> { - // TODO(brettchabot): remove unused legacyShadowClass - private Class<? extends T> legacyShadowClass; private Class<? extends T> binaryShadowClass; private Class<? extends T> binary9ShadowClass; private Class<? extends T> binary10ShadowClass; private Class<? extends T> binary14ShadowClass; - public ResourceModeShadowPicker(Class<? extends T> legacyShadowClass, - Class<? extends T> binaryShadowClass, - Class<? extends T> binary9ShadowClass) { - this.legacyShadowClass = legacyShadowClass; + public ResourceModeShadowPicker( + Class<? extends T> binaryShadowClass, Class<? extends T> binary9ShadowClass) { this.binaryShadowClass = binaryShadowClass; this.binary9ShadowClass = binary9ShadowClass; this.binary10ShadowClass = binary9ShadowClass; @@ -25,12 +21,10 @@ public class ResourceModeShadowPicker<T> implements ShadowPicker<T> { } public ResourceModeShadowPicker( - Class<? extends T> legacyShadowClass, Class<? extends T> binaryShadowClass, Class<? extends T> binary9ShadowClass, Class<? extends T> binary10ShadowClass, Class<? extends T> binary14ShadowClass) { - this.legacyShadowClass = legacyShadowClass; this.binaryShadowClass = binaryShadowClass; this.binary9ShadowClass = binary9ShadowClass; this.binary10ShadowClass = binary10ShadowClass; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApkAssets.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApkAssets.java index c7177518d..219233da1 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApkAssets.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApkAssets.java @@ -8,7 +8,7 @@ abstract public class ShadowApkAssets { public static class Picker extends ResourceModeShadowPicker<ShadowApkAssets> { public Picker() { - super(ShadowLegacyApkAssets.class, null, ShadowArscApkAssets9.class); + super(null, ShadowArscApkAssets9.class); } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscResourcesImpl.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscResourcesImpl.java index 13b8058c9..9012341b8 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscResourcesImpl.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscResourcesImpl.java @@ -4,7 +4,6 @@ import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; import static android.os.Build.VERSION_CODES.N_MR1; import static android.os.Build.VERSION_CODES.O; -import static org.robolectric.shadows.ShadowAssetManager.legacyShadowOf; import static org.robolectric.util.reflector.Reflector.reflector; import android.content.res.AssetFileDescriptor; @@ -21,16 +20,9 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; -import java.util.Locale; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.RealObject; -import org.robolectric.res.Plural; -import org.robolectric.res.PluralRules; -import org.robolectric.res.ResName; -import org.robolectric.res.ResType; -import org.robolectric.res.ResourceTable; -import org.robolectric.res.TypedResource; import org.robolectric.shadows.ShadowResourcesImpl.Picker; import org.robolectric.util.reflector.Direct; import org.robolectric.util.reflector.ForType; @@ -65,53 +57,6 @@ public class ShadowArscResourcesImpl extends ShadowResourcesImpl { return resettableArrays; } - @Implementation(maxSdk = M) - public String getQuantityString(int id, int quantity, Object... formatArgs) throws Resources.NotFoundException { - String raw = getQuantityString(id, quantity); - return String.format(Locale.ENGLISH, raw, formatArgs); - } - - @Implementation(maxSdk = M) - public String getQuantityString(int resId, int quantity) throws Resources.NotFoundException { - ShadowLegacyAssetManager shadowAssetManager = legacyShadowOf(realResourcesImpl.getAssets()); - - TypedResource typedResource = shadowAssetManager.getResourceTable().getValue(resId, shadowAssetManager.config); - if (typedResource != null && typedResource instanceof PluralRules) { - PluralRules pluralRules = (PluralRules) typedResource; - Plural plural = pluralRules.find(quantity); - - if (plural == null) { - return null; - } - - TypedResource<?> resolvedTypedResource = - shadowAssetManager.resolve( - new TypedResource<>( - plural.getString(), ResType.CHAR_SEQUENCE, pluralRules.getXmlContext()), - shadowAssetManager.config, - resId); - return resolvedTypedResource == null ? null : resolvedTypedResource.asString(); - } else { - return null; - } - } - - @Implementation(maxSdk = M) - public InputStream openRawResource(int id) throws Resources.NotFoundException { - if (false) { - ShadowLegacyAssetManager shadowAssetManager = legacyShadowOf(realResourcesImpl.getAssets()); - ResourceTable resourceTable = shadowAssetManager.getResourceTable(); - InputStream inputStream = resourceTable.getRawValue(id, shadowAssetManager.config); - if (inputStream == null) { - throw newNotFoundException(id); - } else { - return inputStream; - } - } else { - return reflector(ResourcesImplReflector.class, realResourcesImpl).openRawResource(id); - } - } - /** * Since {@link AssetFileDescriptor}s are not yet supported by Robolectric, {@code null} will * be returned if the resource is found. If the resource cannot be found, {@link Resources.NotFoundException} will @@ -119,7 +64,9 @@ public class ShadowArscResourcesImpl extends ShadowResourcesImpl { */ @Implementation(maxSdk = M) public AssetFileDescriptor openRawResourceFd(int id) throws Resources.NotFoundException { - InputStream inputStream = openRawResource(id); + InputStream inputStream = + reflector(ResourcesImplReflector.class, realResourcesImpl).openRawResource(id); + ; if (!(inputStream instanceof FileInputStream)) { // todo fixme return null; @@ -134,13 +81,7 @@ public class ShadowArscResourcesImpl extends ShadowResourcesImpl { } private Resources.NotFoundException newNotFoundException(int id) { - ResourceTable resourceTable = legacyShadowOf(realResourcesImpl.getAssets()).getResourceTable(); - ResName resName = resourceTable.getResName(id); - if (resName == null) { - return new Resources.NotFoundException("resource ID #0x" + Integer.toHexString(id)); - } else { - return new Resources.NotFoundException(resName.getFullyQualifiedName()); - } + return new Resources.NotFoundException("resource ID #0x" + Integer.toHexString(id)); } @Implementation(maxSdk = N_MR1) diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetInputStream.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetInputStream.java index 10de51eb8..77a177a58 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetInputStream.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetInputStream.java @@ -22,19 +22,13 @@ public abstract class ShadowAssetInputStream { from(long.class, assetPtr)); ShadowAssetInputStream sais = Shadow.extract(ais); - if (sais instanceof ShadowLegacyAssetInputStream) { - ShadowLegacyAssetInputStream slais = (ShadowLegacyAssetInputStream) sais; - slais.setDelegate(delegateInputStream); - slais.setNinePatch(asset.isNinePatch()); - } return ais; } public static class Picker extends ResourceModeShadowPicker<ShadowAssetInputStream> { public Picker() { - super(ShadowLegacyAssetInputStream.class, ShadowArscAssetInputStream.class, - ShadowArscAssetInputStream.class); + super(ShadowArscAssetInputStream.class, ShadowArscAssetInputStream.class); } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetManager.java index 12a9c8fed..74c9ad1e7 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetManager.java @@ -5,6 +5,7 @@ import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.util.ArraySet; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Ordering; import java.nio.file.Path; import java.util.Collection; import java.util.List; @@ -13,7 +14,6 @@ import org.robolectric.res.android.AssetPath; import org.robolectric.res.android.CppAssetManager; import org.robolectric.res.android.ResTable; import org.robolectric.res.android.String8; -import org.robolectric.shadow.api.Shadow; import org.robolectric.util.reflector.Accessor; import org.robolectric.util.reflector.Direct; import org.robolectric.util.reflector.ForType; @@ -21,11 +21,24 @@ import org.robolectric.util.reflector.Static; abstract public class ShadowAssetManager { + public static final Ordering<String> ATTRIBUTE_TYPE_PRECIDENCE = + Ordering.explicit( + "reference", + "color", + "boolean", + "integer", + "fraction", + "dimension", + "float", + "enum", + "flag", + "flags", + "string"); + public static class Picker extends ResourceModeShadowPicker<ShadowAssetManager> { public Picker() { super( - ShadowLegacyAssetManager.class, ShadowArscAssetManager.class, ShadowArscAssetManager9.class, ShadowArscAssetManager10.class, @@ -33,14 +46,6 @@ abstract public class ShadowAssetManager { } } - /** - * @deprecated Avoid use. - */ - @Deprecated - static ShadowLegacyAssetManager legacyShadowOf(AssetManager assetManager) { - return Shadow.extract(assetManager); - } - abstract Collection<Path> getAllAssetDirs(); @VisibleForTesting diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyApkAssets.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyApkAssets.java deleted file mode 100644 index a9679f5eb..000000000 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyApkAssets.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.robolectric.shadows; - -import static android.os.Build.VERSION_CODES.P; -import static android.os.Build.VERSION_CODES.Q; - -import android.content.res.ApkAssets; -import com.android.internal.util.Preconditions; -import java.io.IOException; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; - -// transliterated from -// https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/core/jni/android_content_res_ApkAssets.cpp - -/** Shadow for {@link ApkAssets} that is used for legacy resources. */ -@Implements(value = ApkAssets.class, minSdk = P, isInAndroidSdk = false) -public class ShadowLegacyApkAssets extends ShadowApkAssets { - - private String assetPath; - - @Implementation(maxSdk = Q) - protected void __constructor__( - String path, boolean system, boolean forceSharedLib, boolean overlay) throws IOException { - Preconditions.checkNotNull(path, "path"); - this.assetPath = path; - } - - - @Implementation - protected String getAssetPath() { - return assetPath; - } -} diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyAssetInputStream.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyAssetInputStream.java deleted file mode 100644 index 09fb27199..000000000 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyAssetInputStream.java +++ /dev/null @@ -1,141 +0,0 @@ -package org.robolectric.shadows; - -import static org.robolectric.util.reflector.Reflector.reflector; - -import android.content.res.AssetManager.AssetInputStream; -import java.io.IOException; -import java.io.InputStream; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; -import org.robolectric.annotation.RealObject; -import org.robolectric.shadows.ShadowAssetInputStream.Picker; -import org.robolectric.util.reflector.Direct; -import org.robolectric.util.reflector.ForType; - -@SuppressWarnings("UnusedDeclaration") -@Implements(value = AssetInputStream.class, shadowPicker = Picker.class) -public class ShadowLegacyAssetInputStream extends ShadowAssetInputStream { - - @RealObject private AssetInputStream realObject; - - private InputStream delegate; - private boolean ninePatch; - - @Override - InputStream getDelegate() { - return delegate; - } - - void setDelegate(InputStream delegate) { - this.delegate = delegate; - } - - @Override - boolean isNinePatch() { - return ninePatch; - } - - void setNinePatch(boolean ninePatch) { - this.ninePatch = ninePatch; - } - - @Implementation - protected int read() throws IOException { - return delegate == null - ? reflector(AssetInputStreamReflector.class, realObject).read() - : delegate.read(); - } - - @Implementation - protected int read(byte[] b) throws IOException { - return delegate == null - ? reflector(AssetInputStreamReflector.class, realObject).read(b) - : delegate.read(b); - } - - @Implementation - protected int read(byte[] b, int off, int len) throws IOException { - return delegate == null - ? reflector(AssetInputStreamReflector.class, realObject).read(b, off, len) - : delegate.read(b, off, len); - } - - @Implementation - protected long skip(long n) throws IOException { - return delegate == null - ? reflector(AssetInputStreamReflector.class, realObject).skip(n) - : delegate.skip(n); - } - - @Implementation - protected int available() throws IOException { - return delegate == null - ? reflector(AssetInputStreamReflector.class, realObject).available() - : delegate.available(); - } - - @Implementation - protected void close() throws IOException { - if (delegate == null) { - reflector(AssetInputStreamReflector.class, realObject).close(); - } else { - delegate.close(); - } - } - - @Implementation - protected void mark(int readlimit) { - if (delegate == null) { - reflector(AssetInputStreamReflector.class, realObject).mark(readlimit); - } else { - delegate.mark(readlimit); - } - } - - @Implementation - protected void reset() throws IOException { - if (delegate == null) { - reflector(AssetInputStreamReflector.class, realObject).reset(); - } else { - delegate.reset(); - } - } - - @Implementation - protected boolean markSupported() { - return delegate == null - ? reflector(AssetInputStreamReflector.class, realObject).markSupported() - : delegate.markSupported(); - } - - @ForType(AssetInputStream.class) - interface AssetInputStreamReflector { - - @Direct - int read(); - - @Direct - int read(byte[] b); - - @Direct - int read(byte[] b, int off, int len); - - @Direct - long skip(long n); - - @Direct - int available(); - - @Direct - void close(); - - @Direct - void mark(int readlimit); - - @Direct - void reset(); - - @Direct - boolean markSupported(); - } -} diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyAssetManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyAssetManager.java deleted file mode 100644 index 780b99fc1..000000000 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyAssetManager.java +++ /dev/null @@ -1,1418 +0,0 @@ -package org.robolectric.shadows; - -import static android.os.Build.VERSION_CODES.M; -import static android.os.Build.VERSION_CODES.N; -import static android.os.Build.VERSION_CODES.N_MR1; -import static android.os.Build.VERSION_CODES.O; -import static android.os.Build.VERSION_CODES.O_MR1; -import static android.os.Build.VERSION_CODES.P; -import static android.os.Build.VERSION_CODES.TIRAMISU; -import static org.robolectric.shadow.api.Shadow.invokeConstructor; -import static org.robolectric.util.ReflectionHelpers.ClassParameter.from; -import static org.robolectric.util.reflector.Reflector.reflector; - -import android.annotation.SuppressLint; -import android.content.res.ApkAssets; -import android.content.res.AssetFileDescriptor; -import android.content.res.AssetManager; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.content.res.XmlResourceParser; -import android.os.Build.VERSION_CODES; -import android.os.ParcelFileDescriptor; -import android.util.AttributeSet; -import android.util.SparseArray; -import android.util.TypedValue; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.Ordering; -import dalvik.system.VMRuntime; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLDecoder; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArraySet; -import javax.annotation.Nonnull; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.android.XmlResourceParserImpl; -import org.robolectric.annotation.HiddenApi; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; -import org.robolectric.annotation.RealObject; -import org.robolectric.res.AttrData; -import org.robolectric.res.AttributeResource; -import org.robolectric.res.EmptyStyle; -import org.robolectric.res.FileTypedResource; -import org.robolectric.res.Fs; -import org.robolectric.res.ResName; -import org.robolectric.res.ResType; -import org.robolectric.res.ResourceIds; -import org.robolectric.res.ResourceTable; -import org.robolectric.res.Style; -import org.robolectric.res.StyleData; -import org.robolectric.res.StyleResolver; -import org.robolectric.res.ThemeStyleSet; -import org.robolectric.res.TypedResource; -import org.robolectric.res.android.Asset; -import org.robolectric.res.android.Registries; -import org.robolectric.res.android.ResTable_config; -import org.robolectric.res.builder.XmlBlock; -import org.robolectric.shadow.api.Shadow; -import org.robolectric.shadows.ShadowAssetManager.Picker; -import org.robolectric.util.Logger; -import org.robolectric.util.TempDirectory; -import org.robolectric.util.reflector.Direct; -import org.robolectric.util.reflector.ForType; - -@SuppressLint("NewApi") -@Implements(value = AssetManager.class, /* this one works for P too... maxSdk = VERSION_CODES.O_MR1,*/ - looseSignatures = true, shadowPicker = Picker.class) -public class ShadowLegacyAssetManager extends ShadowAssetManager { - - public static final Ordering<String> ATTRIBUTE_TYPE_PRECIDENCE = - Ordering.explicit( - "reference", - "color", - "boolean", - "integer", - "fraction", - "dimension", - "float", - "enum", - "flag", - "flags", - "string"); - - static boolean strictErrors = false; - - private static final int STYLE_NUM_ENTRIES = 6; - private static final int STYLE_TYPE = 0; - private static final int STYLE_DATA = 1; - private static final int STYLE_ASSET_COOKIE = 2; - private static final int STYLE_RESOURCE_ID = 3; - private static final int STYLE_CHANGING_CONFIGURATIONS = 4; - private static final int STYLE_DENSITY = 5; - - private static long nextInternalThemeId = 1000; - private static final Map<Long, NativeTheme> nativeThemes = new HashMap<>(); - - @RealObject protected AssetManager realObject; - - boolean isSystem = false; - - class NativeTheme { - private ThemeStyleSet themeStyleSet; - - public NativeTheme(ThemeStyleSet themeStyleSet) { - this.themeStyleSet = themeStyleSet; - } - - public ShadowLegacyAssetManager getShadowAssetManager() { - return ShadowLegacyAssetManager.this; - } - } - - ResTable_config config = new ResTable_config(); - private final Set<Path> assetDirs = new CopyOnWriteArraySet<>(); - - private void convertAndFill(AttributeResource attribute, TypedValue outValue, ResTable_config config, boolean resolveRefs) { - if (attribute.isNull()) { - outValue.type = TypedValue.TYPE_NULL; - outValue.data = TypedValue.DATA_NULL_UNDEFINED; - return; - } else if (attribute.isEmpty()) { - outValue.type = TypedValue.TYPE_NULL; - outValue.data = TypedValue.DATA_NULL_EMPTY; - return; - } - - // short-circuit Android caching of loaded resources cuz our string positions don't remain stable... - outValue.assetCookie = Converter.getNextStringCookie(); - outValue.changingConfigurations = 0; - - // TODO: Handle resource and style references - if (attribute.isStyleReference()) { - return; - } - - while (attribute.isResourceReference()) { - Integer resourceId; - ResName resName = attribute.getResourceReference(); - if (attribute.getReferenceResId() != null) { - resourceId = attribute.getReferenceResId(); - } else { - resourceId = getResourceTable().getResourceId(resName); - } - - if (resourceId == null) { - throw new Resources.NotFoundException("unknown resource " + resName); - } - outValue.type = TypedValue.TYPE_REFERENCE; - if (!resolveRefs) { - // Just return the resourceId if resolveRefs is false. - outValue.data = resourceId; - return; - } - - outValue.resourceId = resourceId; - - TypedResource dereferencedRef = getResourceTable().getValue(resName, config); - if (dereferencedRef == null) { - Logger.strict("couldn't resolve %s from %s", resName.getFullyQualifiedName(), attribute); - return; - } else { - if (dereferencedRef.isFile()) { - outValue.type = TypedValue.TYPE_STRING; - outValue.data = 0; - outValue.assetCookie = Converter.getNextStringCookie(); - outValue.string = dereferencedRef.asString(); - return; - } else if (dereferencedRef.getData() instanceof String) { - attribute = new AttributeResource(attribute.resName, dereferencedRef.asString(), resName.packageName); - if (attribute.isResourceReference()) { - continue; - } - if (resolveRefs) { - Converter.getConverter(dereferencedRef.getResType()).fillTypedValue(attribute.value, outValue); - return; - } - } - } - break; - } - - if (attribute.isNull()) { - outValue.type = TypedValue.TYPE_NULL; - return; - } - - TypedResource attrTypeData = getAttrTypeData(attribute.resName); - if (attrTypeData != null) { - AttrData attrData = (AttrData) attrTypeData.getData(); - String format = attrData.getFormat(); - String[] types = format.split("\\|"); - Arrays.sort(types, ATTRIBUTE_TYPE_PRECIDENCE); - for (String type : types) { - if ("reference".equals(type)) continue; // already handled above - Converter converter = Converter.getConverterFor(attrData, type); - - if (converter != null) { - if (converter.fillTypedValue(attribute.value, outValue)) { - return; - } - } - } - } else { - /** - * In cases where the runtime framework doesn't know this attribute, e.g: viewportHeight (added in 21) on a - * KitKat runtine, then infer the attribute type from the value. - * - * TODO: When we are able to pass the SDK resources from the build environment then we can remove this - * and replace the NullResourceLoader with simple ResourceProvider that only parses attribute type information. - */ - ResType resType = ResType.inferFromValue(attribute.value); - Converter.getConverter(resType).fillTypedValue(attribute.value, outValue); - } - } - - - public TypedResource getAttrTypeData(ResName resName) { - return getResourceTable().getValue(resName, config); - } - - @Implementation - protected void __constructor__() { - if (RuntimeEnvironment.getApiLevel() >= P) { - invokeConstructor(AssetManager.class, realObject); - } - - } - - @Implementation - protected void __constructor__(boolean isSystem) { - this.isSystem = isSystem; - if (RuntimeEnvironment.getApiLevel() >= P) { - invokeConstructor(AssetManager.class, realObject, from(boolean.class, isSystem)); - } - - } - - @Implementation(minSdk = P) - protected static long nativeCreate() { - // Return a fake pointer, must not be 0. - return 1; - } - - @HiddenApi - @Implementation(maxSdk = O_MR1) - protected void init(boolean isSystem) { - // no op - } - - protected ResourceTable getResourceTable() { - return isSystem - ? RuntimeEnvironment.getSystemResourceTable() - : RuntimeEnvironment.getAppResourceTable(); - } - - @HiddenApi @Implementation - public CharSequence getResourceText(int ident) { - TypedResource value = getAndResolve(ident, config, true); - if (value == null) return null; - return (CharSequence) value.getData(); - } - - @HiddenApi @Implementation - public CharSequence getResourceBagText(int ident, int bagEntryId) { - throw new UnsupportedOperationException(); // todo - } - - @HiddenApi @Implementation(maxSdk = O_MR1) - protected int getStringBlockCount() { - return 0; - } - - @HiddenApi @Implementation - public String[] getResourceStringArray(final int id) { - CharSequence[] resourceTextArray = getResourceTextArray(id); - if (resourceTextArray == null) return null; - String[] strings = new String[resourceTextArray.length]; - for (int i = 0; i < strings.length; i++) { - strings[i] = resourceTextArray[i].toString(); - } - return strings; - } - - @HiddenApi @Implementation - public int getResourceIdentifier(String name, String defType, String defPackage) { - Integer resourceId = - getResourceTable().getResourceId(ResName.qualifyResName(name, defPackage, defType)); - return resourceId == null ? 0 : resourceId; - } - - @HiddenApi @Implementation - public boolean getResourceValue(int ident, int density, TypedValue outValue, boolean resolveRefs) { - TypedResource value = getAndResolve(ident, config, resolveRefs); - if (value == null) return false; - - getConverter(value).fillTypedValue(value.getData(), outValue); - return true; - } - - private Converter getConverter(TypedResource value) { - if (value instanceof FileTypedResource.Image - || (value instanceof FileTypedResource - && ((FileTypedResource) value).getPath().getFileName().toString().endsWith(".xml"))) { - return new Converter.FromFilePath(); - } - return Converter.getConverter(value.getResType()); - } - - @HiddenApi @Implementation - public CharSequence[] getResourceTextArray(int resId) { - TypedResource value = getAndResolve(resId, config, true); - if (value == null) return null; - List<TypedResource> items = getConverter(value).getItems(value); - CharSequence[] charSequences = new CharSequence[items.size()]; - for (int i = 0; i < items.size(); i++) { - TypedResource typedResource = resolve(items.get(i), config, resId); - charSequences[i] = getConverter(typedResource).asCharSequence(typedResource); - } - return charSequences; - } - - @HiddenApi - @Implementation - public boolean getThemeValue(long themePtr, int ident, TypedValue outValue, boolean resolveRefs) { - ResName resName = getResourceTable().getResName(ident); - - ThemeStyleSet themeStyleSet = getNativeTheme(themePtr).themeStyleSet; - AttributeResource attrValue = themeStyleSet.getAttrValue(resName); - while(attrValue != null && attrValue.isStyleReference()) { - ResName attrResName = attrValue.getStyleReference(); - if (attrValue.resName.equals(attrResName)) { - Logger.info("huh... circular reference for %s?", attrResName.getFullyQualifiedName()); - return false; - } - attrValue = themeStyleSet.getAttrValue(attrResName); - } - if (attrValue != null) { - convertAndFill(attrValue, outValue, config, resolveRefs); - return true; - } - return false; - } - - @HiddenApi @Implementation(maxSdk = O_MR1) - protected Object ensureStringBlocks() { - return null; - } - - @Implementation - protected InputStream open(String fileName) throws IOException { - return Fs.getInputStream(findAssetFile(fileName)); - } - - @Implementation - protected InputStream open(String fileName, int accessMode) throws IOException { - return Fs.getInputStream(findAssetFile(fileName)); - } - - @Implementation - protected AssetFileDescriptor openFd(String fileName) throws IOException { - Path path = findAssetFile(fileName); - if (path.getFileSystem().provider().getScheme().equals("jar")) { - path = getFileFromZip(path); - } - ParcelFileDescriptor parcelFileDescriptor = - ParcelFileDescriptor.open(path.toFile(), ParcelFileDescriptor.MODE_READ_ONLY); - return new AssetFileDescriptor(parcelFileDescriptor, 0, Files.size(path)); - } - - private Path findAssetFile(String fileName) throws IOException { - for (Path assetDir : getAllAssetDirs()) { - Path assetFile = assetDir.resolve(fileName); - if (Files.exists(assetFile)) { - return assetFile; - } - } - - throw new FileNotFoundException("Asset file " + fileName + " not found"); - } - - /** - * Extract an asset from a zipped up assets provided by the build system, this is required because - * there is no way to get a FileDescriptor from a zip entry. This is a temporary measure for Bazel - * which can be removed once binary resources are supported. - */ - private static Path getFileFromZip(Path path) { - byte[] buffer = new byte[1024]; - try { - Path outputDir = new TempDirectory("robolectric_assets").create("fromzip"); - try (InputStream zis = Fs.getInputStream(path)) { - Path fileFromZip = outputDir.resolve(path.getFileName().toString()); - - try (OutputStream fos = Files.newOutputStream(fileFromZip)) { - int len; - while ((len = zis.read(buffer)) > 0) { - fos.write(buffer, 0, len); - } - } - return fileFromZip; - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Implementation - protected String[] list(String path) throws IOException { - List<String> assetFiles = new ArrayList<>(); - - for (Path assetsDir : getAllAssetDirs()) { - Path file; - if (path.isEmpty()) { - file = assetsDir; - } else { - file = assetsDir.resolve(path); - } - - if (Files.isDirectory(file)) { - Collections.addAll(assetFiles, Fs.listFileNames(file)); - } - } - return assetFiles.toArray(new String[assetFiles.size()]); - } - - @HiddenApi @Implementation(maxSdk = O_MR1) - protected Number openAsset(String fileName, int mode) throws FileNotFoundException { - return 0; - } - - @HiddenApi @Implementation(maxSdk = O_MR1) - protected ParcelFileDescriptor openAssetFd(String fileName, long[] outOffsets) throws IOException { - return null; - } - - @HiddenApi - @Implementation - public InputStream openNonAsset(int cookie, String fileName, int accessMode) throws IOException { - final ResName resName = qualifyFromNonAssetFileName(fileName); - - final FileTypedResource typedResource = - (FileTypedResource) getResourceTable().getValue(resName, config); - - if (typedResource == null) { - throw new IOException("Unable to find resource for " + fileName); - } - - InputStream stream; - if (accessMode == AssetManager.ACCESS_STREAMING) { - stream = Fs.getInputStream(typedResource.getPath()); - } else { - stream = new ByteArrayInputStream(Fs.getBytes(typedResource.getPath())); - } - - if (RuntimeEnvironment.getApiLevel() >= P) { - Asset asset = Asset.newFileAsset(typedResource); - long assetPtr = Registries.NATIVE_ASSET_REGISTRY.register(asset); - // Camouflage the InputStream as an AssetInputStream so subsequent instanceof checks pass. - stream = ShadowAssetInputStream.createAssetInputStream(stream, assetPtr, realObject); - } - - return stream; - } - - @HiddenApi @Implementation(maxSdk = O_MR1) - protected Number openNonAssetNative(int cookie, String fileName, int accessMode) - throws FileNotFoundException { - throw new IllegalStateException(); - } - - private ResName qualifyFromNonAssetFileName(String fileName) { - // Resources from a jar belong to the "android" namespace, except when they come from "resource_files.zip" - // when they are application resources produced by Bazel. - if (fileName.startsWith("jar:") && !fileName.contains("resource_files.zip")) { - // Must remove "jar:" prefix, or else qualifyFromFilePath fails on Windows - if (File.separatorChar == '\\') { - fileName = windowsWorkaround(fileName); - } - return ResName.qualifyFromFilePath("android", fileName.replaceFirst("jar:", "")); - } else { - return ResName.qualifyFromFilePath( - RuntimeEnvironment.getApplication().getPackageName(), fileName); - } - } - - private String windowsWorkaround(String fileWithinJar) { - try { - String path = new URL(new URL(fileWithinJar).getPath()).getPath(); - int bangI = path.indexOf('!'); - String jarPath = path.substring(1, bangI); - return URLDecoder.decode(URLDecoder.decode(jarPath, "UTF-8"), "UTF-8") - + "!" + path.substring(bangI + 1); - } catch (MalformedURLException | UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - - @HiddenApi - @Implementation - public AssetFileDescriptor openNonAssetFd(int cookie, String fileName) throws IOException { - throw new IllegalStateException(); - } - - @HiddenApi @Implementation(maxSdk = O_MR1) - protected ParcelFileDescriptor openNonAssetFdNative(int cookie, String fileName, long[] outOffsets) - throws IOException { - throw new IllegalStateException(); - } - - @HiddenApi @Implementation(maxSdk = O_MR1) - protected Number openXmlAssetNative(int cookie, String fileName) throws FileNotFoundException { - throw new IllegalStateException(); - } - - @Implementation - protected XmlResourceParser openXmlResourceParser(int cookie, String fileName) - throws IOException { - XmlBlock xmlBlock = XmlBlock.create(Fs.fromUrl(fileName), getResourceTable().getPackageName()); - if (xmlBlock == null) { - throw new Resources.NotFoundException(fileName); - } - return getXmlResourceParser(getResourceTable(), xmlBlock, getResourceTable().getPackageName()); - } - - @HiddenApi - @Implementation(maxSdk = O_MR1) - protected long seekAsset(long asset, long offset, int whence) { - return 0; - } - - @HiddenApi - @Implementation(maxSdk = O_MR1) - protected long getAssetLength(long asset) { - return 0; - } - - @HiddenApi - @Implementation(maxSdk = O_MR1) - protected long getAssetRemainingLength(long assetHandle) { - return 0; - } - - @HiddenApi - @Implementation(maxSdk = O_MR1) - protected void destroyAsset(long asset) { - // no op - } - - protected XmlResourceParser loadXmlResourceParser(int resId, String type) throws Resources.NotFoundException { - ResName resName = getResName(resId); - ResName resolvedResName = resolveResName(resName, config); - if (resolvedResName == null) { - throw new RuntimeException("couldn't resolve " + resName.getFullyQualifiedName()); - } - resName = resolvedResName; - - XmlBlock block = getResourceTable().getXml(resName, config); - if (block == null) { - throw new Resources.NotFoundException(resName.getFullyQualifiedName()); - } - - ResourceTable resourceProvider = ResourceIds.isFrameworkResource(resId) ? RuntimeEnvironment.getSystemResourceTable() : RuntimeEnvironment.getCompileTimeResourceTable(); - - return getXmlResourceParser(resourceProvider, block, resName.packageName); - } - - private XmlResourceParser getXmlResourceParser(ResourceTable resourceProvider, XmlBlock block, String packageName) { - return new XmlResourceParserImpl( - block.getDocument(), - block.getPath(), - block.getPackageName(), - packageName, - resourceProvider); - } - - @HiddenApi @Implementation - public int addAssetPath(String path) { - assetDirs.add(Fs.fromUrl(path)); - return 1; - } - - @HiddenApi - @Implementation(maxSdk = M) - protected int addAssetPathNative(String path) { - return addAssetPathNative(path, false); - } - - @HiddenApi @Implementation(minSdk = N, maxSdk = O_MR1) - protected int addAssetPathNative(String path, boolean appAsLib) { - return 0; - } - - @HiddenApi @Implementation(minSdk = P) - public void setApkAssets(Object apkAssetsObject, Object invalidateCachesObject) { - ApkAssets[] apkAssets = (ApkAssets[]) apkAssetsObject; - boolean invalidateCaches = (boolean) invalidateCachesObject; - - for (ApkAssets apkAsset : apkAssets) { - assetDirs.add(Fs.fromUrl(apkAsset.getAssetPath())); - } - reflector(AssetManagerReflector.class, realObject).setApkAssets(apkAssets, invalidateCaches); - } - - @HiddenApi @Implementation - public boolean isUpToDate() { - return true; - } - - @HiddenApi @Implementation(maxSdk = M) - public void setLocale(String locale) { - } - - @Implementation - protected String[] getLocales() { - return new String[0]; // todo - } - - @HiddenApi - @Implementation(maxSdk = N_MR1) - public final void setConfiguration( - int mcc, - int mnc, - String locale, - int orientation, - int touchscreen, - int density, - int keyboard, - int keyboardHidden, - int navigation, - int screenWidth, - int screenHeight, - int smallestScreenWidthDp, - int screenWidthDp, - int screenHeightDp, - int screenLayout, - int uiMode, - int sdkVersion) { - setConfiguration( - mcc, - mnc, - locale, - orientation, - touchscreen, - density, - keyboard, - keyboardHidden, - navigation, - screenWidth, - screenHeight, - smallestScreenWidthDp, - screenWidthDp, - screenHeightDp, - screenLayout, - uiMode, - 0, - sdkVersion); - } - - @HiddenApi - @Implementation(minSdk = VERSION_CODES.O, maxSdk = TIRAMISU) - public void setConfiguration( - int mcc, - int mnc, - String locale, - int orientation, - int touchscreen, - int density, - int keyboard, - int keyboardHidden, - int navigation, - int screenWidth, - int screenHeight, - int smallestScreenWidthDp, - int screenWidthDp, - int screenHeightDp, - int screenLayout, - int uiMode, - int colorMode, - int majorVersion) { - // AssetManager* am = assetManagerForJavaObject(env, clazz); - - ResTable_config config = new ResTable_config(); - - // Constants duplicated from Java class android.content.res.Configuration. - final int kScreenLayoutRoundMask = 0x300; - final int kScreenLayoutRoundShift = 8; - - config.mcc = mcc; - config.mnc = mnc; - config.orientation = orientation; - config.touchscreen = touchscreen; - config.density = density; - config.keyboard = keyboard; - config.inputFlags = keyboardHidden; - config.navigation = navigation; - config.screenWidth = screenWidth; - config.screenHeight = screenHeight; - config.smallestScreenWidthDp = smallestScreenWidthDp; - config.screenWidthDp = screenWidthDp; - config.screenHeightDp = screenHeightDp; - config.screenLayout = screenLayout; - config.uiMode = uiMode; - // config.colorMode = colorMode; // todo - config.sdkVersion = majorVersion; - config.minorVersion = 0; - - // In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer - // in C++. We must extract the round qualifier out of the Java screenLayout and put it - // into screenLayout2. - config.screenLayout2 = - (byte)((screenLayout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift); - - if (locale != null) { - config.setBcp47Locale(locale); - } - // am->setConfiguration(config, locale8); - - this.config = config; - } - - @HiddenApi @Implementation(maxSdk = O_MR1) - public int[] getArrayIntResource(int resId) { - TypedResource value = getAndResolve(resId, config, true); - if (value == null) return null; - List<TypedResource> items = getConverter(value).getItems(value); - int[] ints = new int[items.size()]; - for (int i = 0; i < items.size(); i++) { - TypedResource typedResource = resolve(items.get(i), config, resId); - ints[i] = getConverter(typedResource).asInt(typedResource); - } - return ints; - } - - @HiddenApi @Implementation(minSdk = P) - protected int[] getResourceIntArray(int resId) { - return getArrayIntResource(resId); - } - - @HiddenApi @Implementation(maxSdk = O_MR1) - protected String[] getArrayStringResource(int arrayResId) { - return new String[0]; - } - - @HiddenApi @Implementation(maxSdk = O_MR1) - protected int[] getArrayStringInfo(int arrayResId) { - return new int[0]; - } - - @HiddenApi @Implementation(maxSdk = O_MR1) - protected Number newTheme() { - return null; - } - - protected TypedArray getTypedArrayResource(Resources resources, int resId) { - TypedResource value = getAndResolve(resId, config, true); - if (value == null) { - return null; - } - List<TypedResource> items = getConverter(value).getItems(value); - return getTypedArray(resources, items, resId); - } - - private TypedArray getTypedArray(Resources resources, List<TypedResource> typedResources, int resId) { - final CharSequence[] stringData = new CharSequence[typedResources.size()]; - final int totalLen = typedResources.size() * STYLE_NUM_ENTRIES; - final int[] data = new int[totalLen]; - - for (int i = 0; i < typedResources.size(); i++) { - final int offset = i * STYLE_NUM_ENTRIES; - TypedResource typedResource = typedResources.get(i); - - // Classify the item. - int type = getResourceType(typedResource); - if (type == -1) { - // This type is unsupported; leave empty. - continue; - } - - final TypedValue typedValue = new TypedValue(); - - if (type == TypedValue.TYPE_REFERENCE) { - final String reference = typedResource.asString(); - ResName refResName = - AttributeResource.getResourceReference( - reference, typedResource.getXmlContext().getPackageName(), null); - typedValue.resourceId = getResourceTable().getResourceId(refResName); - typedValue.data = typedValue.resourceId; - typedResource = resolve(typedResource, config, typedValue.resourceId); - - if (typedResource != null) { - // Reclassify to a non-reference type. - type = getResourceType(typedResource); - if (type == TypedValue.TYPE_ATTRIBUTE) { - type = TypedValue.TYPE_REFERENCE; - } else if (type == -1) { - // This type is unsupported; leave empty. - continue; - } - } - } - - if (type == TypedValue.TYPE_ATTRIBUTE) { - final String reference = typedResource.asString(); - final ResName attrResName = - AttributeResource.getStyleReference( - reference, typedResource.getXmlContext().getPackageName(), "attr"); - typedValue.data = getResourceTable().getResourceId(attrResName); - } - - if (typedResource != null && type != TypedValue.TYPE_NULL && type != TypedValue.TYPE_ATTRIBUTE) { - getConverter(typedResource).fillTypedValue(typedResource.getData(), typedValue); - } - - data[offset + STYLE_TYPE] = type; - data[offset + STYLE_RESOURCE_ID] = typedValue.resourceId; - data[offset + STYLE_DATA] = typedValue.data; - data[offset + STYLE_ASSET_COOKIE] = typedValue.assetCookie; - data[offset + STYLE_CHANGING_CONFIGURATIONS] = typedValue.changingConfigurations; - data[offset + STYLE_DENSITY] = typedValue.density; - stringData[i] = typedResource == null ? null : typedResource.asString(); - } - - int[] indices = new int[typedResources.size() + 1]; /* keep zeroed out */ - return ShadowTypedArray.create(resources, null, data, indices, typedResources.size(), stringData); - } - - private int getResourceType(TypedResource typedResource) { - if (typedResource == null) { - return -1; - } - final ResType resType = typedResource.getResType(); - int type; - if (typedResource.getData() == null || resType == ResType.NULL) { - type = TypedValue.TYPE_NULL; - } else if (typedResource.isReference()) { - type = TypedValue.TYPE_REFERENCE; - } else if (resType == ResType.STYLE) { - type = TypedValue.TYPE_ATTRIBUTE; - } else if (resType == ResType.CHAR_SEQUENCE || resType == ResType.DRAWABLE) { - type = TypedValue.TYPE_STRING; - } else if (resType == ResType.INTEGER) { - type = TypedValue.TYPE_INT_DEC; - } else if (resType == ResType.FLOAT || resType == ResType.FRACTION) { - type = TypedValue.TYPE_FLOAT; - } else if (resType == ResType.BOOLEAN) { - type = TypedValue.TYPE_INT_BOOLEAN; - } else if (resType == ResType.DIMEN) { - type = TypedValue.TYPE_DIMENSION; - } else if (resType == ResType.COLOR) { - type = TypedValue.TYPE_INT_COLOR_ARGB8; - } else if (resType == ResType.TYPED_ARRAY || resType == ResType.CHAR_SEQUENCE_ARRAY) { - type = TypedValue.TYPE_REFERENCE; - } else { - type = -1; - } - return type; - } - - @HiddenApi - @Implementation - public long createTheme() { - synchronized (nativeThemes) { - long nativePtr = nextInternalThemeId++; - nativeThemes.put(nativePtr, new NativeTheme(new ThemeStyleSet())); - return nativePtr; - } - } - - @HiddenApi - @Implementation(maxSdk = O_MR1) - protected static void dumpTheme(long theme, int priority, String tag, String prefix) { - throw new UnsupportedOperationException("not yet implemented"); - } - - - private static NativeTheme getNativeTheme(long themePtr) { - NativeTheme nativeTheme; - synchronized (nativeThemes) { - nativeTheme = nativeThemes.get(themePtr); - } - if (nativeTheme == null) { - throw new RuntimeException("no theme " + themePtr + " found in AssetManager"); - } - return nativeTheme; - } - - @HiddenApi - @Implementation - public void releaseTheme(long themePtr) { - synchronized (nativeThemes) { - nativeThemes.remove(themePtr); - } - } - - @HiddenApi - @Implementation(maxSdk = O_MR1) - protected void deleteTheme(long theme) { - // no op - } - - @HiddenApi - @Implementation(maxSdk = O_MR1) - public static void applyThemeStyle(long themePtr, int styleRes, boolean force) { - NativeTheme nativeTheme = getNativeTheme(themePtr); - Style style = nativeTheme.getShadowAssetManager().resolveStyle(styleRes, null); - nativeTheme.themeStyleSet.apply(style, force); - } - - @HiddenApi - @Implementation(maxSdk = O_MR1) - public static void copyTheme(long destPtr, long sourcePtr) { - NativeTheme destNativeTheme = getNativeTheme(destPtr); - NativeTheme sourceNativeTheme = getNativeTheme(sourcePtr); - destNativeTheme.themeStyleSet = sourceNativeTheme.themeStyleSet.copy(); - } - - @HiddenApi @Implementation(minSdk = P, maxSdk = P) - protected static void nativeThemeCopy(long destPtr, long sourcePtr) { - copyTheme(destPtr, sourcePtr); - } - - @HiddenApi - @Implementation(minSdk = VERSION_CODES.Q) - protected static void nativeThemeCopy( - long dstAssetManagerPtr, long dstThemePtr, long srcAssetManagerPtr, long srcThemePtr) { - copyTheme(dstThemePtr, srcThemePtr); - } - - @HiddenApi - @Implementation(minSdk = O, maxSdk = O_MR1) - protected static void applyStyle( - long themeToken, - int defStyleAttr, - int defStyleRes, - long xmlParserToken, - int[] inAttrs, - int length, - long outValuesAddress, - long outIndicesAddress) { - ShadowVMRuntime shadowVMRuntime = Shadow.extract(VMRuntime.getRuntime()); - int[] outValues = (int[])shadowVMRuntime.getObjectForAddress(outValuesAddress); - int[] outIndices = (int[])shadowVMRuntime.getObjectForAddress(outIndicesAddress); - applyStyle( - themeToken, defStyleAttr, defStyleRes, xmlParserToken, inAttrs, outValues, outIndices); - } - - @HiddenApi @Implementation(minSdk = P) - protected void applyStyleToTheme(long themePtr, int resId, boolean force) { - applyThemeStyle(themePtr, resId, force); - } - - @HiddenApi - @Implementation(maxSdk = N_MR1) - protected static boolean applyStyle( - long themeToken, - int defStyleAttr, - int defStyleRes, - long xmlParserToken, - int[] attrs, - int[] outValues, - int[] outIndices) { - // no-op - return false; - } - - @HiddenApi - @Implementation(maxSdk = O_MR1) - protected static boolean resolveAttrs( - long themeToken, - int defStyleAttr, - int defStyleRes, - int[] inValues, - int[] attrs, - int[] outValues, - int[] outIndices) { - // no-op - return false; - } - - @Implementation(maxSdk = O_MR1) - protected boolean retrieveAttributes( - long xmlParserToken, int[] attrs, int[] outValues, int[] outIndices) { - return false; - } - - @HiddenApi - @Implementation(maxSdk = O_MR1) - protected static int loadThemeAttributeValue( - long themeHandle, int ident, TypedValue outValue, boolean resolve) { - // no-op - return 0; - } - - ///////////////////////// - - Style resolveStyle(int resId, Style themeStyleSet) { - return resolveStyle(getResName(resId), themeStyleSet); - } - - private Style resolveStyle(@Nonnull ResName themeStyleName, Style themeStyleSet) { - TypedResource themeStyleResource = getResourceTable().getValue(themeStyleName, config); - if (themeStyleResource == null) return null; - StyleData themeStyleData = (StyleData) themeStyleResource.getData(); - if (themeStyleSet == null) { - themeStyleSet = new ThemeStyleSet(); - } - return new StyleResolver( - getResourceTable(), - legacyShadowOf(AssetManager.getSystem()).getResourceTable(), - themeStyleData, - themeStyleSet, - themeStyleName, - config); - } - - private TypedResource getAndResolve(int resId, ResTable_config config, boolean resolveRefs) { - TypedResource value = getResourceTable().getValue(resId, config); - if (resolveRefs) { - value = resolve(value, config, resId); - } - return value; - } - - TypedResource resolve(TypedResource value, ResTable_config config, int resId) { - return resolveResourceValue(value, config, resId); - } - - protected ResName resolveResName(ResName resName, ResTable_config config) { - TypedResource value = getResourceTable().getValue(resName, config); - return resolveResource(value, config, resName); - } - - // todo: DRY up #resolveResource vs #resolveResourceValue - private ResName resolveResource(TypedResource value, ResTable_config config, ResName resName) { - while (value != null && value.isReference()) { - String s = value.asString(); - if (AttributeResource.isNull(s) || AttributeResource.isEmpty(s)) { - value = null; - } else { - String refStr = s.substring(1).replace("+", ""); - resName = ResName.qualifyResName(refStr, resName); - value = getResourceTable().getValue(resName, config); - } - } - - return resName; - } - - private TypedResource resolveResourceValue(TypedResource value, ResTable_config config, ResName resName) { - while (value != null && value.isReference()) { - String s = value.asString(); - if (AttributeResource.isNull(s) || AttributeResource.isEmpty(s)) { - value = null; - } else { - String refStr = s.substring(1).replace("+", ""); - resName = ResName.qualifyResName(refStr, resName); - value = getResourceTable().getValue(resName, config); - } - } - - return value; - } - - protected TypedResource resolveResourceValue(TypedResource value, ResTable_config config, int resId) { - ResName resName = getResName(resId); - return resolveResourceValue(value, config, resName); - } - - private TypedValue buildTypedValue(AttributeSet set, int resId, int defStyleAttr, Style themeStyleSet, int defStyleRes) { - /* - * When determining the final value of a particular attribute, there are four inputs that come into play: - * - * 1. Any attribute values in the given AttributeSet. - * 2. The style resource specified in the AttributeSet (named "style"). - * 3. The default style specified by defStyleAttr and defStyleRes - * 4. The base values in this theme. - */ - Style defStyleFromAttr = null; - Style defStyleFromRes = null; - Style styleAttrStyle = null; - - if (defStyleAttr != 0) { - // Load the theme attribute for the default style attributes. E.g., attr/buttonStyle - ResName defStyleName = getResName(defStyleAttr); - - // Load the style for the default style attribute. E.g. "@style/Widget.Robolectric.Button"; - AttributeResource defStyleAttribute = themeStyleSet.getAttrValue(defStyleName); - if (defStyleAttribute != null) { - while (defStyleAttribute.isStyleReference()) { - AttributeResource other = themeStyleSet.getAttrValue(defStyleAttribute.getStyleReference()); - if (other == null) { - throw new RuntimeException("couldn't dereference " + defStyleAttribute); - } - defStyleAttribute = other; - } - - if (defStyleAttribute.isResourceReference()) { - ResName defStyleResName = defStyleAttribute.getResourceReference(); - defStyleFromAttr = resolveStyle(defStyleResName, themeStyleSet); - } - } - } - - if (set != null && set.getStyleAttribute() != 0) { - ResName styleAttributeResName = getResName(set.getStyleAttribute()); - while (styleAttributeResName.type.equals("attr")) { - AttributeResource attrValue = themeStyleSet.getAttrValue(styleAttributeResName); - if (attrValue == null) { - throw new RuntimeException( - "no value for " - + styleAttributeResName.getFullyQualifiedName() - + " in " - + themeStyleSet); - } - if (attrValue.isResourceReference()) { - styleAttributeResName = attrValue.getResourceReference(); - } else if (attrValue.isStyleReference()) { - styleAttributeResName = attrValue.getStyleReference(); - } - } - styleAttrStyle = resolveStyle(styleAttributeResName, themeStyleSet); - } - - if (defStyleRes != 0) { - ResName resName = getResName(defStyleRes); - if (resName.type.equals("attr")) { - // todo: this should be a style resId, not an attr - System.out.println("WARN: " + resName.getFullyQualifiedName() + " should be a style resId"); - // AttributeResource attributeValue = findAttributeValue(defStyleRes, set, styleAttrStyle, defStyleFromAttr, defStyleFromAttr, themeStyleSet); - // if (attributeValue != null) { - // if (attributeValue.isStyleReference()) { - // resName = themeStyleSet.getAttrValue(attributeValue.getStyleReference()).getResourceReference(); - // } else if (attributeValue.isResourceReference()) { - // resName = attributeValue.getResourceReference(); - // } - // } - } else if (resName.type.equals("style")) { - defStyleFromRes = resolveStyle(resName, themeStyleSet); - } - } - - AttributeResource attribute = findAttributeValue(resId, set, styleAttrStyle, defStyleFromAttr, defStyleFromRes, themeStyleSet); - while (attribute != null && attribute.isStyleReference()) { - ResName otherAttrName = attribute.getStyleReference(); - if (attribute.resName.equals(otherAttrName)) { - Logger.info("huh... circular reference for %s?", attribute.resName.getFullyQualifiedName()); - return null; - } - ResName resName = getResourceTable().getResName(resId); - - AttributeResource otherAttr = themeStyleSet.getAttrValue(otherAttrName); - if (otherAttr == null) { - strictError( - "no such attr %s in %s while resolving value for %s", - attribute.value, themeStyleSet, resName.getFullyQualifiedName()); - attribute = null; - } else { - attribute = new AttributeResource(resName, otherAttr.value, otherAttr.contextPackageName); - } - } - - if (attribute == null || attribute.isNull()) { - return null; - } else { - TypedValue typedValue = new TypedValue(); - convertAndFill(attribute, typedValue, config, true); - return typedValue; - } - } - - private void strictError(String message, Object... args) { - if (strictErrors) { - throw new RuntimeException(String.format(message, args)); - } else { - Logger.strict(message, args); - } - } - - TypedArray attrsToTypedArray(Resources resources, AttributeSet set, int[] attrs, int defStyleAttr, long nativeTheme, int defStyleRes) { - CharSequence[] stringData = new CharSequence[attrs.length]; - int[] data = new int[attrs.length * STYLE_NUM_ENTRIES]; - int[] indices = new int[attrs.length + 1]; - int nextIndex = 0; - - Style themeStyleSet = nativeTheme == 0 - ? new EmptyStyle() - : getNativeTheme(nativeTheme).themeStyleSet; - - for (int i = 0; i < attrs.length; i++) { - int offset = i * STYLE_NUM_ENTRIES; - - TypedValue typedValue = buildTypedValue(set, attrs[i], defStyleAttr, themeStyleSet, defStyleRes); - if (typedValue != null) { - //noinspection PointlessArithmeticExpression - data[offset + STYLE_TYPE] = typedValue.type; - data[offset + STYLE_DATA] = typedValue.type == TypedValue.TYPE_STRING ? i : typedValue.data; - data[offset + STYLE_ASSET_COOKIE] = typedValue.assetCookie; - data[offset + STYLE_RESOURCE_ID] = typedValue.resourceId; - data[offset + STYLE_CHANGING_CONFIGURATIONS] = typedValue.changingConfigurations; - data[offset + STYLE_DENSITY] = typedValue.density; - stringData[i] = typedValue.string; - - indices[nextIndex + 1] = i; - nextIndex++; - } - } - - indices[0] = nextIndex; - - TypedArray typedArray = ShadowTypedArray.create(resources, attrs, data, indices, nextIndex, stringData); - if (set != null) { - ShadowTypedArray shadowTypedArray = Shadow.extract(typedArray); - shadowTypedArray.positionDescription = set.getPositionDescription(); - } - return typedArray; - } - - private AttributeResource findAttributeValue(int resId, AttributeSet attributeSet, Style styleAttrStyle, Style defStyleFromAttr, Style defStyleFromRes, @Nonnull Style themeStyleSet) { - if (attributeSet != null) { - for (int i = 0; i < attributeSet.getAttributeCount(); i++) { - if (attributeSet.getAttributeNameResource(i) == resId) { - String attributeValue; - try { - attributeValue = attributeSet.getAttributeValue(i); - } catch (IndexOutOfBoundsException e) { - // type is TypedValue.TYPE_NULL, ignore... - continue; - } - if (attributeValue != null) { - String defaultPackageName = - ResourceIds.isFrameworkResource(resId) - ? "android" - : RuntimeEnvironment.getApplication().getPackageName(); - ResName resName = - ResName.qualifyResName( - attributeSet.getAttributeName(i), defaultPackageName, "attr"); - Integer referenceResId = null; - if (AttributeResource.isResourceReference(attributeValue)) { - referenceResId = attributeSet.getAttributeResourceValue(i, -1); - // binary AttributeSet references have a string value of @resId rather than fully qualified resource name - if (referenceResId != 0) { - ResName refResName = getResourceTable().getResName(referenceResId); - if (refResName != null) { - attributeValue = "@" + refResName.getFullyQualifiedName(); - } - } - } - return new AttributeResource(resName, attributeValue, "fixme!!!", referenceResId); - } - } - } - } - - ResName attrName = getResourceTable().getResName(resId); - if (attrName == null) return null; - - if (styleAttrStyle != null) { - AttributeResource attribute = styleAttrStyle.getAttrValue(attrName); - if (attribute != null) { - return attribute; - } - } - - // else if attr in defStyleFromAttr, use its value - if (defStyleFromAttr != null) { - AttributeResource attribute = defStyleFromAttr.getAttrValue(attrName); - if (attribute != null) { - return attribute; - } - } - - if (defStyleFromRes != null) { - AttributeResource attribute = defStyleFromRes.getAttrValue(attrName); - if (attribute != null) { - return attribute; - } - } - - // else if attr in theme, use its value - return themeStyleSet.getAttrValue(attrName); - } - - @Override - Collection<Path> getAllAssetDirs() { - return assetDirs; - } - - @Nonnull private ResName getResName(int id) { - ResName resName = getResourceTable().getResName(id); - if (resName == null) { - throw new Resources.NotFoundException("Resource ID #0x" + Integer.toHexString(id)); - } - return resName; - } - - @Implementation - protected String getResourceName(int resid) { - return getResName(resid).getFullyQualifiedName(); - } - - @Implementation - protected String getResourcePackageName(int resid) { - return getResName(resid).packageName; - } - - @Implementation - protected String getResourceTypeName(int resid) { - return getResName(resid).type; - } - - @Implementation - protected String getResourceEntryName(int resid) { - return getResName(resid).name; - } - - @Implementation(maxSdk = O_MR1) - protected int getArraySize(int id) { - return 0; - } - - @Implementation(maxSdk = O_MR1) - protected int retrieveArray(int id, int[] outValues) { - return 0; - } - - @Implementation(maxSdk = O_MR1) - protected Number getNativeStringBlock(int block) { - throw new IllegalStateException(); - } - - @Implementation(maxSdk = O_MR1) - protected SparseArray<String> getAssignedPackageIdentifiers() { - return new SparseArray<>(); - } - - @Implementation(maxSdk = O_MR1) - protected int loadResourceValue(int ident, short density, TypedValue outValue, boolean resolve) { - return 0; - } - - @Implementation(maxSdk = O_MR1) - protected int loadResourceBagValue(int ident, int bagEntryId, TypedValue outValue, boolean resolve) { - return 0; - } - - // static void NativeAssetDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) { - @Implementation(minSdk = P) - protected static void nativeAssetDestroy(long asset_ptr) { - ShadowArscAssetManager9.nativeAssetDestroy(asset_ptr); - } - - // static jint NativeAssetReadChar(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) { - @Implementation(minSdk = P) - protected static int nativeAssetReadChar(long asset_ptr) { - return ShadowArscAssetManager9.nativeAssetReadChar(asset_ptr); - } - - // static jint NativeAssetRead(JNIEnv* env, jclass /*clazz*/, jlong asset_ptr, jbyteArray java_buffer, -// jint offset, jint len) { - @Implementation(minSdk = P) - protected static int nativeAssetRead(long asset_ptr, byte[] java_buffer, int offset, int len) - throws IOException { - return ShadowArscAssetManager9.nativeAssetRead(asset_ptr, java_buffer, offset, len); - } - - // static jlong NativeAssetSeek(JNIEnv* env, jclass /*clazz*/, jlong asset_ptr, jlong offset, -// jint whence) { - @Implementation(minSdk = P) - protected static long nativeAssetSeek(long asset_ptr, long offset, int whence) { - return ShadowArscAssetManager9.nativeAssetSeek(asset_ptr, offset, whence); - } - - // static jlong NativeAssetGetLength(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) { - @Implementation(minSdk = P) - protected static long nativeAssetGetLength(long asset_ptr) { - return ShadowArscAssetManager9.nativeAssetGetLength(asset_ptr); - } - - // static jlong NativeAssetGetRemainingLength(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) { - @Implementation(minSdk = P) - protected static long nativeAssetGetRemainingLength(long asset_ptr) { - return ShadowArscAssetManager9.nativeAssetGetRemainingLength(asset_ptr); - } - - @Implementation(minSdk = VERSION_CODES.Q, maxSdk = VERSION_CODES.R) - protected static String[] nativeCreateIdmapsForStaticOverlaysTargetingAndroid() { - return new String[0]; - } - - @VisibleForTesting - @Override - long getNativePtr() { - return 0; - } - - @ForType(AssetManager.class) - interface AssetManagerReflector { - - @Direct - void setApkAssets(ApkAssets[] apkAssetsObject, boolean invalidateCachesObject); - } -} diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyResourcesImpl.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyResourcesImpl.java deleted file mode 100644 index 8f0243b11..000000000 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyResourcesImpl.java +++ /dev/null @@ -1,186 +0,0 @@ -package org.robolectric.shadows; - -import static android.os.Build.VERSION_CODES.M; -import static android.os.Build.VERSION_CODES.N; -import static android.os.Build.VERSION_CODES.N_MR1; -import static android.os.Build.VERSION_CODES.O; -import static org.robolectric.shadows.ShadowAssetManager.legacyShadowOf; -import static org.robolectric.util.reflector.Reflector.reflector; - -import android.content.res.AssetFileDescriptor; -import android.content.res.AssetManager; -import android.content.res.Resources; -import android.content.res.ResourcesImpl; -import android.content.res.TypedArray; -import android.content.res.XmlResourceParser; -import android.graphics.drawable.Drawable; -import android.os.ParcelFileDescriptor; -import android.util.AttributeSet; -import android.util.TypedValue; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Locale; -import org.robolectric.annotation.HiddenApi; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; -import org.robolectric.annotation.RealObject; -import org.robolectric.res.Plural; -import org.robolectric.res.PluralRules; -import org.robolectric.res.ResName; -import org.robolectric.res.ResType; -import org.robolectric.res.ResourceTable; -import org.robolectric.res.TypedResource; -import org.robolectric.shadows.ShadowResourcesImpl.Picker; -import org.robolectric.util.ReflectionHelpers; -import org.robolectric.util.reflector.Direct; -import org.robolectric.util.reflector.ForType; - -@SuppressWarnings("NewApi") -@Implements( - value = ResourcesImpl.class, - isInAndroidSdk = false, - minSdk = N, - shadowPicker = Picker.class) -public class ShadowLegacyResourcesImpl extends ShadowResourcesImpl { - - @RealObject private ResourcesImpl realResourcesImpl; - - @Implementation(maxSdk = M) - public String getQuantityString(int id, int quantity, Object... formatArgs) throws Resources.NotFoundException { - String raw = getQuantityString(id, quantity); - return String.format(Locale.ENGLISH, raw, formatArgs); - } - - @Implementation(maxSdk = M) - public String getQuantityString(int resId, int quantity) throws Resources.NotFoundException { - ShadowLegacyAssetManager shadowAssetManager = legacyShadowOf(realResourcesImpl.getAssets()); - - TypedResource typedResource = shadowAssetManager.getResourceTable().getValue(resId, shadowAssetManager.config); - if (typedResource != null && typedResource instanceof PluralRules) { - PluralRules pluralRules = (PluralRules) typedResource; - Plural plural = pluralRules.find(quantity); - - if (plural == null) { - return null; - } - - TypedResource<?> resolvedTypedResource = - shadowAssetManager.resolve( - new TypedResource<>( - plural.getString(), ResType.CHAR_SEQUENCE, pluralRules.getXmlContext()), - shadowAssetManager.config, - resId); - return resolvedTypedResource == null ? null : resolvedTypedResource.asString(); - } else { - return null; - } - } - - @Implementation(maxSdk = M) - public InputStream openRawResource(int id) throws Resources.NotFoundException { - ShadowLegacyAssetManager shadowAssetManager = legacyShadowOf(realResourcesImpl.getAssets()); - ResourceTable resourceTable = shadowAssetManager.getResourceTable(); - InputStream inputStream = resourceTable.getRawValue(id, shadowAssetManager.config); - if (inputStream == null) { - throw newNotFoundException(id); - } else { - return inputStream; - } - } - - /** - * Since {@link AssetFileDescriptor}s are not yet supported by Robolectric, {@code null} will - * be returned if the resource is found. If the resource cannot be found, {@link Resources.NotFoundException} will - * be thrown. - */ - @Implementation(maxSdk = M) - public AssetFileDescriptor openRawResourceFd(int id) throws Resources.NotFoundException { - InputStream inputStream = openRawResource(id); - if (!(inputStream instanceof FileInputStream)) { - // todo fixme - return null; - } - - FileInputStream fis = (FileInputStream) inputStream; - try { - return new AssetFileDescriptor(ParcelFileDescriptor.dup(fis.getFD()), 0, fis.getChannel().size()); - } catch (IOException e) { - throw newNotFoundException(id); - } - } - - private Resources.NotFoundException newNotFoundException(int id) { - ResourceTable resourceTable = legacyShadowOf(realResourcesImpl.getAssets()).getResourceTable(); - ResName resName = resourceTable.getResName(id); - if (resName == null) { - return new Resources.NotFoundException("resource ID #0x" + Integer.toHexString(id)); - } else { - return new Resources.NotFoundException(resName.getFullyQualifiedName()); - } - } - - @HiddenApi @Implementation(maxSdk = M) - public XmlResourceParser loadXmlResourceParser(int resId, String type) throws Resources.NotFoundException { - ShadowLegacyAssetManager shadowAssetManager = legacyShadowOf(realResourcesImpl.getAssets()); - return shadowAssetManager.loadXmlResourceParser(resId, type); - } - - @HiddenApi @Implementation - public XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie, String type) throws Resources.NotFoundException { - return loadXmlResourceParser(id, type); - } - - @Implements( - value = ResourcesImpl.ThemeImpl.class, - minSdk = N, - isInAndroidSdk = false, - shadowPicker = ShadowResourcesImpl.ShadowThemeImpl.Picker.class) - public static class ShadowLegacyThemeImpl extends ShadowThemeImpl { - @RealObject ResourcesImpl.ThemeImpl realThemeImpl; - - @Implementation - public TypedArray obtainStyledAttributes(Resources.Theme wrapper, AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) { - Resources resources = wrapper.getResources(); - AssetManager assets = resources.getAssets(); - return legacyShadowOf(assets) - .attrsToTypedArray(resources, set, attrs, defStyleAttr, getNativePtr(), defStyleRes); - } - - public long getNativePtr() { - return ReflectionHelpers.getField(realThemeImpl, "mTheme"); - } - } - - @Implementation(maxSdk = N_MR1) - public Drawable loadDrawable(Resources wrapper, TypedValue value, int id, Resources.Theme theme, boolean useCache) throws Resources.NotFoundException { - Drawable drawable = - reflector(ResourcesImplReflector.class, realResourcesImpl) - .loadDrawable(wrapper, value, id, theme, useCache); - - ShadowResources.setCreatedFromResId(wrapper, id, drawable); - return drawable; - } - - @Implementation(minSdk = O) - public Drawable loadDrawable(Resources wrapper, TypedValue value, int id, int density, Resources.Theme theme) { - Drawable drawable = - reflector(ResourcesImplReflector.class, realResourcesImpl) - .loadDrawable(wrapper, value, id, density, theme); - - ShadowResources.setCreatedFromResId(wrapper, id, drawable); - return drawable; - } - - @ForType(ResourcesImpl.class) - interface ResourcesImplReflector { - - @Direct - Drawable loadDrawable( - Resources wrapper, TypedValue value, int id, Resources.Theme theme, boolean useCache); - - @Direct - Drawable loadDrawable( - Resources wrapper, TypedValue value, int id, int density, Resources.Theme theme); - } -} diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResources.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResources.java index ae6906a2b..e9b3f77e5 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResources.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResources.java @@ -1,12 +1,8 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.LOLLIPOP; -import static android.os.Build.VERSION_CODES.M; -import static android.os.Build.VERSION_CODES.N; import static android.os.Build.VERSION_CODES.N_MR1; import static android.os.Build.VERSION_CODES.Q; -import static org.robolectric.shadows.ShadowAssetManager.legacyShadowOf; import static org.robolectric.util.reflector.Reflector.reflector; import android.content.res.AssetFileDescriptor; @@ -15,7 +11,6 @@ import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.Resources.NotFoundException; -import android.content.res.ResourcesImpl; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; @@ -38,10 +33,7 @@ import org.robolectric.annotation.Implements; import org.robolectric.annotation.RealObject; import org.robolectric.annotation.Resetter; import org.robolectric.internal.bytecode.ShadowedObject; -import org.robolectric.res.ResName; -import org.robolectric.res.ResourceTable; import org.robolectric.shadow.api.Shadow; -import org.robolectric.shadows.ShadowLegacyResourcesImpl.ShadowLegacyThemeImpl; import org.robolectric.util.ReflectionHelpers; import org.robolectric.util.reflector.Direct; import org.robolectric.util.reflector.ForType; @@ -79,55 +71,6 @@ public class ShadowResources { return system; } - @Implementation - protected TypedArray obtainAttributes(AttributeSet set, int[] attrs) { - return reflector(ResourcesReflector.class, realResources).obtainAttributes(set, attrs); - } - - @Implementation - protected String getQuantityString(int id, int quantity, Object... formatArgs) - throws Resources.NotFoundException { - - return reflector(ResourcesReflector.class, realResources) - .getQuantityString(id, quantity, formatArgs); - } - - @Implementation - protected String getQuantityString(int resId, int quantity) throws Resources.NotFoundException { - return reflector(ResourcesReflector.class, realResources).getQuantityString(resId, quantity); - } - - @Implementation - protected InputStream openRawResource(int id) throws Resources.NotFoundException { - - return reflector(ResourcesReflector.class, realResources).openRawResource(id); - } - - /** - * Since {@link AssetFileDescriptor}s are not yet supported by Robolectric, {@code null} will be - * returned if the resource is found. If the resource cannot be found, {@link - * Resources.NotFoundException} will be thrown. - */ - @Implementation - protected AssetFileDescriptor openRawResourceFd(int id) throws Resources.NotFoundException { - return reflector(ResourcesReflector.class, realResources).openRawResourceFd(id); - } - - private Resources.NotFoundException newNotFoundException(int id) { - ResourceTable resourceTable = legacyShadowOf(realResources.getAssets()).getResourceTable(); - ResName resName = resourceTable.getResName(id); - if (resName == null) { - return new Resources.NotFoundException("resource ID #0x" + Integer.toHexString(id)); - } else { - return new Resources.NotFoundException(resName.getFullyQualifiedName()); - } - } - - @Implementation - protected TypedArray obtainTypedArray(int id) throws Resources.NotFoundException { - return reflector(ResourcesReflector.class, realResources).obtainTypedArray(id); - } - @HiddenApi @Implementation protected XmlResourceParser loadXmlResourceParser(int resId, String type) @@ -248,63 +191,6 @@ public class ShadowResources { } } - /** Base class for shadows of {@link Resources.Theme}. */ - public abstract static class ShadowTheme { - - /** Shadow picker for {@link ShadowTheme}. */ - public static class Picker extends ResourceModeShadowPicker<ShadowTheme> { - - public Picker() { - super(ShadowLegacyTheme.class, null, null); - } - } - } - - /** Shadow for {@link Resources.Theme}. */ - @Implements(value = Resources.Theme.class, shadowPicker = ShadowTheme.Picker.class) - public static class ShadowLegacyTheme extends ShadowTheme { - @RealObject Resources.Theme realTheme; - - long getNativePtr() { - if (RuntimeEnvironment.getApiLevel() >= N) { - ResourcesImpl.ThemeImpl themeImpl = ReflectionHelpers.getField(realTheme, "mThemeImpl"); - return ((ShadowLegacyThemeImpl) Shadow.extract(themeImpl)).getNativePtr(); - } else { - return ((Number) ReflectionHelpers.getField(realTheme, "mTheme")).longValue(); - } - } - - @Implementation(maxSdk = M) - protected TypedArray obtainStyledAttributes(int[] attrs) { - return obtainStyledAttributes(0, attrs); - } - - @Implementation(maxSdk = M) - protected TypedArray obtainStyledAttributes(int resid, int[] attrs) - throws Resources.NotFoundException { - return obtainStyledAttributes(null, attrs, 0, resid); - } - - @Implementation(maxSdk = M) - protected TypedArray obtainStyledAttributes( - AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) { - return getShadowAssetManager() - .attrsToTypedArray( - innerGetResources(), set, attrs, defStyleAttr, getNativePtr(), defStyleRes); - } - - private ShadowLegacyAssetManager getShadowAssetManager() { - return legacyShadowOf(innerGetResources().getAssets()); - } - - private Resources innerGetResources() { - if (RuntimeEnvironment.getApiLevel() >= LOLLIPOP) { - return realTheme.getResources(); - } - return ReflectionHelpers.getField(realTheme, "this$0"); - } - } - static void setCreatedFromResId(Resources resources, int id, Drawable drawable) { // todo: this kinda sucks, find some better way... if (drawable != null && Shadow.extract(drawable) instanceof ShadowDrawable) { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResourcesImpl.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResourcesImpl.java index 97f96efe3..7cfe1954d 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResourcesImpl.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResourcesImpl.java @@ -6,15 +6,13 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; -import org.robolectric.shadows.ShadowLegacyResourcesImpl.ShadowLegacyThemeImpl; abstract public class ShadowResourcesImpl { public static class Picker extends ResourceModeShadowPicker<ShadowResourcesImpl> { public Picker() { - super(ShadowLegacyResourcesImpl.class, ShadowArscResourcesImpl.class, - ShadowArscResourcesImpl.class); + super(ShadowArscResourcesImpl.class, ShadowArscResourcesImpl.class); } } @@ -47,13 +45,4 @@ abstract public class ShadowResourcesImpl { } return resettableArrays; } - - abstract public static class ShadowThemeImpl { - public static class Picker extends ResourceModeShadowPicker<ShadowThemeImpl> { - - public Picker() { - super(ShadowLegacyThemeImpl.class, null, null); - } - } - } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTypedArray.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTypedArray.java deleted file mode 100644 index 05c050957..000000000 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTypedArray.java +++ /dev/null @@ -1,156 +0,0 @@ -package org.robolectric.shadows; - -import static org.robolectric.res.android.AttributeResolution.STYLE_ASSET_COOKIE; -import static org.robolectric.res.android.AttributeResolution.STYLE_CHANGING_CONFIGURATIONS; -import static org.robolectric.res.android.AttributeResolution.STYLE_DATA; -import static org.robolectric.res.android.AttributeResolution.STYLE_DENSITY; -import static org.robolectric.res.android.AttributeResolution.STYLE_NUM_ENTRIES; -import static org.robolectric.res.android.AttributeResolution.STYLE_RESOURCE_ID; -import static org.robolectric.res.android.AttributeResolution.STYLE_TYPE; -import static org.robolectric.util.reflector.Reflector.reflector; - -import android.annotation.StyleableRes; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.os.Build; -import android.util.TypedValue; -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableMap; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.HiddenApi; -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; -import org.robolectric.util.ReflectionHelpers.ClassParameter; -import org.robolectric.util.reflector.Direct; -import org.robolectric.util.reflector.ForType; - -@SuppressWarnings({"UnusedDeclaration"}) -@Implements(value = TypedArray.class, shadowPicker = ShadowTypedArray.Picker.class) -public class ShadowTypedArray { - public static class Picker extends ResourceModeShadowPicker<ShadowTypedArray> { - public Picker() { - super(ShadowTypedArray.class, null, null); - } - } - - @RealObject private TypedArray realTypedArray; - private CharSequence[] stringData; - public String positionDescription; - - public static TypedArray create(Resources realResources, int[] attrs, int[] data, int[] indices, int len, CharSequence[] stringData) { - TypedArray typedArray; - if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.O) { - typedArray = - ReflectionHelpers.callConstructor( - TypedArray.class, ClassParameter.from(Resources.class, realResources)); - ReflectionHelpers.setField(typedArray, "mData", data); - ReflectionHelpers.setField(typedArray, "mLength", len); - ReflectionHelpers.setField(typedArray, "mIndices", indices); - } else { - typedArray = - ReflectionHelpers.callConstructor( - TypedArray.class, - ClassParameter.from(Resources.class, realResources), - ClassParameter.from(int[].class, data), - ClassParameter.from(int[].class, indices), - ClassParameter.from(int.class, len)); - } - - ShadowTypedArray shadowTypedArray = Shadow.extract(typedArray); - shadowTypedArray.stringData = stringData; - return typedArray; - } - - @HiddenApi @Implementation - protected CharSequence loadStringValueAt(int index) { - return stringData[index / STYLE_NUM_ENTRIES]; - } - - @Implementation - protected String getNonResourceString(@StyleableRes int index) { - return reflector(TypedArrayReflector.class, realTypedArray).getString(index); - } - - @Implementation - protected String getNonConfigurationString(@StyleableRes int index, int allowedChangingConfigs) { - return reflector(TypedArrayReflector.class, realTypedArray).getString(index); - } - - @Implementation - protected String getPositionDescription() { - return positionDescription; - } - - @SuppressWarnings("NewApi") - public static void dump(TypedArray typedArray) { - int[] data = ReflectionHelpers.getField(typedArray, "mData"); - - StringBuilder result = new StringBuilder(); - for (int index = 0; index < data.length; index+= STYLE_NUM_ENTRIES) { - final int type = data[index+STYLE_TYPE]; - result.append("Index: ").append(index / STYLE_NUM_ENTRIES).append(System.lineSeparator()); - result - .append(Strings.padEnd("Type: ", 25, ' ')) - .append(TYPE_MAP.get(type)) - .append(System.lineSeparator()); - if (type != TypedValue.TYPE_NULL) { - result - .append(Strings.padEnd("Style data: ", 25, ' ')) - .append(data[index + STYLE_DATA]) - .append(System.lineSeparator()); - result - .append(Strings.padEnd("Asset cookie ", 25, ' ')) - .append(data[index + STYLE_ASSET_COOKIE]) - .append(System.lineSeparator()); - result - .append(Strings.padEnd("Style resourceId: ", 25, ' ')) - .append(data[index + STYLE_RESOURCE_ID]) - .append(System.lineSeparator()); - result - .append(Strings.padEnd("Changing configurations ", 25, ' ')) - .append(data[index + STYLE_CHANGING_CONFIGURATIONS]) - .append(System.lineSeparator()); - result - .append(Strings.padEnd("Style density: ", 25, ' ')) - .append(data[index + STYLE_DENSITY]) - .append(System.lineSeparator()); - if (type == TypedValue.TYPE_STRING) { - ShadowTypedArray shadowTypedArray = Shadow.extract(typedArray); - result - .append(Strings.padEnd("Style value: ", 25, ' ')) - .append(shadowTypedArray.loadStringValueAt(index)) - .append(System.lineSeparator()); - } - } - result.append(System.lineSeparator()); - } - System.out.println(result.toString()); - } - - private static final ImmutableMap<Integer, String> TYPE_MAP = ImmutableMap.<Integer, String>builder() - .put(TypedValue.TYPE_NULL, "TYPE_NULL") - .put(TypedValue.TYPE_REFERENCE, "TYPE_REFERENCE") - .put(TypedValue.TYPE_ATTRIBUTE, "TYPE_ATTRIBUTE") - .put(TypedValue.TYPE_STRING, "TYPE_STRING") - .put(TypedValue.TYPE_FLOAT, "TYPE_FLOAT") - .put(TypedValue.TYPE_DIMENSION, "TYPE_DIMENSION") - .put(TypedValue.TYPE_FRACTION, "TYPE_FRACTION") - .put(TypedValue.TYPE_INT_DEC, "TYPE_INT_DEC") - .put(TypedValue.TYPE_INT_HEX, "TYPE_INT_HEX") - .put(TypedValue.TYPE_INT_BOOLEAN, "TYPE_INT_BOOLEAN") - .put(TypedValue.TYPE_INT_COLOR_ARGB8, "TYPE_INT_COLOR_ARGB8") - .put(TypedValue.TYPE_INT_COLOR_RGB8, "TYPE_INT_COLOR_RGB8") - .put(TypedValue.TYPE_INT_COLOR_ARGB4, "TYPE_INT_COLOR_ARGB4") - .put(TypedValue.TYPE_INT_COLOR_RGB4, "TYPE_INT_COLOR_RGB4") - .build(); - - @ForType(TypedArray.class) - interface TypedArrayReflector { - - @Direct - String getString(int index); - } -} |