| package org.robolectric.shadows; |
| |
| 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.ParcelFileDescriptor; |
| import android.util.AttributeSet; |
| import android.util.TypedValue; |
| |
| import org.jetbrains.annotations.NotNull; |
| 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.res.Attribute; |
| import org.robolectric.res.DrawableNode; |
| import org.robolectric.res.DrawableResourceLoader; |
| import org.robolectric.res.FsFile; |
| import org.robolectric.res.ResName; |
| import org.robolectric.res.ResType; |
| import org.robolectric.res.ResourceIndex; |
| import org.robolectric.res.ResourceLoader; |
| import org.robolectric.res.builder.ResourceLoaderProvider; |
| import org.robolectric.res.Style; |
| import org.robolectric.res.StyleData; |
| import org.robolectric.res.TypedResource; |
| import org.robolectric.res.builder.ResourceParser; |
| import org.robolectric.util.Strings; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.robolectric.annotation.Resetter; |
| |
| import static org.robolectric.Shadows.shadowOf; |
| import static org.robolectric.shadows.ShadowApplication.getInstance; |
| |
| /** |
| * Shadow for {@link android.content.res.AssetManager}. |
| */ |
| @Implements(AssetManager.class) |
| public final class ShadowAssetManager { |
| public static final int STYLE_NUM_ENTRIES = 6; |
| public static final int STYLE_TYPE = 0; |
| public static final int STYLE_DATA = 1; |
| public static final int STYLE_ASSET_COOKIE = 2; |
| public static final int STYLE_RESOURCE_ID = 3; |
| public static final int STYLE_CHANGING_CONFIGURATIONS = 4; |
| public static final int STYLE_DENSITY = 5; |
| |
| private Map<$ptrClassBoxed, Resources.Theme> themesById = new LinkedHashMap<>(); |
| private Map<$ptrClassBoxed, List<OverlayedStyle>> appliedStyles = new HashMap<>(); |
| private int nextInternalThemeId = 1000; |
| private ResourceLoader resourceLoader; |
| |
| @RealObject |
| AssetManager realObject; |
| |
| public ResourceLoader getResourceLoader() { |
| return resourceLoader; |
| } |
| |
| public void __constructor__() { |
| resourceLoader = RuntimeEnvironment.getAppResourceLoader(); |
| } |
| |
| public void __constructor__(boolean isSystem) { |
| resourceLoader = isSystem ? RuntimeEnvironment.getSystemResourceLoader() : RuntimeEnvironment.getAppResourceLoader(); |
| } |
| |
| @HiddenApi @Implementation |
| public CharSequence getResourceText(int ident) { |
| TypedResource value = getAndResolve(ident, RuntimeEnvironment.getQualifiers(), 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 |
| 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) { |
| ResourceIndex resourceIndex = resourceLoader.getResourceIndex(); |
| ResName resName = ResName.qualifyResName(name, defPackage, defType); |
| Integer resourceId = resourceIndex.getResourceId(resName); |
| if (resourceId == null) return 0; |
| return resourceId; |
| } |
| |
| @HiddenApi @Implementation |
| public boolean getResourceValue(int ident, int density, TypedValue outValue, boolean resolveRefs) { |
| TypedResource value = getAndResolve(ident, RuntimeEnvironment.getQualifiers(), resolveRefs); |
| if (value == null) return false; |
| |
| getConverter(value).fillTypedValue(value.getData(), outValue); |
| return true; |
| } |
| |
| private Converter getConverter(TypedResource value) { |
| return Converter.getConverter(value.getResType()); |
| } |
| |
| @HiddenApi @Implementation |
| public CharSequence[] getResourceTextArray(int resId) { |
| TypedResource value = getAndResolve(resId, RuntimeEnvironment.getQualifiers(), true); |
| if (value == null) return null; |
| TypedResource[] items = getConverter(value).getItems(value); |
| CharSequence[] charSequences = new CharSequence[items.length]; |
| for (int i = 0; i < items.length; i++) { |
| TypedResource typedResource = resolve(items[i], RuntimeEnvironment.getQualifiers(), resId); |
| charSequences[i] = getConverter(typedResource).asCharSequence(typedResource); |
| } |
| return charSequences; |
| } |
| |
| @HiddenApi @Implementation |
| public boolean getThemeValue($ptrClass theme, int ident, TypedValue outValue, boolean resolveRefs) { |
| ResourceIndex resourceIndex = resourceLoader.getResourceIndex(); |
| ResName resName = resourceIndex.getResName(ident); |
| Resources.Theme theTheme = getThemeByInternalId(theme); |
| // Load the style for the theme we represent. E.g. "@style/Theme.Robolectric" |
| int styleResourceId = shadowOf(theTheme).getStyleResourceId(); |
| ResName themeStyleName = resourceIndex.getResName(styleResourceId); |
| if (themeStyleName == null) return false; // is this right? |
| |
| Style themeStyle = resolveStyle(null, themeStyleName); |
| |
| //// Load the theme attribute for the default style attributes. E.g., attr/buttonStyle |
| //ResName defStyleName = resourceLoader.getResourceIndex().getResName(ident); |
| // |
| //// Load the style for the default style attribute. E.g. "@style/Widget.Robolectric.Button"; |
| //String defStyleNameValue = themeStyle.getAttrValue(defStyleName); |
| //ResName defStyleResName = new ResName(defStyleName.packageName, "style", defStyleName.name); |
| //Style style = resolveStyle(resourceLoader, defStyleResName); |
| if (themeStyle != null) { |
| List<OverlayedStyle> overlayThemeStyles = getOverlayThemeStyles(styleResourceId); |
| Attribute attrValue = getOverlayedThemeValue(resName, themeStyle, overlayThemeStyles); |
| while(resolveRefs && attrValue != null && attrValue.isStyleReference()) { |
| ResName attrResName = new ResName(attrValue.contextPackageName, "attr", attrValue.value.substring(1)); |
| attrValue = getOverlayedThemeValue(attrResName, themeStyle, overlayThemeStyles); |
| } |
| if (attrValue != null) { |
| Converter.convertAndFill(attrValue, outValue, resourceLoader, RuntimeEnvironment.getQualifiers(), resolveRefs); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @HiddenApi @Implementation |
| public void ensureStringBlocks() { |
| } |
| |
| @Implementation |
| public final InputStream open(String fileName) throws IOException { |
| return ShadowApplication.getInstance().getAppManifest().getAssetsDirectory().join(fileName).getInputStream(); |
| } |
| |
| @Implementation |
| public final InputStream open(String fileName, int accessMode) throws IOException { |
| return ShadowApplication.getInstance().getAppManifest().getAssetsDirectory().join(fileName).getInputStream(); |
| } |
| |
| @Implementation |
| public final AssetFileDescriptor openFd(String fileName) throws IOException { |
| File file = new File(ShadowApplication.getInstance().getAppManifest().getAssetsDirectory().join(fileName).getPath()); |
| ParcelFileDescriptor parcelFileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); |
| return new AssetFileDescriptor(parcelFileDescriptor, 0, file.length()); |
| } |
| |
| @Implementation |
| public final String[] list(String path) throws IOException { |
| FsFile file = ShadowApplication.getInstance().getAppManifest().getAssetsDirectory().join(path); |
| if (file.isDirectory()) { |
| return file.listFileNames(); |
| } |
| return new String[0]; |
| } |
| |
| @HiddenApi @Implementation |
| public final InputStream openNonAsset(int cookie, String fileName, int accessMode) throws IOException { |
| final ResName resName = qualifyFromNonAssetFileName(fileName); |
| final DrawableNode drawableNode = resourceLoader.getDrawableNode(resName, RuntimeEnvironment.getQualifiers()); |
| |
| if (drawableNode == null) { |
| throw new IOException("Unable to find resource for " + fileName); |
| } |
| |
| if (accessMode == AssetManager.ACCESS_STREAMING) { |
| return drawableNode.getFsFile().getInputStream(); |
| } else { |
| return new ByteArrayInputStream(drawableNode.getFsFile().getBytes()); |
| } |
| } |
| |
| private ResName qualifyFromNonAssetFileName(String fileName) { |
| if (fileName.startsWith("jar:")) { |
| // Must remove "jar:" prefix, or else qualifyFromFilePath fails on Windows |
| return ResName.qualifyFromFilePath("android", fileName.replaceFirst("jar:", "")); |
| } else { |
| return ResName.qualifyFromFilePath(ShadowApplication.getInstance().getAppManifest().getPackageName(), fileName); |
| } |
| } |
| |
| @HiddenApi @Implementation |
| public final AssetFileDescriptor openNonAssetFd(int cookie, String fileName) throws IOException { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Implementation |
| public final XmlResourceParser openXmlResourceParser(int cookie, String fileName) throws IOException { |
| return ResourceParser.create(fileName, "fixme", "fixme", null); |
| } |
| |
| @HiddenApi @Implementation |
| public int addAssetPath(String path) { |
| return 1; |
| } |
| |
| @HiddenApi @Implementation |
| public boolean isUpToDate() { |
| return true; |
| } |
| |
| @HiddenApi @Implementation |
| public void setLocale(String locale) { |
| } |
| |
| @Implementation |
| public String[] getLocales() { |
| return new String[0]; // todo |
| } |
| |
| @HiddenApi @Implementation |
| 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 majorVersion) { |
| } |
| |
| @HiddenApi @Implementation |
| public int[] getArrayIntResource(int resId) { |
| TypedResource value = getAndResolve(resId, RuntimeEnvironment.getQualifiers(), true); |
| if (value == null) return null; |
| TypedResource[] items = getConverter(value).getItems(value); |
| int[] ints = new int[items.length]; |
| for (int i = 0; i < items.length; i++) { |
| TypedResource typedResource = resolve(items[i], RuntimeEnvironment.getQualifiers(), resId); |
| ints[i] = getConverter(typedResource).asInt(typedResource); |
| } |
| return ints; |
| } |
| |
| @HiddenApi @Implementation |
| synchronized public int createTheme() { |
| return nextInternalThemeId++; |
| } |
| |
| @HiddenApi @Implementation |
| synchronized public void releaseTheme($ptrClass theme) { |
| themesById.remove(theme); |
| } |
| |
| @HiddenApi @Implementation |
| public static void applyThemeStyle($ptrClass theme, int styleRes, boolean force) { |
| ShadowAssetManager assetManager = shadowOf(RuntimeEnvironment.application.getAssets()); |
| |
| final Map<$ptrClassBoxed, List<OverlayedStyle>> appliedStyles = assetManager.appliedStyles; |
| |
| if (!appliedStyles.containsKey(theme)) { |
| appliedStyles.put(theme, new LinkedList<OverlayedStyle>()); |
| } |
| |
| ResourceIndex resourceIndex = assetManager.resourceLoader.getResourceIndex(); |
| ResName resName = resourceIndex.getResName(styleRes); |
| |
| Style style = assetManager.resolveStyle(null, resName); |
| |
| List<OverlayedStyle> overlayedStyleList = appliedStyles.get(theme); |
| OverlayedStyle styleToAdd = new OverlayedStyle(style, force); |
| for (int i = 0; i < overlayedStyleList.size(); ++i) { |
| if (styleToAdd.equals(overlayedStyleList.get(i))) { |
| overlayedStyleList.remove(i); |
| break; |
| } |
| } |
| overlayedStyleList.add(styleToAdd); |
| } |
| |
| List<OverlayedStyle> getOverlayThemeStyles($ptrClass themeResourceId) { |
| return appliedStyles.get(themeResourceId); |
| } |
| |
| static class OverlayedStyle { |
| Style style; |
| boolean force; |
| |
| public OverlayedStyle(Style style, boolean force) { |
| this.style = style; |
| this.force = force; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (!(obj instanceof OverlayedStyle)) { |
| return false; |
| } |
| OverlayedStyle overlayedStyle = (OverlayedStyle) obj; |
| return style.equals(overlayedStyle.style); |
| } |
| |
| @Override |
| public int hashCode() { |
| return style.hashCode(); |
| } |
| } |
| |
| @HiddenApi @Implementation |
| public static void copyTheme($ptrClass dest, long source) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| ///////////////////////// |
| |
| synchronized public void setTheme($ptrClass internalThemeId, Resources.Theme theme) { |
| themesById.put(internalThemeId, theme); |
| } |
| |
| synchronized private Resources.Theme getThemeByInternalId($ptrClass internalThemeId) { |
| return themesById.get(internalThemeId); |
| } |
| |
| Style resolveStyle(Style appTheme, @NotNull ResName themeStyleName) { |
| TypedResource themeStyleResource = resourceLoader.getValue(themeStyleName, RuntimeEnvironment.getQualifiers()); |
| if (themeStyleResource == null) return null; |
| StyleData themeStyleData = (StyleData) themeStyleResource.getData(); |
| return new StyleResolver(resourceLoader, themeStyleData, appTheme, themeStyleName, RuntimeEnvironment.getQualifiers()); |
| } |
| |
| private TypedResource getAndResolve(int resId, String qualifiers, boolean resolveRefs) { |
| TypedResource value = resourceLoader.getValue(resId, qualifiers); |
| if (resolveRefs) { |
| value = resolve(value, qualifiers, resId); |
| } |
| |
| // todo: make the drawable loader put stuff into the normal spot... |
| String resourceTypeName = getResourceTypeName(resId); |
| if (value == null && DrawableResourceLoader.isStillHandledHere(resourceTypeName)) { |
| DrawableNode drawableNode = resourceLoader.getDrawableNode(resId, qualifiers); |
| return new TypedResource<>(drawableNode.getFsFile(), ResType.FILE); |
| } |
| |
| // todo: gross. this is so resources.getString(R.layout.foo) works for ABS. |
| if (value == null && "layout".equals(resourceTypeName)) { |
| throw new UnsupportedOperationException("ugh, this doesn't work still?"); |
| } |
| |
| return value; |
| } |
| |
| TypedResource resolve(TypedResource value, String qualifiers, int resId) { |
| return resourceLoader.resolveResourceValue(value, qualifiers, resId); |
| } |
| |
| static class StyleResolver implements Style { |
| private final ResourceLoader resourceLoader; |
| private final List<StyleData> styles = new ArrayList<>(); |
| private final Style theme; |
| private final ResName myResName; |
| private final String qualifiers; |
| |
| public StyleResolver(ResourceLoader resourceLoader, StyleData styleData, |
| Style theme, ResName myResName, String qualifiers) { |
| this.resourceLoader = resourceLoader; |
| this.theme = theme; |
| this.myResName = myResName; |
| this.qualifiers = qualifiers; |
| styles.add(styleData); |
| } |
| |
| @Override public Attribute getAttrValue(ResName resName) { |
| for (StyleData style : styles) { |
| Attribute value = style.getAttrValue(resName); |
| if (value != null) return value; |
| } |
| int initialSize = styles.size(); |
| while (hasParent(styles.get(styles.size() - 1))) { |
| StyleData parent = getParent(styles.get(styles.size() - 1)); |
| if (parent != null) { |
| styles.add(parent); |
| } else { |
| break; |
| } |
| } |
| for (int i = initialSize; i < styles.size(); i++) { |
| StyleData style = styles.get(i); |
| Attribute value = style.getAttrValue(resName); |
| if (value != null) return value; |
| } |
| if (theme != null) { |
| Attribute value = theme.getAttrValue(resName); |
| if (value != null) return value; |
| } |
| |
| return null; |
| } |
| |
| private static String getParentStyleName(StyleData style) { |
| if (style == null) { |
| return null; |
| } |
| String parent = style.getParent(); |
| if (parent == null || parent.isEmpty()) { |
| parent = null; |
| String name = style.getName(); |
| if (name.contains(".")) { |
| parent = name.substring(0, name.lastIndexOf('.')); |
| if (parent.isEmpty()) { |
| return null; |
| } |
| } |
| } |
| return parent; |
| } |
| |
| private static boolean hasParent(StyleData style) { |
| if (style == null) return false; |
| String parent = style.getParent(); |
| return parent != null && !parent.isEmpty(); |
| } |
| |
| private StyleData getParent(StyleData style) { |
| String parent = getParentStyleName(style); |
| |
| if (parent == null) return null; |
| |
| if (parent.startsWith("@")) parent = parent.substring(1); |
| |
| ResName styleRef = ResName.qualifyResName(parent, style.getPackageName(), "style"); |
| |
| styleRef = dereferenceResName(styleRef); |
| |
| TypedResource typedResource = resourceLoader.getValue(styleRef, qualifiers); |
| |
| if (typedResource == null) { |
| StringBuilder builder = new StringBuilder("Could not find any resource ") |
| .append(" from reference ").append(styleRef) |
| .append(" from style ").append(style) |
| .append(" with theme ").append(theme); |
| throw new RuntimeException(builder.toString()); |
| } |
| |
| Object data = typedResource.getData(); |
| if (data instanceof StyleData) { |
| return (StyleData) data; |
| } else { |
| StringBuilder builder = new StringBuilder(styleRef.toString()) |
| .append(" does not resolve to a Style.") |
| .append(" got ").append(data).append(" instead. ") |
| .append(" from style ").append(style) |
| .append(" with theme ").append(theme); |
| throw new RuntimeException(builder.toString()); |
| } |
| } |
| |
| private ResName dereferenceResName(ResName res) { |
| ResName styleRef = res; |
| boolean dereferencing = true; |
| while ("attr".equals(styleRef.type) && dereferencing) { |
| dereferencing = false; |
| for (StyleData parentStyle : styles) { |
| Attribute value = parentStyle.getAttrValue(styleRef); |
| if (value != null) { |
| styleRef = dereferenceAttr(value); |
| dereferencing = true; |
| break; |
| } |
| } |
| if (!dereferencing && theme != null) { |
| Attribute value = theme.getAttrValue(styleRef); |
| if (value != null) { |
| styleRef = dereferenceAttr(value); |
| dereferencing = true; |
| } |
| } |
| } |
| |
| return styleRef; |
| } |
| |
| private ResName dereferenceAttr(Attribute attr) { |
| if (attr.isResourceReference()) { |
| return attr.getResourceReference(); |
| } else if (attr.isStyleReference()) { |
| return attr.getStyleReference(); |
| } |
| throw new RuntimeException("Found a " + attr + " but can't cast it :("); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (!(obj instanceof StyleResolver)) { |
| return false; |
| } |
| StyleResolver other = (StyleResolver) obj; |
| |
| return ((theme == null && other.theme == null) || (theme != null && theme.equals(other.theme))) |
| && ((myResName == null && other.myResName == null) |
| || (myResName != null && myResName.equals(other.myResName))) |
| && Strings.equals(qualifiers, other.qualifiers); |
| } |
| |
| @Override |
| public int hashCode() { |
| int hashCode = 0; |
| hashCode = 31 * hashCode + (theme != null ? theme.hashCode() : 0); |
| hashCode = 31 * hashCode + (myResName != null ? myResName.hashCode() : 0); |
| hashCode = 31 * hashCode + Strings.nullToEmpty(qualifiers).hashCode(); |
| return hashCode; |
| } |
| |
| @Override |
| public String toString() { |
| return "StyleResolver{" |
| + "name=" + myResName |
| + ", of=" + styles.get(0) |
| + "}"; |
| } |
| |
| } |
| |
| private Attribute buildAttribute(AttributeSet set, int resId, int defStyleAttr, int themeResourceId, 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; |
| Style theme = null; |
| |
| List<ShadowAssetManager.OverlayedStyle> overlayedStyles = getOverlayThemeStyles(themeResourceId); |
| if (themeResourceId != 0) { |
| // Load the style for the theme we represent. E.g. "@style/Theme.Robolectric" |
| ResName themeStyleName = getResName(themeResourceId); |
| theme = resolveStyle(null, themeStyleName); |
| |
| 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"; |
| Attribute defStyleAttribute = getOverlayedThemeValue(defStyleName, theme, overlayedStyles); |
| if (defStyleAttribute != null) { |
| while (defStyleAttribute.isStyleReference()) { |
| Attribute other = getOverlayedThemeValue(defStyleAttribute.getStyleReference(), theme, overlayedStyles); |
| if (other == null) { |
| throw new RuntimeException("couldn't dereference " + defStyleAttribute); |
| } |
| defStyleAttribute = other; |
| } |
| |
| if (defStyleAttribute.isResourceReference()) { |
| ResName defStyleResName = defStyleAttribute.getResourceReference(); |
| defStyleFromAttr = resolveStyle(theme, defStyleResName); |
| } |
| } |
| } |
| } |
| |
| if (set != null && set.getStyleAttribute() != 0) { |
| ResName styleAttributeResName = getResName(set.getStyleAttribute()); |
| while (styleAttributeResName.type.equals("attr")) { |
| Attribute attrValue = getOverlayedThemeValue(styleAttributeResName, theme, overlayedStyles); |
| if (attrValue.isResourceReference()) { |
| styleAttributeResName = attrValue.getResourceReference(); |
| } else if (attrValue.isStyleReference()) { |
| styleAttributeResName = attrValue.getStyleReference(); |
| } |
| } |
| styleAttrStyle = resolveStyle(theme, styleAttributeResName); |
| } |
| |
| if (defStyleRes != 0) { |
| ResName resName = getResName(defStyleRes); |
| if (resName.type.equals("attr")) { |
| Attribute attributeValue = findAttributeValue(defStyleRes, set, styleAttrStyle, defStyleFromAttr, defStyleFromAttr, theme, overlayedStyles); |
| if (attributeValue != null) { |
| if (attributeValue.isStyleReference()) { |
| resName = getOverlayedThemeValue(attributeValue.getStyleReference(), theme, overlayedStyles).getResourceReference(); |
| } else if (attributeValue.isResourceReference()) { |
| resName = attributeValue.getResourceReference(); |
| } |
| } |
| } |
| defStyleFromRes = resolveStyle(theme, resName); |
| } |
| |
| Attribute attribute = findAttributeValue(resId, set, styleAttrStyle, defStyleFromAttr, defStyleFromRes, theme, overlayedStyles); |
| while (attribute != null && attribute.isStyleReference()) { |
| ResName otherAttrName = attribute.getStyleReference(); |
| |
| // TODO: this is just a debugging hack to avoid the problem of Resources.loadDrawableForCookie not working. |
| // TODO: We need to address the real problem instead, but are putting it off for a day or two -AV, ED 2014-12-03 |
| if (theme == null) break; |
| |
| attribute = getOverlayedThemeValue(otherAttrName, theme, overlayedStyles); |
| if (attribute != null) { |
| attribute = new Attribute(resourceLoader.getResourceIndex().getResName(resId), attribute.value, attribute.contextPackageName); |
| } |
| } |
| |
| return attribute; |
| } |
| |
| TypedArray attrsToTypedArray(Resources resources, AttributeSet set, int[] attrs, int defStyleAttr, int themeResourceId, int defStyleRes) { |
| CharSequence[] stringData = new CharSequence[attrs.length]; |
| int[] data = new int[attrs.length * ShadowAssetManager.STYLE_NUM_ENTRIES]; |
| int[] indices = new int[attrs.length + 1]; |
| int nextIndex = 0; |
| |
| for (int i = 0; i < attrs.length; i++) { |
| int offset = i * ShadowAssetManager.STYLE_NUM_ENTRIES; |
| |
| Attribute attribute = buildAttribute(set, attrs[i], defStyleAttr, themeResourceId, defStyleRes); |
| if (attribute != null && !attribute.isNull()) { |
| TypedValue typedValue = new TypedValue(); |
| // If there is an AttributeSet then use the resource loader that the attribute set was created with. |
| ResourceLoader resourceLoader = set != null ? ((ResourceLoaderProvider) set).getResourceLoader() : this.resourceLoader; |
| Converter.convertAndFill(attribute, typedValue, resourceLoader, RuntimeEnvironment.getQualifiers(), true); |
| //noinspection PointlessArithmeticExpression |
| data[offset + ShadowAssetManager.STYLE_TYPE] = typedValue.type; |
| data[offset + ShadowAssetManager.STYLE_DATA] = typedValue.type == TypedValue.TYPE_STRING ? i : typedValue.data; |
| data[offset + ShadowAssetManager.STYLE_ASSET_COOKIE] = typedValue.assetCookie; |
| data[offset + ShadowAssetManager.STYLE_RESOURCE_ID] = typedValue.resourceId; |
| data[offset + ShadowAssetManager.STYLE_CHANGING_CONFIGURATIONS] = typedValue.changingConfigurations; |
| data[offset + ShadowAssetManager.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) { |
| shadowOf(typedArray).positionDescription = set.getPositionDescription(); |
| } |
| return typedArray; |
| } |
| |
| private Attribute findAttributeValue(int resId, AttributeSet attributeSet, Style styleAttrStyle, Style defStyleFromAttr, Style defStyleFromRes, Style theme, List<ShadowAssetManager.OverlayedStyle> overlayedStyles) { |
| if (attributeSet != null) { |
| for (int i = 0; i < attributeSet.getAttributeCount(); i++) { |
| if (attributeSet.getAttributeNameResource(i) == resId && attributeSet.getAttributeValue(i) != null) { |
| return new Attribute(ResName.qualifyResName(attributeSet.getAttributeName(i), "android", "attr"), attributeSet.getAttributeValue(i), "fixme!!!"); |
| } |
| } |
| } |
| |
| ResName attrName = resourceLoader.getResourceIndex().getResName(resId); |
| if (attrName == null) return null; |
| |
| if (styleAttrStyle != null) { |
| Attribute attribute = styleAttrStyle.getAttrValue(attrName); |
| if (attribute != null) { |
| return attribute; |
| } |
| } |
| |
| // else if attr in defStyleFromAttr, use its value |
| if (defStyleFromAttr != null) { |
| Attribute attribute = defStyleFromAttr.getAttrValue(attrName); |
| if (attribute != null) { |
| return attribute; |
| } |
| } |
| |
| if (defStyleFromRes != null) { |
| Attribute attribute = defStyleFromRes.getAttrValue(attrName); |
| if (attribute != null) { |
| return attribute; |
| } |
| } |
| |
| // else if attr in theme, use its value |
| if (theme != null) { |
| return getOverlayedThemeValue(attrName, theme, overlayedStyles); |
| } |
| |
| return null; |
| } |
| |
| private static Attribute getOverlayedThemeValue(ResName attrName, Style theme, List<ShadowAssetManager.OverlayedStyle> overlayedStyles) { |
| Attribute attribute = theme.getAttrValue(attrName); |
| |
| if (overlayedStyles != null) { |
| for (ShadowAssetManager.OverlayedStyle overlayedStyle : overlayedStyles) { |
| Attribute overlayedAttribute = overlayedStyle.style.getAttrValue(attrName); |
| if (overlayedAttribute != null && (attribute == null || overlayedStyle.force)) { |
| attribute = overlayedAttribute; |
| } |
| } |
| } |
| |
| return attribute; |
| } |
| |
| @NotNull private ResName getResName(int id) { |
| ResName resName = resourceLoader.getResourceIndex().getResName(id); |
| return checkResName(id, resName); |
| } |
| |
| private ResName checkResName(int id, ResName resName) { |
| if (resName == null) { |
| throw new Resources.NotFoundException("Unable to find resource ID #0x" + Integer.toHexString(id)); |
| } |
| return resName; |
| } |
| |
| @Implementation |
| public String getResourceName(int resid) { |
| return getResName(resid).getFullyQualifiedName(); |
| } |
| |
| @Implementation |
| public String getResourcePackageName(int resid) { |
| return getResName(resid).packageName; |
| } |
| |
| @Implementation |
| public String getResourceTypeName(int resid) { |
| return getResName(resid).type; |
| } |
| |
| @Implementation |
| public String getResourceEntryName(int resid) { |
| return getResName(resid).name; |
| } |
| } |