package org.holoeverywhere.drawable; import android.content.Context; import android.content.res.Resources; import android.content.res.Resources.NotFoundException; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.graphics.BitmapFactory; import android.graphics.drawable.Drawable; import android.support.v4.util.LongSparseArray; import android.support.v4.view.ViewCompat; import android.util.AttributeSet; 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; public final class DrawableCompat { private static final Map<String, Class<? extends Drawable>> CLASS_MAP = new HashMap<String, Class<? extends Drawable>>(); private static final LongSparseArray<WeakReference<Drawable.ConstantState>> sDrawableCache = new LongSparseArray<WeakReference<Drawable.ConstantState>>(); static { registerDrawable(RotateDrawable.class, "rotate"); registerDrawable(LayerDrawable.class, "layer-list"); registerDrawable(StateListDrawable.class, "selector"); registerDrawable(ColorDrawable.class, "color"); } private DrawableCompat() { } public static 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 static void unregisterDrawable(String name) { CLASS_MAP.remove(name); } public static Drawable createFromPath(String pathName) { return Drawable.createFromPath(pathName); } public static Drawable createFromResourceStream(Resources res, TypedValue value, InputStream is, String srcName) { return createFromResourceStream(res, value, is, srcName, null); } public static Drawable createFromResourceStream(Resources res, TypedValue value, InputStream is, String srcName, BitmapFactory.Options opts) { return Drawable.createFromResourceStream(res, value, is, srcName, opts); } public static Drawable createFromStream(InputStream is, String srcName) { return createFromResourceStream(null, null, is, srcName, null); } public static Drawable createFromXml(Resources r, XmlPullParser parser) throws XmlPullParserException, IOException { AttributeSet attrs = Xml.asAttributeSet(parser); int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { } if (type != XmlPullParser.START_TAG) { throw new XmlPullParserException("No start tag found"); } Drawable drawable = createFromXmlInner(r, parser, attrs); if (drawable == null) { throw new RuntimeException("Unknown initial tag: " + parser.getName()); } return drawable; } public static Drawable createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs) 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) { return Drawable.createFromXmlInner(r, parser, attrs); } drawable.inflate(r, parser, attrs); return drawable; } private static Drawable getCachedDrawable(long key, Resources res) { WeakReference<Drawable.ConstantState> wr = sDrawableCache.get(key); if (wr != null) { Drawable.ConstantState entry = wr.get(); if (entry != null) { return entry.newDrawable(res); } else { sDrawableCache.delete(key); } } return null; } public static Drawable getDrawable(Resources res, int resid) { TypedValue value = new TypedValue(); res.getValue(resid, value, true); return loadDrawable(res, value); } public static Drawable getDrawable(TypedArray array, int index) { TypedValue value = new TypedValue(); array.getValue(index, value); return loadDrawable(array.getResources(), value); } public static Drawable loadDrawable(Resources res, TypedValue value) throws NotFoundException { if (value == null || value.resourceId <= 0) { return null; } final long key = (long) value.assetCookie << 32 | value.data; Drawable dr = getCachedDrawable(key, res); if (dr != null) { return dr; } Drawable.ConstantState cs = null; if (value.string == null) { throw new NotFoundException("Resource is not a Drawable (color or path): " + value); } String file = value.string.toString(); if (file.endsWith(".xml")) { try { XmlResourceParser rp = res.getAssets().openXmlResourceParser(value.assetCookie, file); dr = DrawableCompat.createFromXml(res, rp); rp.close(); } catch (Exception e) { Log.w(DrawableCompat.class.getSimpleName(), "Failed to load drawable resource, using a fallback...", e); return res.getDrawable(value.resourceId); } } else { try { InputStream is = res.getAssets().openNonAssetFd(value.assetCookie, file) .createInputStream(); dr = DrawableCompat.createFromResourceStream(res, value, is, file, null); is.close(); } catch (Exception e) { Log.w(DrawableCompat.class.getSimpleName(), "Failed to load drawable resource, using a fallback...", e); return res.getDrawable(value.resourceId); } } if (dr != null) { dr.setChangingConfigurations(value.changingConfigurations); cs = dr.getConstantState(); if (cs != null) { sDrawableCache.put(key, new WeakReference<Drawable.ConstantState>(cs)); } } return dr; } public static interface StateStub { public boolean isActivated(); public void setActivated(boolean activated); } public static interface IStateOverlay extends StateStub { public void refreshDrawableState(); public void invalidate(); public int[] superOnCreateDrawableState(int extraSpace); } public static class StateOverlay implements StateStub { private static final int FLAG_ACTIVATED = 1 << 0; private final IStateOverlay mOverlayInterface; private int mFlags; public StateOverlay(IStateOverlay overlayInterface) { mOverlayInterface = overlayInterface; } public StateOverlay(IStateOverlay overlayInterface, Context context, AttributeSet attrs, int defStyle) { this(overlayInterface); init(context, attrs, defStyle); } private void setFlag(int flag, boolean value) { mFlags = (mFlags & ~flag) | (value ? flag : 0); } private boolean getFlag(int flag) { return (mFlags & flag) == flag; } @Override public boolean isActivated() { return getFlag(FLAG_ACTIVATED); } @Override public void setActivated(boolean activated) { if (isActivated() != activated) { setFlag(FLAG_ACTIVATED, activated); mOverlayInterface.invalidate(); mOverlayInterface.refreshDrawableState(); } } public void init(Context context, AttributeSet attrs, int defStyle) { TypedArray a = context.obtainStyledAttributes(attrs, new int[]{ android.R.attr.state_activated }, defStyle, 0); setActivated(a.getBoolean(0, false)); a.recycle(); } public int[] onCreateDrawableState(int extraSpace) { extraSpace += 1; int[] state = mOverlayInterface.superOnCreateDrawableState(extraSpace); state = ViewCompat.mergeDrawableStates(state, new int[]{ mOverlayInterface.isActivated() ? android.R.attr.state_activated : -android.R.attr.state_activated }); return state; } } }