public interface ISkinHandler {
    void collect(int mode, View view, Context context, AttributeSet attrs);
    void onSkinChanged(int skin, View view);
} public class OwlHandlerManager {
    private static HashMap<Class,ISkinHandler> sHandlers = new HashMap<>();
    private static HashMap<Class,Class<? extends ISkinHandler>> sHandlerTable = new HashMap<>();

    // TODO: 15/11/5 impl it later.
    private static Class<ISkinHandler> generateHandler() {
        return null;
    }

    public static void registerView(Class<? extends View> clz){
        sHandlerTable.put(clz, generateHandler());
    }

    public static void registerHandler(Class key, Class<? extends ISkinHandler> clz){
        sHandlerTable.put(key,clz);
    }

    public static void registerHandler(Class<? extends ISkinHandler> clz){
        OwlHandle owlHandle = clz.getAnnotation(OwlHandle.class);
        if ( owlHandle != null ){
            Class<? extends View>[] keys = owlHandle.value();
            for ( Class<? extends View> key : keys )
                registerHandler(key,clz);
        }
    }

    public static ISkinHandler queryHandler(Class clz) {
        Class<? extends ISkinHandler> handlerClz = sHandlerTable.get(clz);
        // if handlerClz == null
        // look for superclass's handlerClz
        // it will be end when meet View.class
        while( handlerClz == null ){
            clz = clz.getSuperclass();
            handlerClz = sHandlerTable.get(clz);
        }
        // query handler now
        ISkinHandler skinHandler = sHandlers.get(handlerClz);
        if ( skinHandler == null ) {
            skinHandler = newInstanceSafely(handlerClz);
            sHandlers.put(handlerClz, skinHandler);
        }
        return skinHandler;
    }

} public class Factory4InjectedInflater {
    public static LayoutInflater newInstance(LayoutInflater original, Context newContext) {
        final int version = Build.VERSION.SDK_INT;
        if (version >= 11) {
            return new InjectedInflaterV11(original,newContext);
        } else {
            return new InjectedInflaterV7(original,newContext);
        }
    }
} public interface IOwlObserver {

    /**
     *
     * @param mode current style mode
     * @param activity notice that: it may be null
     *
     * @see com.asha.nightowllib.inflater.InjectedInflaterBase#handleOnCreateView(View, String, AttributeSet)
     */
    void onSkinChange(int mode, Activity activity);
} public class OwlViewContext {
    private int mLastMode = -1;
    private SparseArray<IOwlObserverWithId> observers;
    public OwlViewContext() {
        observers = new SparseArray<>();
    }

    public void registerObserver( IOwlObserverWithId owlObserver ){
        observers.put(owlObserver.getObserverId(), owlObserver);
    }

    public void unregisterObserver( IOwlObserverWithId owlObserver ){
        observers.delete(owlObserver.getObserverId());
    }

    public void notifyObserver(int mode, Activity activity){
        if ( mode == mLastMode ) return;
        mLastMode = mode;
        int size = observers.size();
        for (int i = 0; i < size; i++) {
            IOwlObserverWithId owlObserver = observers.valueAt(i);
            owlObserver.onSkinChange(mode, activity);
        }
    }

    public void setupWithCurrentActivity(Activity activity){
        Resources.Theme theme = activity.getTheme();
        TypedArray a = theme.obtainStyledAttributes(R.styleable.NightOwl_Theme);
        if (a != null) {
            if ( !checkBeforeLollipop() ){
                if ( a.hasValue(R.styleable.NightOwl_Theme_night_navigationBarColor) )
                    registerObserver(new NavBarObserver(activity, a, R.styleable.NightOwl_Theme_night_navigationBarColor));
                if ( a.hasValue(R.styleable.NightOwl_Theme_night_statusBarColor) )
                    registerObserver(new StatusBarObserver(activity, a, R.styleable.NightOwl_Theme_night_statusBarColor));
            }

            if ( a.hasValue(R.styleable.NightOwl_Theme_night_additionThemeDay)
                    && a.hasValue(R.styleable.NightOwl_Theme_night_additionThemeNight) ){
                int themeDay = a.getResourceId(R.styleable.NightOwl_Theme_night_additionThemeDay,0 );
                int themeNight = a.getResourceId(R.styleable.NightOwl_Theme_night_additionThemeNight,0 );
                registerObserver(new AdditionThemeObserver(themeDay,themeNight));
            }
            a.recycle();
        }
    }

    public boolean needSync( int target ){
        return mLastMode != target;
    }
} public class ColorBox {
    private int mMode = -1;
    // may override the value, so we use SparseArray
    private SparseArray<Object[]> mBox;
    private ColorBox() {
        mBox = new SparseArray<>(4);
    }

    public void put(int attr, int scope, Object... objects){
        mBox.put(attr + scope, objects);
    }

    public void refreshSkin(int mode, View view, boolean force){
        if ( force ) mMode = -1;
        refreshSkin(mode,view);
    }

    public Object[] get(int attr, int scope){
        return mBox.get( attr + scope );
    }

    public void refreshSkin(int mode, View view){
        if ( mMode != mode ){
            int size = mBox.size();
            for (int i = 0; i < size; i++) {
                int attrWithScope = mBox.keyAt(i);
                Object[] res = mBox.valueAt(i);
                IOwlPaint paint = queryPaint(attrWithScope);
                if ( paint != null ) paint.draw(view, res[mode]);
            }
        }
        mMode = mode;
    }

    public int getMode() {
        return mMode;
    }

    public static ColorBox newInstance() {
        return new ColorBox();
    }

} public class OwlPaintManager {
    private static final SparseArray<Class< ? extends IOwlPaint>> sPaints = new SparseArray<>();
    private static final Map<Class< ? extends IOwlPaint>,IOwlPaint> sPaintInstances = new HashMap<>();
    static {
        // traverse all subclass.
        Class<?>[] classes = NightOwlTable.class.getDeclaredClasses();
        for ( Class subClz : classes ){
            registerPaint(subClz);
        }
    }

    public static void registerPaint(Class subClz){
        // look for all OwlAttrScope
        OwlAttrScope owlAttrScope = (OwlAttrScope) subClz.getAnnotation(OwlAttrScope.class);
        if ( owlAttrScope == null ) return;
        int scope = owlAttrScope.value();

        // traverse all declared fields in this subclass
        Field[] fields = subClz.getDeclaredFields();
        for (Field field : fields){
            // look for all OwlAttr
            OwlAttr attr = field.getAnnotation(OwlAttr.class);
            if ( attr == null ) continue;
            int attrId =  getStaticFieldIntSafely(field);
            Class< ? extends IOwlPaint> targetClz = attr.value();
            sPaints.append( attrId + scope,targetClz );
        }
    }

    public static IOwlPaint queryPaint(int attrWithScope){
        Class< ? extends IOwlPaint> clz = sPaints.get(attrWithScope);
        if ( clz == null ) return null;
        IOwlPaint instance = sPaintInstances.get(clz);
        if ( instance == null ){
            instance = newInstanceSafely(clz);
            sPaintInstances.put(clz,instance);
        }
        return instance;
    }
} public static void checkNonNull(Object obj,String msg){
    if ( obj == null ) throw new NullPointerException(msg);
} public static boolean checkViewCollected(View view){
    return view.getTag( NIGHT_OWL_VIEW_TAG ) != null;
} public static void injectLayoutInflater(LayoutInflater layoutInflater, Object src,Class clz, String name){
    try {
        Field field = clz.getDeclaredField(name);
        field.setAccessible(true);
        field.set(src, layoutInflater);
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }

} public static void insertEmptyTag(View view){
    view.setTag(NIGHT_OWL_VIEW_TAG, EMPTY_TAG);
} public static void insertViewContext(View view, OwlViewContext viewContext){
    view.setTag(NIGHT_OWL_VIEW_TAG + 1,viewContext);
} public static ColorBox obtainSkinBox(View view){
    Object box = view.getTag(NIGHT_OWL_VIEW_TAG);
    checkNonNull(box,"wtf, it can't be null.");
    if ( box instanceof ColorBox ){
        return (ColorBox) box;
    } else if ( box.equals(EMPTY_TAG) ) {
        Log.d(TAG, "EMPTY_TAG...");
        return null;
    } else {
        Log.e(TAG, "wtf, NIGHT_OWL_VIEW_TAG had been used by someone else.");
    }
    return null;
} public static OwlViewContext obtainViewContext(View view){
    Object observable = view.getTag(NIGHT_OWL_VIEW_TAG + 1);
    if ( observable != null && observable instanceof OwlViewContext){
        return (OwlViewContext) observable;
    }
    return null;
}
import android.app.Activity;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import com.asha.nightowllib.handler.ISkinHandler;
import com.asha.nightowllib.handler.OwlHandlerManager;
import com.asha.nightowllib.inflater.Factory4InjectedInflater;
import com.asha.nightowllib.observer.IOwlObserver;
import com.asha.nightowllib.observer.OwlViewContext;
import com.asha.nightowllib.paint.ColorBox;
import com.asha.nightowllib.paint.OwlPaintManager;
import static com.asha.nightowllib.NightOwlUtil.checkNonNull;
import static com.asha.nightowllib.NightOwlUtil.checkViewCollected;
import static com.asha.nightowllib.NightOwlUtil.injectLayoutInflater;
import static com.asha.nightowllib.NightOwlUtil.insertEmptyTag;
import static com.asha.nightowllib.NightOwlUtil.insertViewContext;
import static com.asha.nightowllib.NightOwlUtil.obtainSkinBox;
import static com.asha.nightowllib.NightOwlUtil.obtainViewContext;
package com.asha.nightowllib;




/**
 * Created by hzqiujiadi on 15/11/5.
 * hzqiujiadi ashqalcn@gmail.com
 */
public class NightOwl {
    private static final String TAG = "NightOwl";
    private static final String WINDOW_INFLATER = "mLayoutInflater";
    private static final String THEME_INFLATER = "mInflater";
    private static NightOwl sInstance;
    static {
        NightOwlTable.init();
    }

    private int mMode = 0;
    private IOwlObserver mOwlObserver;
    private NightOwl(){
    }

    public static void owlBeforeCreate(Activity activity){
        Window window = activity.getWindow();
        LayoutInflater layoutInflater = window.getLayoutInflater();

        // replace the inflater in window
        LayoutInflater injectLayoutInflater1 = Factory4InjectedInflater.newInstance(layoutInflater, activity);
        injectLayoutInflater(injectLayoutInflater1
                , activity.getWindow()
                , activity.getWindow().getClass()
                , WINDOW_INFLATER);

        // replace the inflater in current ContextThemeWrapper
        LayoutInflater injectLayoutInflater2 = injectLayoutInflater1.cloneInContext(activity);
        injectLayoutInflater(injectLayoutInflater2
                , activity
                , ContextThemeWrapper.class
                , THEME_INFLATER);

        // insert owlViewContext into root view.
        View v = activity.getWindow().getDecorView();
        OwlViewContext owlObservable = new OwlViewContext();
        insertViewContext(v, owlObservable);