package carbon;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.BitmapFactory;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.v4.util.LongSparseArray;
import android.support.v7.content.res.AppCompatResources;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
import carbon.drawable.VectorDrawable;
import carbon.drawable.ripple.LollipopDrawable;
import carbon.drawable.ripple.RippleDrawableICS;
public class CarbonResources extends Resources {
private final Object mAccessLock = new Object();
private final Map<String, Class<? extends Drawable>> CLASS_MAP = new HashMap<>();
private final LongSparseArray<WeakReference<Drawable.ConstantState>> sDrawableCache = new LongSparseArray<>();
private final LongSparseArray<WeakReference<Drawable.ConstantState>> sColorDrawableCache = new LongSparseArray<>();
private final IDrawable IMPL;
private final Context context;
/**
* Create a new Resources object on top of an existing set of assets in an
* AssetManager.
*
* @param assets Previously created AssetManager.
* @param metrics Current display metrics to consider when selecting/computing resource values.
* @param config Desired device configuration to consider when
*/
public CarbonResources(Context context, AssetManager assets, DisplayMetrics metrics, Configuration config) {
super(assets, metrics, config);
this.context = context;
registerDrawable(RippleDrawableICS.class, "ripple");
if (Carbon.IS_LOLLIPOP) {
IMPL = new LollipopDrawableImpl();
} else {
IMPL = new BaseDrawableImpl();
}
}
public void registerDrawable(Class<? extends Drawable> clazz, String name) {
if (name == null || clazz == null) {
throw new NullPointerException("Class: " + clazz + ". Name: " + name);
}
CLASS_MAP.put(name, clazz);
}
public void unregisterDrawable(String name) {
CLASS_MAP.remove(name);
}
/**
* Applies the specified theme to this Drawable and its children.
*/
public void applyTheme(Drawable d, Resources.Theme t) {
IMPL.applyTheme(d, t);
}
public boolean canApplyTheme(Drawable d) {
return IMPL.canApplyTheme(d);
}
/**
* Create a drawable from an inputstream
*/
public Drawable createFromStream(InputStream is, String srcName) {
return createFromResourceStream(null, is, srcName);
}
/**
* Create a drawable from an inputstream, using the given resources and
* value to determine density information.
*/
public Drawable createFromResourceStream(TypedValue value, InputStream is, String srcName) {
return createFromResourceStream(value, is, srcName, null);
}
/**
* Create a drawable from an inputstream, using the given resources and
* value to determine density information.
*/
public Drawable createFromResourceStream(TypedValue value, InputStream is, String srcName, BitmapFactory.Options opts) {
return Drawable.createFromResourceStream(this, value, is, srcName, opts);
}
/**
* Create a drawable from an XML document. For more information on how to
* create resources in XML, see
* <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.
*/
public Drawable createFromXml(XmlPullParser parser) throws XmlPullParserException, IOException {
return createFromXml(parser, null);
}
/**
* Create a drawable from an XML document using an optional {@link Resources.Theme}.
* For more information on how to create resources in XML, see
* <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.
*/
public Drawable createFromXml(XmlPullParser parser, Resources.Theme theme) throws XmlPullParserException, IOException {
AttributeSet attrs = Xml.asAttributeSet(parser);
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) {
// Empty loop
}
if (type != XmlPullParser.START_TAG) {
throw new XmlPullParserException("No start tag found");
}
Drawable drawable = createFromXmlInner(parser, attrs, theme);
if (drawable == null) {
throw new RuntimeException("Unknown initial tag: " + parser.getName());
}
return drawable;
}
/**
* Create a drawable from inside an XML document using an optional
* {@link Resources.Theme}. Called on a parser positioned at a tag in an XML
* document, tries to create a Drawable from that tag. Returns {@code null}
* if the tag is not a valid drawable.
*/
public Drawable createFromXmlInner(XmlPullParser parser, AttributeSet attrs, Resources.Theme theme) throws XmlPullParserException, IOException {
Drawable drawable = null;
final String name = parser.getName();
try {
Class<? extends Drawable> clazz = CLASS_MAP.get(name);
if (clazz != null) {
drawable = clazz.newInstance();
} else if (name.indexOf('.') > 0) {
drawable = (Drawable) Class.forName(name).newInstance();
}
} catch (Exception e) {
throw new XmlPullParserException("Error while inflating drawable resource", parser, e);
}
if (drawable == null) {
if (Carbon.IS_LOLLIPOP) {
return Drawable.createFromXmlInner(this, parser, attrs, theme);
} else {
return Drawable.createFromXmlInner(this, parser, attrs);
}
}
IMPL.inflate(drawable, this, parser, attrs, theme);
return drawable;
}
private Drawable getCachedDrawable(LongSparseArray<WeakReference<Drawable.ConstantState>> cache, long key) {
synchronized (mAccessLock) {
WeakReference<Drawable.ConstantState> wr = cache.get(key);
if (wr != null) {
Drawable.ConstantState entry = wr.get();
if (entry != null) {
return entry.newDrawable(this);
} else {
cache.delete(key);
}
}
}
return null;
}
public Drawable getDrawable(int resId, Resources.Theme theme) {
if (resId != 0 && getResourceTypeName(resId).equals("raw")) {
return new VectorDrawable(this, resId);
} else {
TypedValue value = new TypedValue();
getValue(resId, value, true);
return loadDrawable(value, theme);
}
}
public Drawable getDrawable(int resId) {
if (resId != 0 && getResourceTypeName(resId).equals("raw")) {
return new VectorDrawable(this, resId);
} else {
TypedValue value = new TypedValue();
getValue(resId, value, true);
return loadDrawable(value, null);
}
}
public Drawable getDrawable(TypedArray array, int index, Resources.Theme theme) {
TypedValue value = new TypedValue();
array.getValue(index, value);
return loadDrawable(value, theme);
}
public Drawable getDrawable(TypedArray array, int index) {
return getDrawable(array, index, null);
}
public Drawable loadDrawable(TypedValue value, Resources.Theme theme) throws Resources.NotFoundException {
if (value == null || value.resourceId == 0) {
return null;
}
final boolean isColorDrawable;
final LongSparseArray<WeakReference<Drawable.ConstantState>> cache;
final long key;
if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
isColorDrawable = true;
cache = sColorDrawableCache;
key = value.data;
} else {
isColorDrawable = false;
cache = sDrawableCache;
key = (long) value.assetCookie << 32 | value.data;
}
Drawable dr = getCachedDrawable(cache, key);
if (dr != null) {
return dr;
}
Drawable.ConstantState cs = null;
//TODO hm do i need implement preloading of drawables?
if (cs != null) {
final Drawable cloneDr = cs.newDrawable(this);
if (theme != null) {
dr = cloneDr.mutate();
applyTheme(dr, theme);
} else {
dr = cloneDr;
}
} else if (isColorDrawable) {
dr = new ColorDrawable(value.data);
} else {
dr = loadDrawableForCookie(value, value.resourceId, theme);
}
if (dr != null) {
dr.setChangingConfigurations(value.changingConfigurations);
cacheDrawable(value, theme, isColorDrawable, key, dr, cache);
}
return dr;
}
private void cacheDrawable(TypedValue value, Resources.Theme theme, boolean isColorDrawable, long key, Drawable drawable, LongSparseArray<WeakReference<Drawable.ConstantState>> caches) {
Drawable.ConstantState cs = drawable.getConstantState();
if (cs == null) {
return;
}
synchronized (mAccessLock) {
caches.put(key, new WeakReference<>(cs));
}
}
private Drawable loadDrawableForCookie(TypedValue value, int id, Resources.Theme theme) {
if (value.string == null)
throw new Resources.NotFoundException("Resource \"" + getResourceName(id) + "\" (" + Integer.toHexString(id) + ") is not a Drawable (color or path): " + value);
String file = value.string.toString();
Drawable dr = null;
if (file.endsWith(".xml")) {
try {
XmlResourceParser rp = getAssets().openXmlResourceParser(value.assetCookie, file);
dr = createFromXml(rp, theme);
rp.close();
} catch (Exception e) {
try {
dr = AppCompatResources.getDrawable(context, id);
} catch (Exception e2) {
Log.w(CarbonResources.class.getSimpleName(), "Failed to load drawable resource", e);
}
}
} else {
try {
InputStream is = getAssets().openNonAssetFd(value.assetCookie, file).createInputStream();
dr = createFromResourceStream(value, is, file, null);
is.close();
} catch (Exception e) {
try {
dr = AppCompatResources.getDrawable(context, id);
} catch (Exception e2) {
Log.w(CarbonResources.class.getSimpleName(), "Failed to load drawable resource", e);
}
}
}
return dr;
}
/**
* Create a drawable from file path name.
*/
public Drawable createFromPath(String pathName) {
return Drawable.createFromPath(pathName);
}
private interface IDrawable {
void applyTheme(Drawable drawable, Resources.Theme t);
boolean canApplyTheme(Drawable drawable);
void inflate(Drawable drawable, Resources r, XmlPullParser parser, AttributeSet attrs, Resources.Theme theme) throws XmlPullParserException, IOException;
}
static class BaseDrawableImpl implements IDrawable {
@Override
public void applyTheme(Drawable drawable, Resources.Theme t) {
if (drawable instanceof LollipopDrawable) {
((LollipopDrawable) drawable).applyTheme(t);
}
}
@Override
public boolean canApplyTheme(Drawable drawable) {
return drawable instanceof LollipopDrawable && ((LollipopDrawable) drawable).canApplyTheme();
}
@Override
public void inflate(Drawable drawable, Resources r, XmlPullParser parser, AttributeSet attrs, Resources.Theme theme) throws XmlPullParserException, IOException {
if (drawable instanceof LollipopDrawable) {
((LollipopDrawable) drawable).inflate(r, parser, attrs, theme);
return;
}
drawable.inflate(r, parser, attrs);
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
static class LollipopDrawableImpl extends BaseDrawableImpl {
@Override
public void applyTheme(Drawable drawable, Resources.Theme t) {
drawable.applyTheme(t);
}
@Override
public boolean canApplyTheme(Drawable drawable) {
return drawable.canApplyTheme();
}
@Override
public void inflate(Drawable drawable, Resources r, XmlPullParser parser, AttributeSet attrs, Resources.Theme theme) throws XmlPullParserException, IOException {
drawable.inflate(r, parser, attrs, theme);
}
}
@Nullable
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public Drawable getDrawableForDensity(int resId, int density, Theme theme) {
if (resId != 0 && getResourceTypeName(resId).equals("raw")) {
return new VectorDrawable(this, resId);
} else {
return super.getDrawableForDensity(resId, density, theme);
}
}
@Nullable
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public Drawable getDrawableForDensity(int resId, int density) throws NotFoundException {
if (resId != 0 && getResourceTypeName(resId).equals("raw")) {
return new VectorDrawable(this, resId);
} else {
return super.getDrawableForDensity(resId, density);
}
}
}