package org.itsnat.droid.impl.xmlinflater;
import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.text.Html;
import android.text.Spanned;
import android.text.SpannedString;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.view.animation.LayoutAnimationController;
import org.itsnat.droid.AttrResourceInflaterListener;
import org.itsnat.droid.ItsNatDroidException;
import org.itsnat.droid.impl.ItsNatDroidImpl;
import org.itsnat.droid.impl.browser.PageImpl;
import org.itsnat.droid.impl.dom.DOMAttr;
import org.itsnat.droid.impl.dom.ParsedResourceImage;
import org.itsnat.droid.impl.dom.ParsedResourceXMLDOM;
import org.itsnat.droid.impl.dom.ResourceDesc;
import org.itsnat.droid.impl.dom.ResourceDescCompiled;
import org.itsnat.droid.impl.dom.ResourceDescDynamic;
import org.itsnat.droid.impl.dom.ResourceDescRemote;
import org.itsnat.droid.impl.dom.anim.XMLDOMAnimation;
import org.itsnat.droid.impl.dom.animator.XMLDOMAnimator;
import org.itsnat.droid.impl.dom.animinterp.XMLDOMInterpolator;
import org.itsnat.droid.impl.dom.animlayout.XMLDOMLayoutAnimation;
import org.itsnat.droid.impl.dom.drawable.XMLDOMDrawable;
import org.itsnat.droid.impl.dom.layout.XMLDOMLayout;
import org.itsnat.droid.impl.dom.values.XMLDOMValues;
import org.itsnat.droid.impl.util.MimeUtil;
import org.itsnat.droid.impl.util.MiscUtil;
import org.itsnat.droid.impl.util.NamespaceUtil;
import org.itsnat.droid.impl.util.StringUtil;
import org.itsnat.droid.impl.util.WeakMapWithValue;
import org.itsnat.droid.impl.xmlinflated.anim.InflatedXMLAnimation;
import org.itsnat.droid.impl.xmlinflated.animator.InflatedXMLAnimator;
import org.itsnat.droid.impl.xmlinflated.animinterp.InflatedXMLInterpolator;
import org.itsnat.droid.impl.xmlinflated.animlayout.InflatedXMLLayoutAnimation;
import org.itsnat.droid.impl.xmlinflated.drawable.InflatedXMLDrawable;
import org.itsnat.droid.impl.xmlinflated.layout.InflatedXMLLayoutImpl;
import org.itsnat.droid.impl.xmlinflated.layout.InflatedXMLLayoutPageImpl;
import org.itsnat.droid.impl.xmlinflated.layout.InflatedXMLLayoutPageItsNatImpl;
import org.itsnat.droid.impl.xmlinflated.values.ElementValuesResources;
import org.itsnat.droid.impl.xmlinflated.values.ElementValuesStyle;
import org.itsnat.droid.impl.xmlinflated.values.InflatedXMLValues;
import org.itsnat.droid.impl.xmlinflater.anim.ClassDescAnimationMgr;
import org.itsnat.droid.impl.xmlinflater.anim.XMLInflaterAnimation;
import org.itsnat.droid.impl.xmlinflater.animator.ClassDescAnimatorMgr;
import org.itsnat.droid.impl.xmlinflater.animator.XMLInflaterAnimator;
import org.itsnat.droid.impl.xmlinflater.animinterp.ClassDescInterpolatorMgr;
import org.itsnat.droid.impl.xmlinflater.animinterp.XMLInflaterInterpolator;
import org.itsnat.droid.impl.xmlinflater.animlayout.ClassDescLayoutAnimationMgr;
import org.itsnat.droid.impl.xmlinflater.animlayout.XMLInflaterLayoutAnimation;
import org.itsnat.droid.impl.xmlinflater.drawable.BitmapDrawableUtil;
import org.itsnat.droid.impl.xmlinflater.drawable.ClassDescDrawableMgr;
import org.itsnat.droid.impl.xmlinflater.drawable.XMLInflaterDrawable;
import org.itsnat.droid.impl.xmlinflater.layout.ClassDescViewMgr;
import org.itsnat.droid.impl.xmlinflater.layout.LayoutValue;
import org.itsnat.droid.impl.xmlinflater.layout.LayoutValueCompiled;
import org.itsnat.droid.impl.xmlinflater.layout.LayoutValueDynamic;
import org.itsnat.droid.impl.xmlinflater.layout.ViewMapByXMLId;
import org.itsnat.droid.impl.xmlinflater.layout.ViewStyleAttribs;
import org.itsnat.droid.impl.xmlinflater.layout.ViewStyleAttribsCompiled;
import org.itsnat.droid.impl.xmlinflater.layout.ViewStyleAttribsDynamic;
import org.itsnat.droid.impl.xmlinflater.layout.XMLInflaterLayout;
import org.itsnat.droid.impl.xmlinflater.layout.page.XMLInflaterLayoutPage;
import org.itsnat.droid.impl.xmlinflater.values.ClassDescValuesMgr;
import org.itsnat.droid.impl.xmlinflater.values.XMLInflaterValues;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Created by jmarranz on 25/06/14.
*/
public class XMLInflaterRegistry
{
private ItsNatDroidImpl itsNatDroid;
private int sNextGeneratedId = 1; // No usamos AtomicInteger porque no lo usaremos en multihilo
private Map<String, Integer> newViewIdMap = new HashMap<String, Integer>();
private ClassDescViewMgr classDescViewMgr = new ClassDescViewMgr(this);
private ClassDescDrawableMgr classDescDrawableMgr = new ClassDescDrawableMgr(this);
private ClassDescValuesMgr classDescValuesMgr = new ClassDescValuesMgr(this);
private ClassDescAnimationMgr classDescAnimationMgr = new ClassDescAnimationMgr(this);
private ClassDescAnimatorMgr classDescAnimatorMgr = new ClassDescAnimatorMgr(this);
private ClassDescInterpolatorMgr classDescInterpolatorMgr = new ClassDescInterpolatorMgr(this);
private ClassDescLayoutAnimationMgr classDescLayoutAnimationMgr = new ClassDescLayoutAnimationMgr(this);
private Map<XMLDOMValues,ElementValuesResources> cacheXMLDOMValuesXMLInflaterValuesMap = new HashMap<XMLDOMValues, ElementValuesResources>();
public XMLInflaterRegistry(ItsNatDroidImpl itsNatDroid)
{
this.itsNatDroid = itsNatDroid;
}
public ItsNatDroidImpl getItsNatDroidImpl()
{
return itsNatDroid;
}
public ClassDescViewMgr getClassDescViewMgr()
{
return classDescViewMgr;
}
public ClassDescDrawableMgr getClassDescDrawableMgr()
{
return classDescDrawableMgr;
}
public ClassDescValuesMgr getClassDescValuesMgr()
{
return classDescValuesMgr;
}
public ClassDescAnimationMgr getClassDescAnimationMgr()
{
return classDescAnimationMgr;
}
public ClassDescAnimatorMgr getClassDescAnimatorMgr()
{
return classDescAnimatorMgr;
}
public ClassDescInterpolatorMgr getClassDescInterpolatorMgr()
{
return classDescInterpolatorMgr;
}
public ClassDescLayoutAnimationMgr getClassDescLayoutAnimationMgr()
{
return classDescLayoutAnimationMgr;
}
public int generateViewId()
{
// Inspirado en el código fuente de Android View.generateViewId()
final int result = sNextGeneratedId;
// aapt-generated IDs have the high byte nonzero; clamp to the range under that.
int newValue = result + 1;
if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
// No usamos compareAndSet porque no se debe usar en multihilo
this.sNextGeneratedId = newValue;
return result;
}
private int findViewIdDynamicallyAddedAddIfNecessary(String name)
{
int id = findViewIdDynamicallyAdded(name);
if (id == 0)
id = addNewViewId(name);
return id;
}
public int findViewIdDynamicallyAdded(String name)
{
Integer res = newViewIdMap.get(name);
if (res == null)
return 0; // No existe
return res;
}
private int addNewViewId(String name)
{
int newId = generateViewId();
newViewIdMap.put(name, newId);
return newId;
}
public int getIdentifier(ResourceDesc resourceDesc, XMLInflaterContext xmlInflaterContext)
{
if (resourceDesc instanceof ResourceDescDynamic)
{
ResourceDescDynamic resourceDescDyn = (ResourceDescDynamic)resourceDesc;
String type = resourceDescDyn.getResourceType();
ElementValuesResources elementResources = getElementValuesResources(resourceDescDyn, xmlInflaterContext);
return elementResources.getIdentifier(resourceDescDyn.getValuesResourceName(), type, xmlInflaterContext);
}
else if (resourceDesc instanceof ResourceDescCompiled)
{
Context ctx = xmlInflaterContext.getContext();
String resourceDescValue = resourceDesc.getResourceDescValue();
return getIdentifierAddIfNecessaryCompiled(resourceDescValue,null, ctx);
}
else throw MiscUtil.internalError();
}
public int getIdentifier(ResourceDesc resourceDesc, String type, XMLInflaterContext xmlInflaterContext)
{
if (resourceDesc instanceof ResourceDescDynamic)
{
// Es raro que pase por aquí, sería cuando un <item type="id"> en un XML values tiene como valor un path remoto/dinámico
ResourceDescDynamic resourceDescDyn = (ResourceDescDynamic)resourceDesc;
type = resourceDescDyn.getResourceType();
ElementValuesResources elementResources = getElementValuesResources(resourceDescDyn,xmlInflaterContext);
return elementResources.getIdentifier(resourceDescDyn.getValuesResourceName(), type, xmlInflaterContext);
}
else if (resourceDesc instanceof ResourceDescCompiled)
{
Context ctx = xmlInflaterContext.getContext();
String resourceDescValue = resourceDesc.getResourceDescValue();
return getIdentifierAddIfNecessaryCompiled(resourceDescValue,type, ctx);
}
else throw MiscUtil.internalError();
}
private int getIdentifierAddIfNecessaryCompiled(String resourceDescValue,String type, Context ctx)
{
// type puede ser null
// Procesamos aquí los casos de "@+id/...", la razón es que cualquier atributo que referencie un id (más allá
// de android:id) puede registrar un nuevo atributo lo cual es útil si el android:id como tal está después,
// después en android:id ya no hace falta que sea "@+id/...".
// http://stackoverflow.com/questions/11029635/android-radiogroup-checkedbutton-property
if (("+id".equals(type) || "id".equals(type)) && (!resourceDescValue.startsWith("@+id/") && !resourceDescValue.startsWith("@id/")))
{
if ("+id".equals(type)) resourceDescValue = "@+id/" + resourceDescValue;
else resourceDescValue = "@id/" + resourceDescValue;
}
int id;
if (resourceDescValue.startsWith("@+id/") || resourceDescValue.startsWith("@id/")) // Si fuera el caso de "@+mypackage:id/name" ese caso no lo soportamos, no lo he visto nunca aunque en teoría está sintácticamente permitido
{
id = getIdentifierCompiled(resourceDescValue, ctx, false); // Tiene prioridad el recurso de Android, pues para qué generar un id nuevo si ya existe o bien ya fue registrado dinámicamente
if (id <= 0)
{
int pos = resourceDescValue.indexOf('/');
String idName = resourceDescValue.substring(pos + 1);
if (resourceDescValue.startsWith("@+id/")) id = findViewIdDynamicallyAddedAddIfNecessary(idName);
else id = findViewIdDynamicallyAdded(idName);
if (id <= 0)
throw new ItsNatDroidException("Not found resource with id \"" + resourceDescValue + "\" you could use @+id/ ");
}
}
else id = getIdentifierCompiled(resourceDescValue, ctx);
return id;
}
public int getIdentifierCompiled(String resourceDescValue, Context ctx)
{
return getIdentifierCompiled(resourceDescValue, ctx, true);
}
private boolean isIdentifierCompiledValueNull(String resourceDescValue)
{
return ("0".equals(resourceDescValue) || "-1".equals(resourceDescValue) || "@null".equals(resourceDescValue));
}
private int getIdentifierCompiled(String resourceDescValue, Context ctx, boolean throwErr)
{
if (isIdentifierCompiledValueNull(resourceDescValue)) return 0; // Si throwErr es true, es el único caso en el que se devuelve 0
int id;
char first = resourceDescValue.charAt(0);
if (first == '?')
{
id = getIdentifierCompiledAttrTheme(resourceDescValue, ctx);
if (id > 0)
return id;
}
else if (first == '@')
{
// Tiene prioridad el registro de Android que el de ItsNat en el caso de "@+id", para qué generar un id si ya existe como recurso
id = getIdentifierResource(resourceDescValue, ctx);
if (id > 0)
return id;
if (resourceDescValue.startsWith("@id/") || resourceDescValue.startsWith("@+id/"))
{
// En este caso es posible que se haya registrado dinámicamente el id via "@+id/..."
id = getViewIdDynamicallyAdded(resourceDescValue);
if (id > 0)
return id;
}
}
else if (resourceDescValue.startsWith("android:"))
{
// Es el caso de style parent definido por Android ej: <style name="..." parent="android:Theme.Holo.Light.DarkActionBar"> que es la manera reducida de poner:
// parent="@android:style/Theme.Holo.Light.DarkActionBar" que se procesaría en el caso anterior
// Sinceramente no se como obtenerlo via Resources.getIdentifier, lo que hacemos es convertirlo en el formato parent="@android:style/Theme.Holo.Light.DarkActionBar"
int pos = "android:".length();
resourceDescValue = "@android:style/" + resourceDescValue.substring(pos);
id = getIdentifierResource(resourceDescValue,ctx);
if (id > 0)
return id;
}
else
{
throw new ItsNatDroidException("Bad format in identifier declaration: " + resourceDescValue);
}
if (throwErr && id <= 0)
throw new ItsNatDroidException("Not found resource with id value \"" + resourceDescValue + "\"");
return id;
}
private static int getIdentifierCompiledAttrTheme(String value, Context ctx)
{
// http://stackoverflow.com/questions/12781501/android-setting-linearlayout-background-programmatically
// Ej. android:textAppearance="?android:attr/textAppearanceMedium"
int id = getIdentifierResource(value, ctx);
TypedValue outValue = new TypedValue();
ctx.getTheme().resolveAttribute(id, outValue, true);
return outValue.resourceId;
}
private static int getIdentifierResource(String value, Context ctx)
{
Resources res = ctx.getResources();
char firstChar = value.charAt(0);
if (firstChar == '@' || firstChar == '?')
value = value.substring(1); // Quitamos el @ o ?
if (value.startsWith("+id/"))
value = value.substring(1); // Quitamos el +
String packageName;
if (value.indexOf(':') != -1) // Tiene package el value, ej "android:" delegamos en Resources.getIdentifier() que lo resuelva
{
packageName = null;
}
else
{
packageName = ctx.getPackageName(); // El package es necesario como parámetro sólo cuando no está en la string (recursos compilados)
}
return res.getIdentifier(value, null, packageName);
}
private int getViewIdDynamicallyAdded(String value)
{
if (value.indexOf(':') != -1) // Tiene package, ej "@+android:id/", no se encontrará un id registrado como "@+id/..." y los posibles casos con package NO los hemos contemplado
return 0; // No encontrado
// Fue añadido a través de "@+id/..."
value = value.substring(1); // Quitamos el @
int pos = value.indexOf('/');
String idName = value.substring(pos + 1);
return findViewIdDynamicallyAdded(idName);
}
public ViewStyleAttribs getViewStyle(ResourceDesc resourceDesc,XMLInflaterContext xmlInflaterContext)
{
// Ya no solo es para el atributo "style": if (attr.getNamespaceURI() != null || !"style".equals(attr.getName())) throw MiscUtil.internalError();
if (resourceDesc instanceof ResourceDescDynamic)
{
ResourceDescDynamic resourceDescDyn = (ResourceDescDynamic)resourceDesc;
ElementValuesResources elementResources = getElementValuesResources(resourceDescDyn, xmlInflaterContext);
ElementValuesStyle elemStyle = elementResources.getViewStyle(resourceDescDyn.getValuesResourceName());
DOMAttr domAttrParent = elemStyle.getParentAttr();
List<DOMAttr> domAttrValueList = elemStyle.getChildDOMAttrValueList();
return new ViewStyleAttribsDynamic(domAttrParent,domAttrValueList);
}
else if (resourceDesc instanceof ResourceDescCompiled)
{
Context ctx = xmlInflaterContext.getContext();
String resourceDescValue = resourceDesc.getResourceDescValue();
int styleId = getIdentifierCompiled(resourceDescValue, ctx);
return new ViewStyleAttribsCompiled(styleId);
}
else throw MiscUtil.internalError();
}
public int getViewStyle(ViewStyleAttribs style,List<DOMAttr> styleItemsDynamicAttribs,Context ctx)
{
// El retorno es el id del style compilado si existe o el del parent en caso de <style name="..." parent="...">
if (style == null)
return 0;
if (style instanceof ViewStyleAttribsCompiled)
{
return ((ViewStyleAttribsCompiled)style).getIdentifier();
}
else if (style instanceof ViewStyleAttribsDynamic)
{
ViewStyleAttribsDynamic dynStyle = (ViewStyleAttribsDynamic)style;
List<DOMAttr> styleItemAttrs = dynStyle.getDOMAttrItemList();
if (styleItemAttrs != null) // Si es null es raro, es el caso de <style> vacío
styleItemsDynamicAttribs.addAll(styleItemAttrs);
DOMAttr parentStyleDOMAttr = dynStyle.getDOMAttrParentStyle(); // Puede ser null
if (parentStyleDOMAttr == null)
return 0;
return getIdentifierCompiled(parentStyleDOMAttr.getValue(),ctx);
}
else throw MiscUtil.internalError();
}
public static boolean isResource(String attrValue)
{
// No hace falta hacer un trim, un espacio al ppio invalida el atributo
return attrValue.startsWith("@") || attrValue.startsWith("?");
}
public int getInteger(ResourceDesc resourceDesc,XMLInflaterContext xmlInflaterContext)
{
if (resourceDesc instanceof ResourceDescDynamic)
{
ResourceDescDynamic resourceDescDyn = (ResourceDescDynamic)resourceDesc;
ElementValuesResources elementResources = getElementValuesResources(resourceDescDyn, xmlInflaterContext);
return elementResources.getInteger(resourceDescDyn.getValuesResourceName(), xmlInflaterContext);
}
else if (resourceDesc instanceof ResourceDescCompiled)
{
Context ctx = xmlInflaterContext.getContext();
String resourceDescValue = resourceDesc.getResourceDescValue();
return getIntegerCompiled(resourceDescValue, ctx);
}
else throw MiscUtil.internalError();
}
private int getIntegerCompiled(String resourceDescValue, Context ctx)
{
if (isResource(resourceDescValue))
{
int resId = getIdentifierCompiled(resourceDescValue, ctx);
if (resId == 0) throw new ItsNatDroidException("Resource id value cannot be @null for an integer resource");
return ctx.getResources().getInteger(resId);
}
else
{
if (resourceDescValue.startsWith("0x"))
{
resourceDescValue = resourceDescValue.substring(2);
return Integer.parseInt(resourceDescValue, 16);
}
return Integer.parseInt(resourceDescValue);
}
}
public float getFloat(ResourceDesc resourceDesc,XMLInflaterContext xmlInflaterContext)
{
if (resourceDesc instanceof ResourceDescDynamic)
{
ResourceDescDynamic resourceDescDyn = (ResourceDescDynamic)resourceDesc;
ElementValuesResources elementResources = getElementValuesResources(resourceDescDyn, xmlInflaterContext);
return elementResources.getFloat(resourceDescDyn.getValuesResourceName(), xmlInflaterContext);
}
else if (resourceDesc instanceof ResourceDescCompiled)
{
Context ctx = xmlInflaterContext.getContext();
String resourceDescValue = resourceDesc.getResourceDescValue();
return getFloatCompiled(resourceDescValue, ctx);
}
else throw MiscUtil.internalError();
}
private float getFloatCompiled(String resourceDescValue, Context ctx)
{
// Ojo, para valores sin sufijo de dimensión (por ej layout_weight o alpha)
if (isResource(resourceDescValue))
{
int resId = getIdentifierCompiled(resourceDescValue, ctx);
if (resId == 0) throw new ItsNatDroidException("Resource id value cannot be @null for a dimension resource");
return ctx.getResources().getDimension(resId); // No hay getFloat
}
else return parseFloat(resourceDescValue);
}
public String getString(ResourceDesc resourceDesc,XMLInflaterContext xmlInflaterContext)
{
if (resourceDesc instanceof ResourceDescDynamic)
{
ResourceDescDynamic resourceDescDyn = (ResourceDescDynamic)resourceDesc;
ElementValuesResources elementResources = getElementValuesResources(resourceDescDyn, xmlInflaterContext);
return elementResources.getString(resourceDescDyn.getValuesResourceName(), xmlInflaterContext);
}
else if (resourceDesc instanceof ResourceDescCompiled)
{
Context ctx = xmlInflaterContext.getContext();
String resourceDescValue = resourceDesc.getResourceDescValue();
return getStringCompiled(resourceDescValue, ctx);
}
else throw MiscUtil.internalError();
}
private String getStringCompiled(String resourceDescValue, Context ctx)
{
if (isResource(resourceDescValue))
{
int resId = getIdentifierCompiled(resourceDescValue, ctx);
if (resId == 0) throw new ItsNatDroidException("Resource id value cannot be @null for a integer resource");
return ctx.getResources().getString(resId);
}
return StringUtil.convertEscapedStringLiteralToNormalString(resourceDescValue);
}
public CharSequence getText(ResourceDesc resourceDesc,XMLInflaterContext xmlInflaterContext)
{
if (resourceDesc instanceof ResourceDescDynamic)
{
ResourceDescDynamic resourceDescDyn = (ResourceDescDynamic)resourceDesc;
ElementValuesResources elementResources = getElementValuesResources(resourceDescDyn, xmlInflaterContext);
return elementResources.getText(resourceDescDyn.getValuesResourceName(), xmlInflaterContext);
}
else if (resourceDesc instanceof ResourceDescCompiled)
{
Context ctx = xmlInflaterContext.getContext();
String resourceDescValue = resourceDesc.getResourceDescValue();
return getTextCompiled(resourceDescValue, ctx);
}
else throw MiscUtil.internalError();
}
private CharSequence getTextCompiled(String resourceDescValue, Context ctx)
{
if (isResource(resourceDescValue))
{
int resId = getIdentifierCompiled(resourceDescValue, ctx);
if (resId == 0) throw new ItsNatDroidException("Resource id value cannot be @null for a text resource");
return ctx.getResources().getText(resId);
}
else
{
// Vemos si contiene HTML, nos ahorraremos el procesado como HTML sin serlo y además conseguiremos que funcionen los tests a nivel de misma clase devuelta, pues Android
// parece que hace lo mismo, es decir cuando no es HTML devuelve un String en vez de un SpannedString.
if (isHTML(resourceDescValue))
{
Spanned spannedValue = Html.fromHtml(resourceDescValue);
return new SpannedString(spannedValue); // Para que el tipo devuelto sea el mismo que en el caso compilado y pasemos los tests
}
return StringUtil.convertEscapedStringLiteralToNormalString(resourceDescValue);
}
}
private static boolean isHTML(String markup)
{
// Vemos si hay un </tag> (pues lo normal será usar <b>text</b> y similares)
// Es conveniente ver el código de Html.fromHtml(String)
int posStart = markup.indexOf("</");
if (posStart != -1)
{
int posEnd = markup.indexOf('>',posStart);
if (posEnd != -1)
{
String tag = markup.substring(posStart + 2, posEnd);
tag = tag.trim();
boolean isTag = StringUtil.isTag(tag);
if (isTag)
return true;
}
}
// Consideramos el <br/> cerrado (no consideramos <br> pues no se admite en XML compilado)
posStart = markup.indexOf("<");
if (posStart != -1)
{
int posEnd = markup.indexOf("/>",posStart);
if (posEnd != -1)
{
String tag = markup.substring(posStart + 1, posEnd);
tag = tag.trim();
boolean isTag = StringUtil.isTag(tag);
if (isTag)
return true;
}
}
boolean ENABLE_ENTITY_REF = true;
if (ENABLE_ENTITY_REF)
{
// Vemos si al menos hay un entityref ej <
// https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references
posStart = markup.indexOf('&');
if (posStart != -1)
{
int posEnd = markup.indexOf(';', posStart);
if (posEnd != -1 && posEnd - posStart > 2)
{
String entity = markup.substring(posStart + 1, posEnd);
boolean isWord = true;
for (int i = 0; i < entity.length(); i++)
{
char c = entity.charAt(i);
if (!Character.isLetter(c))
{
isWord = false;
break;
}
}
if (isWord) return true;
// Ya está hecho y lo dejamos aunque desactivado pero he descubierto que el parser XML de Android no admite el formato numhex; o &numdec; sólo entities con nombre conocidas (< etc)
if (false)
{
if (entity.length() >= 2)
{
if (entity.charAt(0) == '#')
{
if (entity.charAt(1) == 'x')
{
String numberHex = entity.substring(2, entity.length());
boolean isHexNumber = true;
for (int i = 0; i < numberHex.length(); i++)
{
char c = entity.charAt(i);
if (!Character.isLetterOrDigit(c))
{
isHexNumber = false;
break;
}
}
if (isHexNumber) return true;
}
else
{
String number = entity.substring(1, entity.length());
boolean isDecNumber = true;
for (int i = 0; i < number.length(); i++)
{
char c = entity.charAt(i);
if (!Character.isDigit(c))
{
isDecNumber = false;
break;
}
}
if (isDecNumber) return true;
}
}
}
}
}
}
}
return false;
}
public CharSequence[] getTextArray(ResourceDesc resourceDesc,XMLInflaterContext xmlInflaterContext)
{
if (resourceDesc instanceof ResourceDescDynamic)
{
ResourceDescDynamic resourceDescDyn = (ResourceDescDynamic)resourceDesc;
ElementValuesResources elementResources = getElementValuesResources(resourceDescDyn,xmlInflaterContext);
return elementResources.getTextArray(resourceDescDyn.getValuesResourceName(), xmlInflaterContext);
}
else if (resourceDesc instanceof ResourceDescCompiled)
{
Context ctx = xmlInflaterContext.getContext();
String resourceDescValue = resourceDesc.getResourceDescValue();
return getTextArrayCompiled(resourceDescValue, ctx);
}
else throw MiscUtil.internalError();
}
private CharSequence[] getTextArrayCompiled(String resourceDescValue, Context ctx)
{
if (isResource(resourceDescValue))
{
int resId = getIdentifierCompiled(resourceDescValue, ctx);
if (resId == 0) throw new ItsNatDroidException("Resource id value cannot be @null for a text array resource");
return ctx.getResources().getTextArray(resId);
}
else throw MiscUtil.internalError();
}
public boolean getBoolean(ResourceDesc resourceDesc,XMLInflaterContext xmlInflaterContext)
{
if (resourceDesc instanceof ResourceDescDynamic)
{
ResourceDescDynamic resourceDescDyn = (ResourceDescDynamic)resourceDesc;
ElementValuesResources elementResources = getElementValuesResources(resourceDescDyn, xmlInflaterContext);
return elementResources.getBoolean(resourceDescDyn.getValuesResourceName(), xmlInflaterContext);
}
else if (resourceDesc instanceof ResourceDescCompiled)
{
Context ctx = xmlInflaterContext.getContext();
String resourceDescValue = resourceDesc.getResourceDescValue();
return getBooleanCompiled(resourceDescValue, ctx);
}
else throw MiscUtil.internalError();
}
private boolean getBooleanCompiled(String resourceDescValue, Context ctx)
{
if (isResource(resourceDescValue))
{
int resId = getIdentifierCompiled(resourceDescValue, ctx);
if (resId == 0) throw new ItsNatDroidException("Resource id value cannot be @null for a boolean resource");
return ctx.getResources().getBoolean(resId);
}
else return Boolean.parseBoolean(resourceDescValue);
}
public boolean isDimension(ResourceDesc resourceDesc)
{
if (resourceDesc instanceof ResourceDescDynamic)
{
ResourceDescDynamic resourceDescDyn = (ResourceDescDynamic)resourceDesc;
return "dimen".equals(resourceDescDyn.getResourceType());
}
else if (resourceDesc instanceof ResourceDescCompiled)
{
String value = resourceDesc.getResourceDescValue();
if (value.startsWith("?android:attr/"))
throw new ItsNatDroidException("Resource type cannot be determined using \"?android:attr/\" in this context");
if (value.startsWith("@android:dimen/") || value.startsWith("@dimen/"))
return true;
return getDimensionSuffixNotError(value) != null;
}
else throw MiscUtil.internalError();
}
public Dimension getDimensionObject(ResourceDesc resourceDesc,XMLInflaterContext xmlInflaterContext)
{
if (resourceDesc instanceof ResourceDescDynamic)
{
ResourceDescDynamic resourceDescDyn = (ResourceDescDynamic)resourceDesc;
ElementValuesResources elementResources = getElementValuesResources(resourceDescDyn, xmlInflaterContext);
return elementResources.getDimensionObject(resourceDescDyn.getValuesResourceName(), xmlInflaterContext);
}
else if (resourceDesc instanceof ResourceDescCompiled)
{
Context ctx = xmlInflaterContext.getContext();
String originalValue = resourceDesc.getResourceDescValue();
return getDimensionObjectCompiled(originalValue, ctx);
}
else throw MiscUtil.internalError();
}
private Dimension getDimensionObjectCompiled(String resourceDescValue, Context ctx)
{
// El retorno es en px
if (isResource(resourceDescValue))
{
int resId = getIdentifierCompiled(resourceDescValue, ctx);
if (resId == 0) throw new ItsNatDroidException("Resource id value cannot be @null for a dimension resource");
float num = ctx.getResources().getDimension(resId);
return new Dimension(TypedValue.COMPLEX_UNIT_PX, num);
}
else
{
return getDimensionObjectCompiled(resourceDescValue);
}
}
private static Dimension getDimensionObjectCompiled(String attrValue)
{
// Suponemos que NO es un recurso externo
// El retorno es en px
String valueTrim = attrValue.trim();
String suffix = getDimensionSuffix(valueTrim);
int complexUnit = getDimensionSuffixAsInt(suffix);
float num = extractFloat(valueTrim, suffix);
return new Dimension(complexUnit, num);
}
public int getDimensionIntFloor(ResourceDesc resourceDesc,XMLInflaterContext xmlInflaterContext)
{
// TypedValue.complexToDimensionPixelOffset
return (int) getDimensionFloat(resourceDesc, xmlInflaterContext);
}
public int getDimensionIntRound(ResourceDesc resourceDesc,XMLInflaterContext xmlInflaterContext)
{
// TypedValue.complexToDimensionPixelSize
float num = getDimensionFloat(resourceDesc, xmlInflaterContext);
int numInt = (int)(num + 0.5f);
return numInt;
}
public float getDimensionFloat(ResourceDesc resourceDesc,XMLInflaterContext xmlInflaterContext)
{
// El retorno es en px
Dimension dimen = getDimensionObject(resourceDesc, xmlInflaterContext);
int unit = dimen.getComplexUnit(); // TypedValue.COMPLEX_UNIT_DIP etc
float num = dimen.getValue();
Resources res = xmlInflaterContext.getContext().getResources();
return toPixelFloat(unit, num, res);
}
public float getDimensionFloatFloor(ResourceDesc resourceDesc,XMLInflaterContext xmlInflaterContext)
{
// El retorno es en px
float num = getDimensionFloat(resourceDesc, xmlInflaterContext);
// num = (float) Math.floor(num);
int numInt = (int)num;
return numInt;
}
public float getDimensionFloatRound(ResourceDesc resourceDesc,XMLInflaterContext xmlInflaterContext)
{
// El retorno es en px
float num = getDimensionFloat(resourceDesc, xmlInflaterContext);
//num = Math.round(num);
int numInt = (int)(num + 0.5f);
return numInt;
}
public PercFloatImpl getDimensionPercFloat(ResourceDesc resourceDesc, XMLInflaterContext xmlInflaterContext)
{
if (resourceDesc instanceof ResourceDescDynamic)
{
ResourceDescDynamic resourceDescDyn = (ResourceDescDynamic)resourceDesc;
ElementValuesResources elementResources = getElementValuesResources(resourceDescDyn, xmlInflaterContext);
return elementResources.getDimensionPercFloat(resourceDescDyn.getValuesResourceName(), xmlInflaterContext);
}
else if (resourceDesc instanceof ResourceDescCompiled)
{
Context ctx = xmlInflaterContext.getContext();
String originalValue = resourceDesc.getResourceDescValue();
return getDimensionPercFloatCompiled(originalValue, ctx);
}
else throw MiscUtil.internalError();
}
private PercFloatImpl getDimensionPercFloatCompiled(String resourceDescValue, Context ctx)
{
return getDimensionPercFloatCompiled(resourceDescValue,ctx,true);
}
private PercFloatImpl getDimensionPercFloatCompiled(String resourceDescValue, Context ctx, boolean acceptDimension)
{
// Este método y PercFloat sólo se usa para el gradientRadius de GradientDrawable <shape> <gradient android:gradientRadius android:centerX y centerY>
// Las notas se refieren a gradientRadius:
// La documentación dice que puede tener tres posibles valores:
// Una referencia a un recurso externo "@..." "?..." cuyo valor en el recurso puede ser cualquiera de los siguientes
// Un valor float como tal ej "20.3"
// Un valor porcentual con dos variantes: "10.3%" "10.3%p"
// Un valor dimension: ej "10.3dp"
// El caso es que la documentación está ACTUALIZADA a las versiones últimas y no distingue entre versiones, el problema es que la versión 15 (4.0.3) soporta
// todos los casos excepto el dimension, el editor visual da error y el compilador también. Si se usa un resource y se pone un dimension, en 4.0.3 se ignora.
// Lo que haremos para adelantarnos al futuro es implementar también el caso de dimension, no nos cuesta apenas nada y en los ejemplos NO usar dimension
// Notas:
// No soy capaz de distinguir entre % y %p
// El "valor float como tal" yo creo que son pixels, y esa es la razón por la que no existía la variante dimension inicialmente, luego se añadió para homogeneizar y mejorar la portabilidad
// Recuerda que gradientRadius sólo se usa en el caso de RADIAL_GRADIENT, en dicho caso de hecho es obligatorio
Resources res = ctx.getResources();
if (isResource(resourceDescValue))
{
int resId = getIdentifierCompiled(resourceDescValue, ctx);
if (resId == 0) throw new ItsNatDroidException("Resource id value cannot be @null for a dimension resource");
String value = res.getString(resId);
return parseDimensionPercFloat(value, ctx, acceptDimension);
}
else
{
return parseDimensionPercFloat(resourceDescValue, ctx, acceptDimension);
}
}
private PercFloatImpl parseDimensionPercFloat(String attrValue, Context ctx, boolean acceptDimension)
{
// El retorno es en px
int dataType;
int pos;
pos = attrValue.lastIndexOf("%");
if (pos != -1)
{
dataType = TypedValue.TYPE_FRACTION;
boolean fractionParent = (attrValue.lastIndexOf("%p") != -1);
attrValue = attrValue.substring(0, pos);
float value = Float.parseFloat(attrValue);
return new PercFloatImpl(dataType, fractionParent, value);
}
else
{
final boolean fractionParent = false; // Es indiferente
dataType = TypedValue.TYPE_FLOAT;
char last = attrValue.charAt(attrValue.length() - 1);
if (Character.isDigit(last) || last == '.') // 3. es un float válido
{
float value = Float.parseFloat(attrValue);
return new PercFloatImpl(dataType, fractionParent, value); // fractionParent es indiferente
}
else
{
if (!acceptDimension) throw new ItsNatDroidException("Dimensions like 20dp are not accepted");
Dimension dimen = getDimensionObjectCompiled(attrValue);
int unit = dimen.getComplexUnit(); // TypedValue.COMPLEX_UNIT_DIP etc
float num = dimen.getValue();
float value = toPixelFloat(unit, num, ctx.getResources());
return new PercFloatImpl(dataType, fractionParent, value); // fractionParent es indiferente
}
}
}
public PercFloatImpl getPercFloat(ResourceDesc resourceDesc, XMLInflaterContext xmlInflaterContext)
{
if (resourceDesc instanceof ResourceDescDynamic)
{
ResourceDescDynamic resourceDescDyn = (ResourceDescDynamic)resourceDesc;
ElementValuesResources elementResources = getElementValuesResources(resourceDescDyn, xmlInflaterContext);
return elementResources.getPercFloat(resourceDescDyn.getValuesResourceName(), xmlInflaterContext);
}
else if (resourceDesc instanceof ResourceDescCompiled)
{
Context ctx = xmlInflaterContext.getContext();
String originalValue = resourceDesc.getResourceDescValue();
return getDimensionPercFloatCompiled(originalValue, ctx, false);
}
else throw MiscUtil.internalError();
}
public String getDimensionOrString(ResourceDesc resourceDesc, XMLInflaterContext xmlInflaterContext)
{
if (resourceDesc instanceof ResourceDescDynamic)
{
ResourceDescDynamic resourceDescDyn = (ResourceDescDynamic)resourceDesc;
ElementValuesResources elementResources = getElementValuesResources(resourceDescDyn, xmlInflaterContext);
return elementResources.getDimensionOrString(resourceDescDyn.getValuesResourceName(), xmlInflaterContext);
}
else if (resourceDesc instanceof ResourceDescCompiled)
{
Context ctx = xmlInflaterContext.getContext();
String resourceDescValue = resourceDesc.getResourceDescValue();
return getStringCompiled(resourceDescValue, ctx);
}
else throw MiscUtil.internalError();
}
public int getDimensionWithNameIntRound(ResourceDesc resourceDesc,XMLInflaterContext xmlInflaterContext)
{
int dimension;
String value = resourceDesc.getResourceDescValue();
// No hace falta hacer trim en caso de "match_parent" etc un espacio fastidia el attr
if ("match_parent".equals(value) || "fill_parent".equals(value))
dimension = ViewGroup.LayoutParams.MATCH_PARENT;
else if ("wrap_content".equals(value))
dimension = ViewGroup.LayoutParams.WRAP_CONTENT;
else
dimension = getDimensionIntRound(resourceDesc, xmlInflaterContext);
return dimension;
}
private static int getDimensionSuffixAsInt(String suffix)
{
if (suffix.equals("dp") || suffix.equals("dip")) return TypedValue.COMPLEX_UNIT_DIP;
else if (suffix.equals("px")) return TypedValue.COMPLEX_UNIT_PX;
else if (suffix.equals("sp")) return TypedValue.COMPLEX_UNIT_SP;
else if (suffix.equals("in")) return TypedValue.COMPLEX_UNIT_IN;
else if (suffix.equals("mm")) return TypedValue.COMPLEX_UNIT_MM;
else throw MiscUtil.internalError();
}
private static String getDimensionSuffix(String value)
{
String suffix = getDimensionSuffixNotError(value);
if (suffix == null) throw new ItsNatDroidException("Unrecognized dimension: " + value);
return suffix;
}
public static String getDimensionSuffixNotError(String value)
{
if (value.endsWith(" ")) throw new ItsNatDroidException("Dimension cannot end with space: \"" + value + "\"");
if (value.endsWith("dp")) return "dp";
if (value.endsWith("dip")) // Concesión al pasado
return "dip";
else if (value.endsWith("px")) return "px";
else if (value.endsWith("sp")) return "sp";
else if (value.endsWith("in")) return "in";
else if (value.endsWith("mm")) return "mm";
else return null;
}
private static float parseFloat(String value)
{
return Float.parseFloat(value);
}
private static float extractFloat(String value, String suffix)
{
int pos = value.lastIndexOf(suffix);
value = value.substring(0, pos);
return parseFloat(value);
}
private static float toPixelFloat(int unit, float value, Resources res)
{
// Nexus 4 tiene un scale 2 de dp a px (xhdpi), con un valor de 0.3 devuelve 0.6 bien para probar si usar round/floor
// Nexus 5 tiene un scale 3 de dp a px (xxhdpi), con un valor de 0.3 devuelve 0.9 bien para probar si usar round/floor
// La VM ItsNatDroid es una Nexus 4
return TypedValue.applyDimension(unit, value, res.getDisplayMetrics());
}
public boolean isColor(ResourceDesc resourceDesc)
{
if (resourceDesc instanceof ResourceDescDynamic)
{
ResourceDescDynamic resourceDescDyn = (ResourceDescDynamic)resourceDesc;
return "color".equals(resourceDescDyn.getResourceType());
}
else if (resourceDesc instanceof ResourceDescCompiled)
{
String value = resourceDesc.getResourceDescValue();
if (value.startsWith("?android:attr/"))
throw new ItsNatDroidException("Resource type cannot be determined using \"?android:attr/\" in this context");
if (value.startsWith("@android:color/") || value.startsWith("@color/"))
return true;
return value.startsWith("#");
}
else throw MiscUtil.internalError();
}
public int getColor(ResourceDesc resourceDesc,XMLInflaterContext xmlInflaterContext)
{
if (resourceDesc instanceof ResourceDescDynamic)
{
ResourceDescDynamic resourceDescDyn = (ResourceDescDynamic)resourceDesc;
ElementValuesResources elementResources = getElementValuesResources(resourceDescDyn, xmlInflaterContext);
return elementResources.getColor(resourceDescDyn.getValuesResourceName(), xmlInflaterContext);
}
else if (resourceDesc instanceof ResourceDescCompiled)
{
Context ctx = xmlInflaterContext.getContext();
String resourceDescValue = resourceDesc.getResourceDescValue();
return getColorCompiled(resourceDescValue, ctx);
}
else throw MiscUtil.internalError();
}
private int getColorCompiled(String resourceDescValue, Context ctx)
{
if (isResource(resourceDescValue))
{
int resId = getIdentifierCompiled(resourceDescValue, ctx);
if (resId == 0) throw new ItsNatDroidException("Resource id value cannot be @null for a color resource");
return ctx.getResources().getColor(resId);
}
else if (resourceDescValue.startsWith("#")) // Color literal. No hace falta hacer trim
{
return Color.parseColor(resourceDescValue);
}
throw new ItsNatDroidException("Cannot process " + resourceDescValue);
}
public static String toStringColorTransparent(int value)
{
if (value != Color.TRANSPARENT) throw MiscUtil.internalError();
return "#00000000";
}
public Drawable getDrawable(ResourceDesc resourceDesc,XMLInflaterContext xmlInflaterContext)
{
Context ctx = xmlInflaterContext.getContext();
if (resourceDesc instanceof ResourceDescDynamic)
{
ResourceDescDynamic resourceDescDyn = (ResourceDescDynamic)resourceDesc;
if (resourceDescDyn.getValuesResourceName() != null)
{
ElementValuesResources elementResources = getElementValuesResources(resourceDescDyn, xmlInflaterContext);
return elementResources.getDrawable(resourceDescDyn.getValuesResourceName(), xmlInflaterContext);
}
else
{
int bitmapDensityReference = xmlInflaterContext.getBitmapDensityReference();
String resourceMime = resourceDescDyn.getResourceMime();
if (MimeUtil.isMIMEResourceXML(resourceMime))
{
// Esperamos un drawable
PageImpl page = xmlInflaterContext.getPageImpl();
if (resourceDesc instanceof ResourceDescRemote && page == null) throw MiscUtil.internalError(); // Si es remote hay page por medio
AttrResourceInflaterListener attrResourceInflaterListener = xmlInflaterContext.getAttrResourceInflaterListener();
ParsedResourceXMLDOM resource = (ParsedResourceXMLDOM) resourceDescDyn.getParsedResource();
XMLDOMDrawable xmlDOMDrawable = (XMLDOMDrawable) resource.getXMLDOM();
InflatedXMLDrawable inflatedDrawable = InflatedXMLDrawable.createInflatedDrawable(itsNatDroid, xmlDOMDrawable, ctx, page);
XMLInflaterDrawable xmlInflaterDrawable = XMLInflaterDrawable.createXMLInflaterDrawable(inflatedDrawable, bitmapDensityReference,attrResourceInflaterListener);
return xmlInflaterDrawable.inflateDrawable();
}
else if (MimeUtil.isMIMEResourceImage(resourceMime))
{
ParsedResourceImage resource = (ParsedResourceImage) resourceDescDyn.getParsedResource();
byte[] byteArray = resource.getImgBytes();
boolean expectedNinePatch = resourceDescDyn.isNinePatch();
return BitmapDrawableUtil.createImageBasedDrawable(byteArray, bitmapDensityReference, expectedNinePatch, ctx.getResources());
}
else throw new ItsNatDroidException("Unsupported resource mime: " + resourceMime);
}
}
else if (resourceDesc instanceof ResourceDescCompiled)
{
String resourceDescValue = resourceDesc.getResourceDescValue();
return getDrawableCompiled(resourceDescValue, ctx);
}
else throw MiscUtil.internalError();
}
private Drawable getDrawableCompiled(String resourceDescValue, Context ctx)
{
if (isResource(resourceDescValue))
{
int resId = getIdentifierCompiled(resourceDescValue, ctx);
if (resId == 0) return null;
return ctx.getResources().getDrawable(resId);
}
else if (resourceDescValue.startsWith("#")) // Color literal. No hace falta hacer trim
{
int color = Color.parseColor(resourceDescValue);
return new ColorDrawable(color);
}
throw new ItsNatDroidException("Cannot process " + resourceDescValue);
}
public View getLayout(ResourceDesc resourceDesc,XMLInflaterContext xmlInflaterContext,XMLInflaterLayout xmlInflaterParent, ViewGroup viewParent, int indexChild)
{
return getLayout(resourceDesc,xmlInflaterContext,xmlInflaterParent, viewParent, indexChild, null);
}
public View getLayout(ResourceDesc resourceDesc,XMLInflaterContext xmlInflaterContext,XMLInflaterLayout xmlInflaterParent, ViewGroup viewParent, int indexChild, ArrayList<DOMAttr> includeAttribs)
{
LayoutValue lv = getLayoutValue(resourceDesc,xmlInflaterContext,xmlInflaterParent, viewParent, indexChild, includeAttribs);
return lv.getView();
}
public LayoutValue getLayoutValue(ResourceDesc resourceDesc,XMLInflaterContext xmlInflaterContext,XMLInflaterLayout xmlInflaterParent, ViewGroup viewParent, int indexChild, ArrayList<DOMAttr> includeAttribs)
{
if (resourceDesc instanceof ResourceDescDynamic)
{
ResourceDescDynamic resourceDescDyn = (ResourceDescDynamic)resourceDesc;
if (resourceDescDyn.getValuesResourceName() != null)
{
ElementValuesResources elementResources = getElementValuesResources(resourceDescDyn, xmlInflaterContext);
return elementResources.getLayoutValue(resourceDescDyn.getValuesResourceName(), xmlInflaterContext, xmlInflaterParent, viewParent, indexChild, includeAttribs);
}
else
{
View rootViewOrViewParent = getViewLayoutDynamicFromXML(resourceDescDyn, xmlInflaterContext, xmlInflaterParent, viewParent,indexChild, includeAttribs);
return new LayoutValueDynamic(rootViewOrViewParent);
}
}
else if (resourceDesc instanceof ResourceDescCompiled)
{
Context ctx = xmlInflaterParent.getContext();
String resourceDescValue = resourceDesc.getResourceDescValue();
int layoutId = getIdentifierCompiled(resourceDescValue, ctx);
View rootViewOrViewParent = getViewLayoutCompiled(layoutId,xmlInflaterContext.getContext(),xmlInflaterParent, viewParent, indexChild, includeAttribs);
return new LayoutValueCompiled(rootViewOrViewParent,layoutId);
}
else throw MiscUtil.internalError();
}
private View getViewLayoutCompiled(int resId,Context ctx,XMLInflaterLayout xmlInflaterParent, ViewGroup viewParent, int indexChild, ArrayList<DOMAttr> includeAttribs)
{
// viewParent es por ahora NO nulo
int countBefore = -1;
if (viewParent != null)
{
countBefore = viewParent.getChildCount();
}
View rootView = LayoutInflater.from(ctx).inflate(resId, viewParent);
if (viewParent != null)
{
if (rootView != viewParent) throw MiscUtil.internalError(); // rootView es igual a viewParent
int countAfter = viewParent.getChildCount();
int countInserted = countAfter - countBefore;
if (countInserted == 1 && includeAttribs != null)
{
View rootViewChild = viewParent.getChildAt(indexChild);
xmlInflaterParent.fillIncludeAttributesFromGetLayout(rootViewChild, viewParent, includeAttribs);
}
}
return rootView;
}
private View getViewLayoutDynamicFromXML(ResourceDescDynamic resourceDesc,XMLInflaterContext xmlInflaterContext, XMLInflaterLayout xmlInflaterParent, ViewGroup viewParent, int indexChild, ArrayList<DOMAttr> includeAttribs)
{
if (resourceDesc.getValuesResourceName() != null) throw MiscUtil.internalError();
Context ctx = xmlInflaterContext.getContext();
String resourceMime = resourceDesc.getResourceMime();
if (MimeUtil.isMIMEResourceXML(resourceMime))
{
int bitmapDensityReference = xmlInflaterContext.getBitmapDensityReference();
PageImpl pageParent = xmlInflaterContext.getPageImpl();
if (resourceDesc instanceof ResourceDescRemote && pageParent == null) throw MiscUtil.internalError(); // Si es remote hay page por medio
int countBefore = 0;
if (viewParent != null)
{
countBefore = viewParent.getChildCount();
}
AttrResourceInflaterListener attrResourceInflaterListener = xmlInflaterContext.getAttrResourceInflaterListener();
ParsedResourceXMLDOM resource = (ParsedResourceXMLDOM) resourceDesc.getParsedResource();
XMLDOMLayout xmlDOMLayout = (XMLDOMLayout) resource.getXMLDOM();
XMLInflaterLayout xmlInflaterLayout = XMLInflaterLayout.createXMLInflaterLayout(itsNatDroid, xmlDOMLayout, bitmapDensityReference, attrResourceInflaterListener, ctx, pageParent);
//View rootViewOrViewParent = xmlInflaterLayout.getInflatedXMLLayoutImpl().getRootView();
View rootViewOrViewParent = xmlInflaterLayout.inflateLayout(viewParent, indexChild);
if (pageParent != null) // existe página padre
{
XMLInflaterLayoutPage xmlInflaterLayoutPageParent = (XMLInflaterLayoutPage) xmlInflaterParent;
InflatedXMLLayoutPageImpl inflatedLayoutPageParent = xmlInflaterLayoutPageParent.getInflatedXMLLayoutPageImpl();
InflatedXMLLayoutPageImpl inflatedLayoutPage = ((XMLInflaterLayoutPage) xmlInflaterLayout).getInflatedXMLLayoutPageImpl();
List<String> scriptList = inflatedLayoutPage.getScriptList();
if (!scriptList.isEmpty())
{
inflatedLayoutPageParent.getScriptList().addAll(scriptList);
}
if (inflatedLayoutPage instanceof InflatedXMLLayoutPageItsNatImpl)
{
String loadInitScript = ((InflatedXMLLayoutPageItsNatImpl) inflatedLayoutPage).getLoadInitScript();
if (loadInitScript != null) throw new ItsNatDroidException("Scripting must be disabled in ItsNat Server document for referenced layouts"); // Pues el itsNatDoc es el del padre y la liamos al intentar iniciar un layout siendo incluido en el padre acaba cambiando la inicialización del padre, esto no quita que <script> normales sean permitidos como en web
}
}
if (viewParent != null)
{
if (rootViewOrViewParent != viewParent) throw MiscUtil.internalError(); // rootViewOrViewParent es igual a viewParent
int countAfter = viewParent.getChildCount();
int countInserted = countAfter - countBefore;
if (countInserted == 1 && includeAttribs != null)
{
View rootViewChild = viewParent.getChildAt(indexChild);
xmlInflaterLayout.fillIncludeAttributesFromGetLayout(rootViewChild, viewParent, includeAttribs);
}
}
InflatedXMLLayoutImpl inflatedLayout = xmlInflaterLayout.getInflatedXMLLayoutImpl();
ViewMapByXMLId viewMapByXMLId = inflatedLayout.getViewMapByXMLId();
WeakMapWithValue<String, View> weakMapWithValue = viewMapByXMLId.getMapIdViewXMLStdPureField();
if (weakMapWithValue != null)
{
InflatedXMLLayoutImpl inflatedLayoutParent = (InflatedXMLLayoutImpl) xmlInflaterParent.getInflatedXML();
weakMapWithValue.copyTo(inflatedLayoutParent.getViewMapByXMLId().getMapIdViewXMLStd());
}
return rootViewOrViewParent;
}
else throw new ItsNatDroidException("Unsupported resource mime: " + resourceMime);
}
public LayoutAnimationController getLayoutAnimation(ResourceDesc resourceDesc, XMLInflaterContext xmlInflaterContext)
{
if (resourceDesc instanceof ResourceDescDynamic)
{
ResourceDescDynamic resourceDescDyn = (ResourceDescDynamic)resourceDesc;
if (resourceDescDyn.getValuesResourceName() != null)
{
ElementValuesResources elementResources = getElementValuesResources(resourceDescDyn, xmlInflaterContext);
return elementResources.getLayoutAnimation(resourceDescDyn.getValuesResourceName(), xmlInflaterContext);
}
else
{
return getLayoutAnimationDynamicFromXML(resourceDescDyn,xmlInflaterContext);
}
}
else if (resourceDesc instanceof ResourceDescCompiled)
{
Context ctx = xmlInflaterContext.getContext();
String resourceDescValue = resourceDesc.getResourceDescValue();
return getLayoutAnimationCompiled(resourceDescValue, ctx);
}
else throw MiscUtil.internalError();
}
private LayoutAnimationController getLayoutAnimationDynamicFromXML(ResourceDescDynamic resourceDescDyn, XMLInflaterContext xmlInflaterContext)
{
if (resourceDescDyn.getValuesResourceName() != null) throw MiscUtil.internalError();
Context ctx = xmlInflaterContext.getContext();
int bitmapDensityReference = xmlInflaterContext.getBitmapDensityReference();
AttrResourceInflaterListener attrResourceInflaterListener = xmlInflaterContext.getAttrResourceInflaterListener();
// Esperamos un LayoutAnimationController
PageImpl page = xmlInflaterContext.getPageImpl(); // Puede ser null
if (resourceDescDyn instanceof ResourceDescRemote && page == null) throw MiscUtil.internalError(); // Si es remote hay page por medio
ParsedResourceXMLDOM resource = (ParsedResourceXMLDOM) resourceDescDyn.getParsedResource();
if (resource == null)
throw new ItsNatDroidException("Resource is still not loaded, if remote resource maybe you should use an attribute with namespace " + NamespaceUtil.XMLNS_ITSNATDROID_RESOURCE + " for manual load declaration");
XMLDOMLayoutAnimation xmlDOMLayoutAnimation = (XMLDOMLayoutAnimation) resource.getXMLDOM();
InflatedXMLLayoutAnimation inflatedLayoutAnimation = InflatedXMLLayoutAnimation.createInflatedLayoutAnimation(itsNatDroid, xmlDOMLayoutAnimation, ctx, page);
XMLInflaterLayoutAnimation xmlInflaterLayoutAnimation = XMLInflaterLayoutAnimation.createXMLInflaterLayoutAnimation(inflatedLayoutAnimation, bitmapDensityReference, attrResourceInflaterListener);
return xmlInflaterLayoutAnimation.inflateLayoutAnimation();
}
private LayoutAnimationController getLayoutAnimationCompiled(String resourceDescValue,Context ctx)
{
int id = getIdentifierCompiled(resourceDescValue, ctx);
if (id <= 0)
return null;
return AnimationUtils.loadLayoutAnimation(ctx, id);
}
public Animation getAnimation(ResourceDesc resourceDesc,XMLInflaterContext xmlInflaterContext)
{
if (resourceDesc instanceof ResourceDescDynamic)
{
ResourceDescDynamic resourceDescDyn = (ResourceDescDynamic)resourceDesc;
if (resourceDescDyn.getValuesResourceName() != null)
{
ElementValuesResources elementResources = getElementValuesResources(resourceDescDyn, xmlInflaterContext);
return elementResources.getAnimation(resourceDescDyn.getValuesResourceName(), xmlInflaterContext);
}
else
{
return getAnimationDynamicFromXML(resourceDescDyn,xmlInflaterContext);
}
}
else if (resourceDesc instanceof ResourceDescCompiled)
{
Context ctx = xmlInflaterContext.getContext();
String resourceDescValue = resourceDesc.getResourceDescValue();
return getAnimationCompiled(resourceDescValue, ctx);
}
else throw MiscUtil.internalError();
}
private Animation getAnimationDynamicFromXML(ResourceDescDynamic resourceDescDyn, XMLInflaterContext xmlInflaterContext)
{
if (resourceDescDyn.getValuesResourceName() != null) throw MiscUtil.internalError();
Context ctx = xmlInflaterContext.getContext();
int bitmapDensityReference = xmlInflaterContext.getBitmapDensityReference();
AttrResourceInflaterListener attrResourceInflaterListener = xmlInflaterContext.getAttrResourceInflaterListener();
// Esperamos un Animator
PageImpl page = xmlInflaterContext.getPageImpl(); // Puede ser null
if (resourceDescDyn instanceof ResourceDescRemote && page == null) throw MiscUtil.internalError(); // Si es remote hay page por medio
ParsedResourceXMLDOM resource = (ParsedResourceXMLDOM) resourceDescDyn.getParsedResource();
if (resource == null)
throw new ItsNatDroidException("Resource is still not loaded, if remote resource maybe you should use an attribute with namespace " + NamespaceUtil.XMLNS_ITSNATDROID_RESOURCE + " for manual load declaration");
XMLDOMAnimation xmlDOMAnimation = (XMLDOMAnimation) resource.getXMLDOM();
InflatedXMLAnimation inflatedAnimation = InflatedXMLAnimation.createInflatedAnimation(itsNatDroid, xmlDOMAnimation, ctx, page);
XMLInflaterAnimation xmlInflaterAnimation = XMLInflaterAnimation.createXMLInflaterAnimation(inflatedAnimation, bitmapDensityReference, attrResourceInflaterListener);
return xmlInflaterAnimation.inflateAnimation();
}
private Animation getAnimationCompiled(String resourceDescValue,Context ctx)
{
int id = getIdentifierCompiled(resourceDescValue, ctx);
if (id <= 0)
return null;
return AnimationUtils.loadAnimation(ctx, id);
}
public Animator getAnimator(ResourceDesc resourceDesc,XMLInflaterContext xmlInflaterContext)
{
if (resourceDesc instanceof ResourceDescDynamic)
{
ResourceDescDynamic resourceDescDyn = (ResourceDescDynamic)resourceDesc;
if (resourceDescDyn.getValuesResourceName() != null)
{
ElementValuesResources elementResources = getElementValuesResources(resourceDescDyn, xmlInflaterContext);
return elementResources.getAnimator(resourceDescDyn.getValuesResourceName(), xmlInflaterContext);
}
else
{
return getAnimatorDynamicFromXML(resourceDescDyn,xmlInflaterContext);
}
}
else if (resourceDesc instanceof ResourceDescCompiled)
{
Context ctx = xmlInflaterContext.getContext();
String resourceDescValue = resourceDesc.getResourceDescValue();
return getAnimatorCompiled(resourceDescValue, ctx);
}
else throw MiscUtil.internalError();
}
private Animator getAnimatorDynamicFromXML(ResourceDescDynamic resourceDescDyn, XMLInflaterContext xmlInflaterContext)
{
if (resourceDescDyn.getValuesResourceName() != null) throw MiscUtil.internalError();
Context ctx = xmlInflaterContext.getContext();
int bitmapDensityReference = xmlInflaterContext.getBitmapDensityReference();
AttrResourceInflaterListener attrResourceInflaterListener = xmlInflaterContext.getAttrResourceInflaterListener();
// Esperamos un Animator
PageImpl page = xmlInflaterContext.getPageImpl(); // Puede ser null
if (resourceDescDyn instanceof ResourceDescRemote && page == null) throw MiscUtil.internalError(); // Si es remote hay page por medio
ParsedResourceXMLDOM resource = (ParsedResourceXMLDOM) resourceDescDyn.getParsedResource();
if (resource == null)
throw new ItsNatDroidException("Resource is still not loaded, maybe you should use an attribute with namespace " + NamespaceUtil.XMLNS_ITSNATDROID_RESOURCE + " for manual load declaration");
XMLDOMAnimator xmlDOMAnimator = (XMLDOMAnimator) resource.getXMLDOM();
InflatedXMLAnimator inflatedAnimator = InflatedXMLAnimator.createInflatedAnimator(itsNatDroid, xmlDOMAnimator, ctx, page);
XMLInflaterAnimator xmlInflaterAnimator = XMLInflaterAnimator.createXMLInflaterAnimator(inflatedAnimator, bitmapDensityReference, attrResourceInflaterListener);
return xmlInflaterAnimator.inflateAnimator();
}
private Animator getAnimatorCompiled(String resourceDescValue,Context ctx)
{
int id = getIdentifierCompiled(resourceDescValue, ctx);
if (id <= 0)
return null;
return AnimatorInflater.loadAnimator(ctx, id);
}
public Interpolator getInterpolator(ResourceDesc resourceDesc, XMLInflaterContext xmlInflaterContext)
{
// http://developer.android.com/guide/topics/resources/animation-resource.html#Interpolators
if (resourceDesc instanceof ResourceDescDynamic)
{
ResourceDescDynamic resourceDescDyn = (ResourceDescDynamic)resourceDesc;
if (resourceDescDyn.getValuesResourceName() != null)
{
ElementValuesResources elementResources = getElementValuesResources(resourceDescDyn, xmlInflaterContext);
return elementResources.getInterpolator(resourceDescDyn.getValuesResourceName(), xmlInflaterContext);
}
else
{
return getInterpolatorDynamicFromXML(resourceDescDyn,xmlInflaterContext);
}
}
else if (resourceDesc instanceof ResourceDescCompiled)
{
Context ctx = xmlInflaterContext.getContext();
String resourceDescValue = resourceDesc.getResourceDescValue();
return getInterpolatorCompiled(resourceDescValue, ctx);
}
else throw MiscUtil.internalError();
}
private Interpolator getInterpolatorDynamicFromXML(ResourceDescDynamic resourceDescDyn, XMLInflaterContext xmlInflaterContext)
{
if (resourceDescDyn.getValuesResourceName() != null) throw MiscUtil.internalError();
Context ctx = xmlInflaterContext.getContext();
int bitmapDensityReference = xmlInflaterContext.getBitmapDensityReference();
AttrResourceInflaterListener attrResourceInflaterListener = xmlInflaterContext.getAttrResourceInflaterListener();
// Esperamos un Animator
PageImpl page = xmlInflaterContext.getPageImpl(); // Puede ser null
if (resourceDescDyn instanceof ResourceDescRemote && page == null) throw MiscUtil.internalError(); // Si es remote hay page por medio
ParsedResourceXMLDOM resource = (ParsedResourceXMLDOM) resourceDescDyn.getParsedResource();
if (resource == null)
throw new ItsNatDroidException("Resource is still not loaded, if remote resource maybe you should use an attribute with namespace " + NamespaceUtil.XMLNS_ITSNATDROID_RESOURCE + " for manual load declaration");
XMLDOMInterpolator xmlDOMInterpolator = (XMLDOMInterpolator) resource.getXMLDOM();
InflatedXMLInterpolator inflatedInterpolator = InflatedXMLInterpolator.createInflatedInterpolator(itsNatDroid, xmlDOMInterpolator, ctx, page);
XMLInflaterInterpolator xmlInflaterInterpolator = XMLInflaterInterpolator.createXMLInflaterInterpolator(inflatedInterpolator, bitmapDensityReference, attrResourceInflaterListener);
return xmlInflaterInterpolator.inflateInterpolator();
}
private Interpolator getInterpolatorCompiled(String resourceDescValue,Context ctx)
{
int id = getIdentifierCompiled(resourceDescValue, ctx);
if (id <= 0)
return null;
return AnimationUtils.loadInterpolator(ctx, id);
}
private ElementValuesResources getElementValuesResources(ResourceDescDynamic resourceDescDynamic, XMLInflaterContext xmlInflaterContext)
{
ParsedResourceXMLDOM resource = (ParsedResourceXMLDOM)resourceDescDynamic.getParsedResource();
XMLDOMValues xmlDOMValues = (XMLDOMValues)resource.getXMLDOM();
// Una vez parseado XMLDOMValues y cargados los recursos remotos se cachea y NO se modifica (no hay un pre-clonado
// El resultado de inflar es ElementValuesResources que básicamente contiene los valores de <item> <dim> etc ORIGINALES SIN RESOLVER RESPECTO AL Context, dichos valores sólo pueden
// cambiar si cambia el XMLDOMValues original (lo cual es posible) pero entonces será un XMLDOMValues
// A donde quiero llegar es que PODEMOS CACHEAR ElementValuesResources sin miedo respecto a XMLDOMValues, no es el caso de cachear InflatedValues (el objeto padre) el cual contiene el Context
// Afortunadamente aunque InflatedValues es el objeto padre de ElementValuesResources, este último NO tiene referencia alguna a InflatedValues padre por lo que éste se pierde y no retiene el Context
// Es importante cachear ElementValuesResources de otra manera inflar por cada obtención de un valor es costosísimo
ElementValuesResources elementValuesResources = cacheXMLDOMValuesXMLInflaterValuesMap.get(xmlDOMValues);
if (elementValuesResources != null)
return elementValuesResources;
Context ctx = xmlInflaterContext.getContext();
String resourceMime = resourceDescDynamic.getResourceMime();
if (!MimeUtil.isMIMEResourceXML(resourceMime))
throw new ItsNatDroidException("Unsupported resource MIME in this context: " + resourceMime);
PageImpl page = xmlInflaterContext.getPageImpl(); // Puede ser null
if (resourceDescDynamic instanceof ResourceDescRemote && page == null) throw MiscUtil.internalError(); // Si es remote hay page por medio
int bitmapDensityReference = xmlInflaterContext.getBitmapDensityReference();
AttrResourceInflaterListener attrResourceInflaterListener = xmlInflaterContext.getAttrResourceInflaterListener();
InflatedXMLValues inflatedValues = InflatedXMLValues.createInflatedValues(itsNatDroid, xmlDOMValues, ctx, page);
XMLInflaterValues xmlInflaterValues = XMLInflaterValues.createXMLInflaterValues(inflatedValues, bitmapDensityReference, attrResourceInflaterListener);
ElementValuesResources elementResources = xmlInflaterValues.inflateValues();
cacheXMLDOMValuesXMLInflaterValuesMap.put(xmlDOMValues,elementResources);
return elementResources;
}
}