package org.itsnat.droid.impl.dom;
import android.content.res.Configuration;
import android.os.Build;
import android.util.DisplayMetrics;
import org.itsnat.droid.ItsNatDroidException;
import org.itsnat.droid.impl.dom.values.XMLDOMValues;
import org.itsnat.droid.impl.domparser.XMLDOMParserContext;
import org.itsnat.droid.impl.util.MimeUtil;
import static org.itsnat.droid.impl.dom.values.XMLDOMValues.TYPE_ANIM;
import static org.itsnat.droid.impl.dom.values.XMLDOMValues.TYPE_ANIMATOR;
import static org.itsnat.droid.impl.dom.values.XMLDOMValues.TYPE_DRAWABLE;
import static org.itsnat.droid.impl.dom.values.XMLDOMValues.TYPE_LAYOUT;
/**
* Created by jmarranz on 3/11/14.
*/
public abstract class ResourceDescDynamic extends ResourceDesc
{
protected final String resType;
protected final String extension; // xml, png...
protected final String valuesResourceName; // No nulo sólo en el caso de "values" tras el :
protected final boolean ninePatch;
protected final String mime;
protected final String location;
protected volatile ParsedResource parsedResource;
public ResourceDescDynamic(String resourceDescValue)
{
super(resourceDescValue);
// Ej. @assets:drawable/res/drawable/file.png Path: res/drawable/file.png
// Ej. @remote:drawable/res/drawable/file.png Remote Path: res/drawable/file.png
// @remote:drawable//res/drawable/file.png Remote Path: /res/drawable/file.png
// @remote:drawable/http://somehost/res/drawable/file.png Remote Path: http://somehost/res/drawable/file.png
// @remote:drawable/ItsNatDroidServletExample?itsnat_doc_name=test_droid_remote_drawable
// Ej. values: @assets:dimen/res/values/filename.xml:size
// @remote:dimen/res/values/filename.xml:size
int posType = resourceDescValue.indexOf(':');
int posPath = resourceDescValue.indexOf('/');
String resTypeTmp = resourceDescValue.substring(posType + 1,posPath); // Ej. "drawable"
this.resType = resTypeTmp;
String locationTmp;
if (XMLDOMValues.isResourceTypeValues(resType))
{
int valuesResourcePos = resourceDescValue.lastIndexOf(':'); // Esperamos el de por ej "...filename.xml:size" pero puede devolvernos el de "@assets:dimen..." lo que significa que no existe valuesResourceName lo cual es erróneo
if (valuesResourcePos > posType && isResourceNameValid(resourceDescValue,valuesResourcePos)) // Correcto, existe un segundo ":" para el valuesResourceName y es un nombre válido
{
locationTmp = resourceDescValue.substring(posPath + 1,valuesResourcePos); // incluye la extension
this.valuesResourceName = resourceDescValue.substring(valuesResourcePos + 1);
}
else // No hay selector ":selector"
{
if (TYPE_ANIM.equals(resType) || TYPE_ANIMATOR.equals(resType) || TYPE_DRAWABLE.equals(resType) || TYPE_LAYOUT.equals(resType))
{
// En el caso "drawable" podemos tener un acceso a un <drawable> en archivo XML en /res/values o bien directamente acceder al XML en /res/drawable
// este es el caso de acceso DIRECTO al XML del drawable
// Idem con <item name="..." type="layout">, type="anim" y type="animator"
locationTmp = resourceDescValue.substring(posPath + 1);
this.valuesResourceName = null;
}
else throw new ItsNatDroidException("Bad format of attribute value, expected \"values\" resource ended with \":resname\" : " + resourceDescValue);
}
}
else
{
locationTmp = resourceDescValue.substring(posPath + 1);
this.valuesResourceName = null;
}
// locationTmp = processLocationSuffixes(locationTmp,xmlDOMParserContext.getConfiguration(),xmlDOMParserContext.getDisplayMetrics());
this.location = locationTmp;
int posExt = this.location.lastIndexOf('.');
if (posExt != -1)
{
this.extension = this.location.substring(posExt + 1).toLowerCase(); // xml, png...
}
else
{
// Por ejemplo: @remote:drawable/ItsNatDroidServletExample?itsnat_doc_name=test_droid_remote_drawable
// Suponemos que se genera el XML por ej del drawable
this.extension = null;
}
if (extension != null)
{
// http://www.sitepoint.com/web-foundations/mime-types-complete-list/
String mime = MimeUtil.MIME_BY_EXT.get(extension);
if (mime == null)
throw new ItsNatDroidException("Unexpected extension: \"" + extension + "\" Remote resource: " + resourceDescValue);
this.mime = mime;
this.ninePatch = MimeUtil.MIME_PNG.equals(mime) && resourceDescValue.endsWith(".9.png");
}
else
{
this.mime = MimeUtil.MIME_XML;
this.ninePatch = false;
}
}
private boolean isResourceNameValid(String value,int valuesResourcePos)
{
String resName = value.substring(valuesResourcePos + 1);
// El nombre de un recurso ya sea un path o un <item name="resname"> o un <string name="resname> se convierte en una variable Java en la clase R.java
// (ejemplo public static final int prueba=0x7f090005; por lo que este es el criterio de validez
int len = resName.length();
for(int i = 0; i < len; i++)
{
char c = resName.charAt(0);
if (i == 0 && !Character.isJavaIdentifierStart(c))
return false;
else if (!Character.isJavaIdentifierPart(c))
return false;
}
return true;
}
public String getResourceType()
{
return resType;
}
public String getExtension()
{
return extension;
}
public String getValuesResourceName()
{
return valuesResourceName;
}
public boolean isNinePatch()
{
return ninePatch;
}
public String getLocation(XMLDOMParserContext xmlDOMParserContext)
{
String locationTmp = processLocationSuffixes(this.location,xmlDOMParserContext.getConfiguration(),xmlDOMParserContext.getDisplayMetrics());
return locationTmp;
}
public String getResourceMime()
{
return mime;
}
public ParsedResource getParsedResource()
{
// Es sólo llamado en el hilo UI pero setParsedResource se llama en multihilo
return parsedResource;
}
public void setParsedResource(ParsedResource parsedResource)
{
// Es llamado en multihilo en el caso de recurso remoto (por eso es volatile)
// No pasa nada porque se llame e inmediatamente después se cambie el valor, puede ocurrir que se esté procesando
// el mismo atributo a la vez por dos hilos, ten en cuenta que el template puede estar cacheado y reutilizado, pero no pasa nada
// porque el nuevo remoteResource NUNCA es null y es siempre el mismo recurso como mucho actualizado si ha cambiado
// en el servidor
this.parsedResource = parsedResource;
}
private String processLocationSuffixes(String location,Configuration configuration,DisplayMetrics displayMetrics)
{
// http://developer.android.com/guide/topics/resources/providing-resources.html (el orden de la tabla 2 es el orden de los sufijos en el caso de múltiples sufijos)
// http://developer.android.com/guide/topics/resources/localization.html
// Los filtros de ItsNat Droid tienen dos modos de declaración y por tanto de funcionamiento:
// 1) Caso con valor explícito: ej {lg-es}. Se detecta que hay valor especificado (el "es") y se reemplaza por el valor si se da la regla: "-es" o por nada ""
// 2) Caso sin valor explícito: ej {lg-}. Se detecta que NO hay valor especificado y se reemplaza por el valor actual del dispositivo: "-es"
String suffix = "}";
int posToSearchMore = 0;
{
// No soportamos MCC y MNC filtros, aportan muy poco valor
}
if (location.indexOf("{",posToSearchMore) == -1) // Todos los filtros empiezan de la misma manera, evitamos así buscar a lo tonto
return location;
{
// Soportamos la existencia de sufijo de lenguaje
// Ej {lg-es}
String prefix = "{lg-";
int posStart = location.indexOf(prefix,posToSearchMore);
if (posStart != -1)
{
int posEnd = location.indexOf(suffix, posStart);
if (posEnd == -1) throw new ItsNatDroidException("Unfinished prefix: " + prefix);
String currentLang = configuration.locale.getLanguage();
if (posEnd == posStart + prefix.length())
{
location = location.substring(0, posStart) + "-" + currentLang + location.substring(posEnd + 1);
}
else
{
String lang = location.substring(posStart + prefix.length(), posEnd);
if (currentLang.equals(lang))
{
location = location.substring(0, posStart) + "-" + lang + location.substring(posEnd + 1);
}
else
{
location = location.substring(0, posStart) + location.substring(posEnd + 1); // Quitamos el sufijo pues no se usa
}
}
posToSearchMore = posStart; // recuerda que se ha cambiado la cadena
}
}
if (location.indexOf("{",posToSearchMore) == -1) // Todos los filtros empiezan de la misma manera, evitamos así buscar a lo tonto
return location;
{
// Soportamos la existencia de sufijo de región
// Ej {rg-rES}
String prefix = "{rg-";
int posStart = location.indexOf(prefix,posToSearchMore);
if (posStart != -1)
{
int posEnd = location.indexOf(suffix, posStart);
if (posEnd == -1) throw new ItsNatDroidException("Unfinished prefix: " + prefix);
String currentRegion = configuration.locale.getCountry();
if (posEnd == posStart + prefix.length())
{
location = location.substring(0, posStart) + "-r" + currentRegion + location.substring(posEnd + 1);
}
else
{
String region = location.substring(posStart + prefix.length() + 1 /* el +1 es la r de rES */, posEnd);
if (currentRegion.equals(region))
{
location = location.substring(0, posStart) + "-r" + region + location.substring(posEnd + 1);
}
else
{
// Quitamos el sufijo pues no se usa
location = location.substring(0, posStart) + location.substring(posEnd + 1);
}
}
posToSearchMore = posStart; // recuerda que se ha cambiado la cadena
}
}
{
// Layout Direction es level 17 no lo soportamos todavía
}
if (location.indexOf("{",posToSearchMore) == -1) // Todos los filtros empiezan de la misma manera, evitamos así buscar a lo tonto
return location;
{
// Soportamos la existencia de sufijo smallestWidth
// Ej {sw-sw720dp}
String prefix = "{sw-";
int posStart = location.indexOf(prefix,posToSearchMore);
if (posStart != -1)
{
int posEnd = location.indexOf(suffix, posStart);
if (posEnd == -1) throw new ItsNatDroidException("Unfinished prefix: " + prefix);
int deviceSmallestScreenWidthDp = configuration.smallestScreenWidthDp;
if (posEnd == posStart + prefix.length())
{
location = location.substring(0, posStart) + "-sw" + deviceSmallestScreenWidthDp + "dp" + location.substring(posEnd + 1);
}
else
{
String smallestScreenWidthDpStr = location.substring(posStart + prefix.length() + 2, posEnd - 2); // El +2 es para quitar el "sw" y el -2 es para quitar el "dp" y que smallestScreenWidthDpStr sea un entero
int smallestScreenWidthDp;
try { smallestScreenWidthDp = Integer.parseInt(smallestScreenWidthDpStr); }
catch (Exception ex) { throw new ItsNatDroidException("Bad smallest width suffix: " + smallestScreenWidthDpStr); }
if (deviceSmallestScreenWidthDp >= smallestScreenWidthDp)
{
location = location.substring(0, posStart) + "-sw" + smallestScreenWidthDp + "dp" + location.substring(posEnd + 1);
}
else
{
// Quitamos el sufijo pues no se usa
location = location.substring(0, posStart) + location.substring(posEnd + 1);
}
}
posToSearchMore = posStart; // recuerda que se ha cambiado la cadena
}
}
if (location.indexOf("{",posToSearchMore) == -1) // Todos los filtros empiezan de la misma manera, evitamos así buscar a lo tonto
return location;
{
// Soportamos la existencia de sufijo screenWidthDp (available width)
// Ej {w-w720dp}
String prefix = "{w-";
int posStart = location.indexOf(prefix,posToSearchMore);
if (posStart != -1)
{
int posEnd = location.indexOf(suffix, posStart);
if (posEnd == -1) throw new ItsNatDroidException("Unfinished prefix: " + prefix);
int deviceScreenWidthDp = configuration.screenWidthDp;
if (posEnd == posStart + prefix.length())
{
location = location.substring(0, posStart) + "-w" + deviceScreenWidthDp + "dp" + location.substring(posEnd + 1);
}
else
{
String screenWidthDpStr = location.substring(posStart + prefix.length() + 1, posEnd - 2); // El +1 es para quitar el "w" y el -2 es para quitar el "dp" y que screenWidthDp sea un entero
int screenWidthDp;
try { screenWidthDp = Integer.parseInt(screenWidthDpStr); }
catch (Exception ex) { throw new ItsNatDroidException("Bad screen width dp suffix: " + screenWidthDpStr); }
if (deviceScreenWidthDp >= screenWidthDp)
{
location = location.substring(0, posStart) + "-w" + screenWidthDp + "dp" + location.substring(posEnd + 1);
}
else
{
location = location.substring(0, posStart) + location.substring(posEnd + 1); // Quitamos el sufijo pues no se usa
}
}
posToSearchMore = posStart; // recuerda que se ha cambiado la cadena
}
}
if (location.indexOf("{",posToSearchMore) == -1) // Todos los filtros empiezan de la misma manera, evitamos así buscar a lo tonto
return location;
{
// Soportamos la existencia de sufijo screenWidthDp (available width)
// Ej {h-h720dp}
String prefix = "{h-";
int posStart = location.indexOf(prefix,posToSearchMore);
if (posStart != -1)
{
int posEnd = location.indexOf(suffix, posStart);
if (posEnd == -1) throw new ItsNatDroidException("Unfinished prefix: " + prefix);
int deviceScreenHeightDp = configuration.screenHeightDp;
if (posEnd == posStart + prefix.length())
{
location = location.substring(0, posStart) + "-h" + deviceScreenHeightDp + "dp" + location.substring(posEnd + 1);
}
else
{
String screenHeightDpStr = location.substring(posStart + prefix.length() + 1, posEnd - 2); // El +1 es para quitar el "h" y el -2 es para quitar el "dp" y que screenWidthDp sea un entero
int screenHeightDp;
try { screenHeightDp = Integer.parseInt(screenHeightDpStr); }
catch (Exception ex) { throw new ItsNatDroidException("Bad screen height dp suffix: " + screenHeightDpStr); }
if (deviceScreenHeightDp >= screenHeightDp)
{
location = location.substring(0, posStart) + "-h" + screenHeightDp + "dp" + location.substring(posEnd + 1);
}
else
{
location = location.substring(0, posStart) + location.substring(posEnd + 1); // Quitamos el sufijo pues no se usa
}
}
posToSearchMore = posStart; // recuerda que se ha cambiado la cadena
}
}
if (location.indexOf("{",posToSearchMore) == -1) // Todos los filtros empiezan de la misma manera, evitamos así buscar a lo tonto
return location;
{
// Soportamos la existencia de sufijo screen size (screenLayout) http://developer.android.com/guide/practices/screens_support.html
// Ej {ss-xlarge}
String prefix = "{ss-";
int posStart = location.indexOf(prefix,posToSearchMore);
if (posStart != -1)
{
int posEnd = location.indexOf(suffix, posStart);
if (posEnd == -1) throw new ItsNatDroidException("Unfinished prefix: " + prefix);
int deviceScreenLayout = configuration.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;
if (posEnd == posStart + prefix.length())
{
String deviceScreenLayoutStr;
switch(deviceScreenLayout)
{
case Configuration.SCREENLAYOUT_SIZE_SMALL: deviceScreenLayoutStr = "small"; break;
case Configuration.SCREENLAYOUT_SIZE_NORMAL: deviceScreenLayoutStr = "normal"; break;
case Configuration.SCREENLAYOUT_SIZE_LARGE: deviceScreenLayoutStr = "large"; break;
case Configuration.SCREENLAYOUT_SIZE_XLARGE: deviceScreenLayoutStr = "xlarge"; break;
default: throw new ItsNatDroidException("Unexpected or unsupported screen size value: " + deviceScreenLayout);
}
location = location.substring(0, posStart) + "-" + deviceScreenLayoutStr + location.substring(posEnd + 1);
}
else
{
String screenLayoutStr = location.substring(posStart + prefix.length(), posEnd);
int screenLayout;
if ("small".equals(screenLayoutStr)) screenLayout = Configuration.SCREENLAYOUT_SIZE_SMALL;
else if ("normal".equals(screenLayoutStr)) screenLayout = Configuration.SCREENLAYOUT_SIZE_NORMAL;
else if ("large".equals(screenLayoutStr)) screenLayout = Configuration.SCREENLAYOUT_SIZE_LARGE;
else if ("xlarge".equals(screenLayoutStr)) screenLayout = Configuration.SCREENLAYOUT_SIZE_XLARGE;
else throw new ItsNatDroidException("Unexpected or unsupported prefix: " + screenLayoutStr);
if (deviceScreenLayout >= screenLayout)
{
location = location.substring(0, posStart) + "-" + screenLayoutStr + location.substring(posEnd + 1);
}
else
{
location = location.substring(0, posStart) + location.substring(posEnd + 1); // Quitamos el sufijo pues no se usa
}
}
posToSearchMore = posStart; // recuerda que se ha cambiado la cadena
}
}
if (location.indexOf("{",posToSearchMore) == -1) // Todos los filtros empiezan de la misma manera, evitamos así buscar a lo tonto
return location;
{
// Soportamos la existencia de sufijo screen aspect (screenLayout) http://developer.android.com/guide/practices/screens_support.html
// Ej {sa-long}
String prefix = "{sa-";
int posStart = location.indexOf(prefix,posToSearchMore);
if (posStart != -1)
{
int posEnd = location.indexOf(suffix, posStart);
if (posEnd == -1) throw new ItsNatDroidException("Unfinished prefix: " + prefix);
int deviceScreenLayout = configuration.screenLayout & Configuration.SCREENLAYOUT_LONG_MASK;
if (posEnd == posStart + prefix.length())
{
String deviceScreenLayoutStr;
switch(deviceScreenLayout)
{
case Configuration.SCREENLAYOUT_LONG_NO: deviceScreenLayoutStr = "notlong"; break;
case Configuration.SCREENLAYOUT_LONG_YES: deviceScreenLayoutStr = "long"; break;
default: throw new ItsNatDroidException("Unexpected or unsupported screen aspect value: " + deviceScreenLayout);
}
location = location.substring(0, posStart) + "-" + deviceScreenLayoutStr + location.substring(posEnd + 1);
}
else
{
String screenLayoutStr = location.substring(posStart + prefix.length(), posEnd);
int screenLayout;
if ("notlong".equals(screenLayoutStr)) screenLayout = Configuration.SCREENLAYOUT_LONG_NO;
else if ("long".equals(screenLayoutStr)) screenLayout = Configuration.SCREENLAYOUT_LONG_YES;
else throw new ItsNatDroidException("Unexpected or unsupported prefix: " + screenLayoutStr);
if (deviceScreenLayout >= screenLayout)
{
location = location.substring(0, posStart) + "-" + screenLayoutStr + location.substring(posEnd + 1);
}
else
{
// Quitamos el sufijo pues no se usa
location = location.substring(0, posStart) + location.substring(posEnd + 1);
}
}
posToSearchMore = posStart; // recuerda que se ha cambiado la cadena
}
}
{
// Round screen es level 23 no lo soportamos todavía
}
if (location.indexOf("{",posToSearchMore) == -1) // Todos los filtros empiezan de la misma manera, evitamos así buscar a lo tonto
return location;
{
// Soportamos la existencia de sufijo screen orientation (orientation)
// Ej {so-port}
String prefix = "{so-";
int posStart = location.indexOf(prefix,posToSearchMore);
if (posStart != -1)
{
int posEnd = location.indexOf(suffix, posStart);
if (posEnd == -1) throw new ItsNatDroidException("Unfinished prefix: " + prefix);
int deviceOrientation = configuration.orientation;
if (posEnd == posStart + prefix.length())
{
String deviceOrientationStr;
switch(deviceOrientation)
{
case Configuration.ORIENTATION_PORTRAIT: deviceOrientationStr = "port"; break;
case Configuration.ORIENTATION_LANDSCAPE: deviceOrientationStr = "land"; break;
default: throw new ItsNatDroidException("Unexpected or unsupported screen orientation value: " + deviceOrientation);
}
location = location.substring(0, posStart) + "-" + deviceOrientationStr + location.substring(posEnd + 1);
}
else
{
String orientationStr = location.substring(posStart + prefix.length(), posEnd);
int orientation;
if ("port".equals(orientationStr)) orientation = Configuration.ORIENTATION_PORTRAIT;
else if ("land".equals(orientationStr)) orientation = Configuration.ORIENTATION_LANDSCAPE;
else throw new ItsNatDroidException("Unexpected or unsupported prefix: " + orientationStr); // Existe un ORIENTATION_SQUARE declarado en level 15 pero la doc oficial dice que está deprecado en level 16 http://developer.android.com/reference/android/content/res/Configuration.html#ORIENTATION_SQUARE
if (deviceOrientation == orientation)
{
location = location.substring(0, posStart) + "-" + orientationStr + location.substring(posEnd + 1);
}
else
{
location = location.substring(0, posStart) + location.substring(posEnd + 1); // Quitamos el sufijo pues no se usa
}
}
posToSearchMore = posStart; // recuerda que se ha cambiado la cadena
}
}
if (location.indexOf("{",posToSearchMore) == -1) // Todos los filtros empiezan de la misma manera, evitamos así buscar a lo tonto
return location;
{
// Soportamos la existencia de sufijo UI Mode Type (uiMode)
// Ej {uimt-port}
String prefix = "{uimt-";
int posStart = location.indexOf(prefix,posToSearchMore);
if (posStart != -1)
{
int posEnd = location.indexOf(suffix, posStart);
if (posEnd == -1) throw new ItsNatDroidException("Unfinished prefix: " + prefix);
int deviceUiModeType = configuration.uiMode & Configuration.UI_MODE_TYPE_MASK;
if (posEnd == posStart + prefix.length())
{
String deviceUiModeTypeStr;
switch(deviceUiModeType)
{
case Configuration.UI_MODE_TYPE_NORMAL: deviceUiModeTypeStr = "normal"; break;
case Configuration.UI_MODE_TYPE_DESK: deviceUiModeTypeStr = "desk"; break;
case Configuration.UI_MODE_TYPE_CAR: deviceUiModeTypeStr = "car"; break;
case Configuration.UI_MODE_TYPE_TELEVISION: deviceUiModeTypeStr = "television"; break;
default: throw new ItsNatDroidException("Unexpected or unsupported UI Mode Type value: " + deviceUiModeType);
}
location = location.substring(0, posStart) + "-" + deviceUiModeTypeStr + location.substring(posEnd + 1);
}
else
{
String uimtStr = location.substring(posStart + prefix.length(), posEnd);
int uimt;
if ("normal".equals(uimtStr)) uimt = Configuration.UI_MODE_TYPE_NORMAL; // Los móviles, tabletas etc son NORMAL
else if ("desk".equals(uimtStr)) uimt = Configuration.UI_MODE_TYPE_DESK;
else if ("car".equals(uimtStr)) uimt = Configuration.UI_MODE_TYPE_CAR;
else if ("television".equals(uimtStr)) uimt = Configuration.UI_MODE_TYPE_TELEVISION;
else throw new ItsNatDroidException("Unexpected or unsupported prefix: " + uimtStr); // En versiones superiores hay más (appliance, watch)
if (deviceUiModeType == uimt)
{
location = location.substring(0, posStart) + "-" + uimtStr + location.substring(posEnd + 1);
}
else
{
location = location.substring(0, posStart) + location.substring(posEnd + 1); // Quitamos el sufijo pues no se usa
}
}
posToSearchMore = posStart; // recuerda que se ha cambiado la cadena
}
}
if (location.indexOf("{",posToSearchMore) == -1) // Todos los filtros empiezan de la misma manera, evitamos así buscar a lo tonto
return location;
{
// Soportamos la existencia de sufijo UI Mode Night (uiMode)
// Ej {uimn-notnight}
String prefix = "{uimn-";
int posStart = location.indexOf(prefix,posToSearchMore);
if (posStart != -1)
{
int posEnd = location.indexOf(suffix, posStart);
if (posEnd == -1) throw new ItsNatDroidException("Unfinished prefix: " + prefix);
int deviceUiModeNight = configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
if (posEnd == posStart + prefix.length())
{
String deviceUimnStr;
switch(deviceUiModeNight)
{
case Configuration.UI_MODE_NIGHT_NO: deviceUimnStr = "notnight"; break;
case Configuration.UI_MODE_NIGHT_YES: deviceUimnStr = "night"; break;
default: throw new ItsNatDroidException("Unexpected or unsupported UI Mode Night value: " + deviceUiModeNight);
}
location = location.substring(0, posStart) + "-" + deviceUimnStr + location.substring(posEnd + 1);
}
else
{
String uimnStr = location.substring(posStart + prefix.length(), posEnd);
int uimn;
if ("notnight".equals(uimnStr)) uimn = Configuration.UI_MODE_NIGHT_NO;
else if ("night".equals(uimnStr)) uimn = Configuration.UI_MODE_NIGHT_YES;
else throw new ItsNatDroidException("Unexpected or unsupported prefix: " + uimnStr);
if (deviceUiModeNight == uimn)
{
location = location.substring(0, posStart) + "-" + uimnStr + location.substring(posEnd + 1);
}
else
{
location = location.substring(0, posStart) + location.substring(posEnd + 1); // Quitamos el sufijo pues no se usa
}
}
posToSearchMore = posStart; // recuerda que se ha cambiado la cadena
}
}
if (location.indexOf("{",posToSearchMore) == -1) // Todos los filtros empiezan de la misma manera, evitamos así buscar a lo tonto
return location;
{
// Soportamos la existencia de sufijo Screen pixel density (dpi)
// Ej {spd-xhdpi}
String prefix = "{spd-";
int posStart = location.indexOf(prefix,posToSearchMore);
if (posStart != -1)
{
int posEnd = location.indexOf(suffix, posStart);
if (posEnd == -1) throw new ItsNatDroidException("Unfinished prefix: " + prefix);
int deviceDensityDpi = displayMetrics.densityDpi;
if (posEnd == posStart + prefix.length())
{
String deviceDensityDpiStr;
if (deviceDensityDpi == 1)
deviceDensityDpiStr = "nodpi";
else if (deviceDensityDpi == 213)
deviceDensityDpiStr = "tvdpi"; // No tengo claro si hacer rango con este caso, en teoría sólo se aplica si es una televisión
else if (deviceDensityDpi < 120)
deviceDensityDpiStr = "ldpi";
else if (deviceDensityDpi >= 120 && deviceDensityDpi < 120 + (160 - 120)/2)
deviceDensityDpiStr = "ldpi";
else if (deviceDensityDpi >= 160 && deviceDensityDpi < 160 + (240 - 160)/2)
deviceDensityDpiStr = "mdpi";
else if (deviceDensityDpi >= 240 && deviceDensityDpi < 240 + (320 - 240)/2)
deviceDensityDpiStr = "hdpi";
else if (deviceDensityDpi >= 320 && deviceDensityDpi < 320 + (480 - 320)/2)
deviceDensityDpiStr = "xhdpi";
else if (deviceDensityDpi >= 480 && deviceDensityDpi < 480 + (640 - 480)/2)
deviceDensityDpiStr = "xxhdpi";
else // deviceDensityDpi >= 480 + (640 - 480)/2
deviceDensityDpiStr = "xxxhdpi";
location = location.substring(0, posStart) + "-" + deviceDensityDpiStr + location.substring(posEnd + 1);
}
else
{
String densityDpiStr = location.substring(posStart + prefix.length(), posEnd);
int densityDpi;
if ("ldpi".equals(densityDpiStr)) densityDpi = 120; // low-density
else if ("mdpi".equals(densityDpiStr)) densityDpi = 160; // medium-density
else if ("hdpi".equals(densityDpiStr)) densityDpi = 240; // high-density
else if ("xhdpi".equals(densityDpiStr)) densityDpi = 320; // extra-high-density
else if ("xxhdpi".equals(densityDpiStr)) densityDpi = 480; // extra-extra-high-density
else if ("xxxhdpi".equals(densityDpiStr)) densityDpi = 640; // extra-extra-extra-high-density
else if ("nodpi".equals(densityDpiStr)) densityDpi = 1; // all densities. Por poner algo (no se que poner)
else if ("tvdpi".equals(densityDpiStr)) densityDpi = 213; // screens somewhere between mdpi and hdpi
else throw new ItsNatDroidException("Unexpected or unsupported prefix: " + densityDpiStr);
if (deviceDensityDpi >= densityDpi)
{
location = location.substring(0, posStart) + "-" + densityDpiStr + location.substring(posEnd + 1);
}
else
{
location = location.substring(0, posStart) + location.substring(posEnd + 1); // Quitamos el sufijo pues no se usa
}
}
posToSearchMore = posStart; // recuerda que se ha cambiado la cadena
}
}
if (location.indexOf("{",posToSearchMore) == -1) // Todos los filtros empiezan de la misma manera, evitamos así buscar a lo tonto
return location;
{
// Soportamos la existencia de sufijo Touchscreen type (touchscreen)
// Ej {tst-finger}
String prefix = "{tst-";
int posStart = location.indexOf(prefix,posToSearchMore);
if (posStart != -1)
{
int posEnd = location.indexOf(suffix, posStart);
if (posEnd == -1) throw new ItsNatDroidException("Unfinished prefix: " + prefix);
int deviceTouchscreen = configuration.touchscreen;
if (posEnd == posStart + prefix.length())
{
String deviceTouchscreenStr;
switch(deviceTouchscreen)
{
case Configuration.TOUCHSCREEN_NOTOUCH: deviceTouchscreenStr = "notouch"; break;
case Configuration.TOUCHSCREEN_FINGER: deviceTouchscreenStr = "finger"; break;
default: throw new ItsNatDroidException("Unexpected or unsupported Touchscreen type value: " + deviceTouchscreen);
}
location = location.substring(0, posStart) + "-" + deviceTouchscreenStr + location.substring(posEnd + 1);
}
else
{
String touchscreenStr = location.substring(posStart + prefix.length(), posEnd);
int touchscreen;
if ("notouch".equals(touchscreenStr)) touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
else if ("finger".equals(touchscreenStr)) touchscreen = Configuration.TOUCHSCREEN_FINGER;
else throw new ItsNatDroidException("Unexpected or unsupported prefix: " + touchscreenStr); // No está documentado el literal de "stylus"
if (deviceTouchscreen == touchscreen)
{
location = location.substring(0, posStart) + "-" + touchscreenStr + location.substring(posEnd + 1);
}
else
{
location = location.substring(0, posStart) + location.substring(posEnd + 1); // Quitamos el sufijo pues no se usa
}
}
posToSearchMore = posStart; // recuerda que se ha cambiado la cadena
}
}
{
// No soportamos el sufijo sufijo Keyboard availability, no lo entiendo bien (como implementarlo) y no aporta gran cosa hoy día
}
if (location.indexOf("{",posToSearchMore) == -1) // Todos los filtros empiezan de la misma manera, evitamos así buscar a lo tonto
return location;
{
// Soportamos la existencia de sufijo Primary text input method (keyboard)
// Ej {ptim-qwerty}
String prefix = "{ptim-";
int posStart = location.indexOf(prefix,posToSearchMore);
if (posStart != -1)
{
int posEnd = location.indexOf(suffix, posStart);
if (posEnd == -1) throw new ItsNatDroidException("Unfinished prefix: " + prefix);
int deviceKeyboard = configuration.keyboard;
if (posEnd == posStart + prefix.length())
{
String deviceKeyboardStr;
switch(deviceKeyboard)
{
case Configuration.KEYBOARD_NOKEYS: deviceKeyboardStr = "nokeys"; break;
case Configuration.KEYBOARD_QWERTY: deviceKeyboardStr = "qwerty"; break;
case Configuration.KEYBOARD_12KEY: deviceKeyboardStr = "12key"; break;
default: throw new ItsNatDroidException("Unexpected or unsupported Primary text input method value: " + deviceKeyboard);
}
location = location.substring(0, posStart) + "-" + deviceKeyboardStr + location.substring(posEnd + 1);
}
else
{
String keyboardStr = location.substring(posStart + prefix.length(), posEnd);
int keyboard;
if ("nokeys".equals(keyboardStr)) keyboard = Configuration.KEYBOARD_NOKEYS;
else if ("qwerty".equals(keyboardStr)) keyboard = Configuration.KEYBOARD_QWERTY;
else if ("12key".equals(keyboardStr)) keyboard = Configuration.KEYBOARD_12KEY;
else throw new ItsNatDroidException("Unexpected or unsupported prefix: " + keyboardStr);
if (deviceKeyboard == keyboard)
{
location = location.substring(0, posStart) + "-" + keyboardStr + location.substring(posEnd + 1);
}
else
{
location = location.substring(0, posStart) + location.substring(posEnd + 1); // Quitamos el sufijo pues no se usa
}
}
posToSearchMore = posStart; // recuerda que se ha cambiado la cadena
}
}
{
// No soportamos Navigation key availability (navigationHidden) porque parece que es algo obsoleto en pantallas táctiles, pero lo más importante
// es que no está implementado al menos en los emuladores desde Level 15, como si estuviera deprecated aunque no lo pone en ningún sitio o no aplica en disp táctiles,
// pues cuando añades -navexposed o -navhidden al folder de turno en ambos casos los recursos allí son ignorados.
// http://developer.android.com/reference/android/content/res/Configuration.html#navigationHidden
}
if (location.indexOf("{",posToSearchMore) == -1) // Todos los filtros empiezan de la misma manera, evitamos así buscar a lo tonto
return location;
{
// Soportamos la existencia de sufijo Primary non-touch navigation method (navigation)
// Ej {pntn-nonav}
String prefix = "{pntn-";
int posStart = location.indexOf(prefix,posToSearchMore);
if (posStart != -1)
{
int posEnd = location.indexOf(suffix, posStart);
if (posEnd == -1) throw new ItsNatDroidException("Unfinished prefix: " + prefix);
int deviceNavigation = configuration.navigation;
if (posEnd == posStart + prefix.length())
{
String deviceNavigationStr;
switch(deviceNavigation)
{
case Configuration.NAVIGATION_NONAV: deviceNavigationStr = "nonav"; break;
case Configuration.NAVIGATION_DPAD: deviceNavigationStr = "dpad"; break;
case Configuration.NAVIGATION_TRACKBALL: deviceNavigationStr = "trackball"; break;
case Configuration.NAVIGATION_WHEEL: deviceNavigationStr = "wheel"; break;
default: throw new ItsNatDroidException("Unexpected or unsupported Primary non-touch navigation method value: " + deviceNavigation);
}
location = location.substring(0, posStart) + "-" + deviceNavigationStr + location.substring(posEnd + 1);
}
else
{
String navigationStr = location.substring(posStart + prefix.length(), posEnd);
int navigation;
if ("nonav".equals(navigationStr)) navigation = Configuration.NAVIGATION_NONAV;
else if ("dpad".equals(navigationStr)) navigation = Configuration.NAVIGATION_DPAD;
else if ("trackball".equals(navigationStr)) navigation = Configuration.NAVIGATION_TRACKBALL;
else if ("wheel".equals(navigationStr)) navigation = Configuration.NAVIGATION_WHEEL;
else throw new ItsNatDroidException("Unexpected or unsupported prefix: " + navigationStr);
if (deviceNavigation == navigation)
{
location = location.substring(0, posStart) + "-" + navigationStr + location.substring(posEnd + 1);
}
else
{
location = location.substring(0, posStart) + location.substring(posEnd + 1); // Quitamos el sufijo pues no se usa
}
}
posToSearchMore = posStart; // recuerda que se ha cambiado la cadena
}
}
if (location.indexOf("{",posToSearchMore) == -1) // Todos los filtros empiezan de la misma manera, evitamos así buscar a lo tonto
return location;
{
// Soportamos la existencia de sufijo de versión de la plataforma
// Ej {v-v21}
String prefix = "{v-";
int posStart = location.indexOf(prefix,posToSearchMore);
if (posStart != -1)
{
int posEnd = location.indexOf(suffix, posStart);
if (posEnd == -1) throw new ItsNatDroidException("Unfinished prefix: " + prefix);
int deviceSDKVersion = Build.VERSION.SDK_INT;
if (posEnd == posStart + prefix.length())
{
location = location.substring(0, posStart) + "-v" + deviceSDKVersion + location.substring(posEnd + 1);
}
else
{
String versionStr = location.substring(posStart + prefix.length() + 1 /* el +1 es para quitar el v */, posEnd);
int version;
try { version = Integer.parseInt(versionStr); }
catch (Exception ex) { throw new ItsNatDroidException("Bad platform version suffix: " + versionStr); }
if (deviceSDKVersion >= version)
{
location = location.substring(0, posStart) + "-v" + version + location.substring(posEnd + 1);
}
else
{
location = location.substring(0, posStart) + location.substring(posEnd + 1); // Quitamos el sufijo pues no se usa
}
}
posToSearchMore = posStart; // recuerda que se ha cambiado la cadena
}
}
return location;
}
}