Project: androidZenWriter
Code Examples
/*
 * Copyright (C) 2006 The Android Open Source Project 
 *               2011 Jake Wharton 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */
 
package com.actionbarsherlock.view; 
 
import java.io.IOException; 
import java.lang.reflect.Constructor; 
import java.lang.reflect.Method; 
import org.xmlpull.v1.XmlPullParser; 
import org.xmlpull.v1.XmlPullParserException; 
import android.content.Context; 
import android.content.res.TypedArray; 
import android.content.res.XmlResourceParser; 
import android.util.AttributeSet; 
import android.util.Log; 
import android.util.TypedValue; 
import android.util.Xml; 
import android.view.InflateException; 
import android.view.View; 
 
import com.actionbarsherlock.R; 
import com.actionbarsherlock.internal.view.menu.MenuItemImpl; 
 
/**
 * This class is used to instantiate menu XML files into Menu objects. 
 * <p> 
 * For performance reasons, menu inflation relies heavily on pre-processing of 
 * XML files that is done at build time. Therefore, it is not currently possible 
 * to use MenuInflater with an XmlPullParser over a plain XML file at runtime; 
 * it only works with an XmlPullParser returned from a compiled resource (R. 
 * <em>something</em> file.) 
 */
 
public class MenuInflater { 
    private static final String LOG_TAG = "MenuInflater"
 
    /** Menu tag name in XML. */ 
    private static final String XML_MENU = "menu"
 
    /** Group tag name in XML. */ 
    private static final String XML_GROUP = "group"
 
    /** Item tag name in XML. */ 
    private static final String XML_ITEM = "item"
 
    private static final int NO_ID = 0
 
    private static final Class<?>[] ACTION_VIEW_CONSTRUCTOR_SIGNATURE = new Class[] {Context.class}; 
 
    private static final Class<?>[] ACTION_PROVIDER_CONSTRUCTOR_SIGNATURE = ACTION_VIEW_CONSTRUCTOR_SIGNATURE; 
 
    private final Object[] mActionViewConstructorArguments; 
 
    private final Object[] mActionProviderConstructorArguments; 
 
    private Context mContext; 
 
    /**
     * Constructs a menu inflater. 
     * 
     * @see Activity#getMenuInflater() 
     */
 
    public MenuInflater(Context context) { 
        mContext = context; 
        mActionViewConstructorArguments = new Object[] {context}; 
        mActionProviderConstructorArguments = mActionViewConstructorArguments; 
    } 
 
    /**
     * Inflate a menu hierarchy from the specified XML resource. Throws 
     * {@link InflateException} if there is an error. 
     * 
     * @param menuRes Resource ID for an XML layout resource to load (e.g., 
     *            <code>R.menu.main_activity</code>) 
     * @param menu The Menu to inflate into. The items and submenus will be 
     *            added to this Menu. 
     */
 
    public void inflate(int menuRes, Menu menu) { 
        XmlResourceParser parser = null
        try { 
            parser = mContext.getResources().getLayout(menuRes); 
            AttributeSet attrs = Xml.asAttributeSet(parser); 
 
            parseMenu(parser, attrs, menu); 
        } catch (XmlPullParserException e) { 
            throw new InflateException("Error inflating menu XML", e); 
        } catch (IOException e) { 
            throw new InflateException("Error inflating menu XML", e); 
        } finally { 
            if (parser != null) parser.close(); 
        } 
    } 
 
    /**
     * Called internally to fill the given menu. If a sub menu is seen, it will 
     * call this recursively. 
     */
 
    private void parseMenu(XmlPullParser parser, AttributeSet attrs, Menu menu) 
            throws XmlPullParserException, IOException { 
        MenuState menuState = new MenuState(menu); 
 
        int eventType = parser.getEventType(); 
        String tagName; 
        boolean lookingForEndOfUnknownTag = false
        String unknownTagName = null
 
        // This loop will skip to the menu start tag 
        do { 
            if (eventType == XmlPullParser.START_TAG) { 
                tagName = parser.getName(); 
                if (tagName.equals(XML_MENU)) { 
                    // Go to next tag 
                    eventType = parser.next(); 
                    break
                } 
 
                throw new RuntimeException("Expecting menu, got " + tagName); 
            } 
            eventType = parser.next(); 
        } while (eventType != XmlPullParser.END_DOCUMENT); 
 
        boolean reachedEndOfMenu = false
        while (!reachedEndOfMenu) { 
            switch (eventType) { 
                case XmlPullParser.START_TAG: 
                    if (lookingForEndOfUnknownTag) { 
                        break
                    } 
 
                    tagName = parser.getName(); 
                    if (tagName.equals(XML_GROUP)) { 
                        menuState.readGroup(attrs); 
                    } else if (tagName.equals(XML_ITEM)) { 
                        menuState.readItem(attrs); 
                    } else if (tagName.equals(XML_MENU)) { 
                        // A menu start tag denotes a submenu for an item 
                        SubMenu subMenu = menuState.addSubMenuItem(); 
 
                        // Parse the submenu into returned SubMenu 
                        parseMenu(parser, attrs, subMenu); 
                    } else { 
                        lookingForEndOfUnknownTag = true
                        unknownTagName = tagName; 
                    } 
                    break
 
                case XmlPullParser.END_TAG: 
                    tagName = parser.getName(); 
                    if (lookingForEndOfUnknownTag && tagName.equals(unknownTagName)) { 
                        lookingForEndOfUnknownTag = false
                        unknownTagName = null
                    } else if (tagName.equals(XML_GROUP)) { 
                        menuState.resetGroup(); 
                    } else if (tagName.equals(XML_ITEM)) { 
                        // Add the item if it hasn't been added (if the item was 
                        // a submenu, it would have been added already) 
                        if (!menuState.hasAddedItem()) { 
                            if (menuState.itemActionProvider != null && 
                                    menuState.itemActionProvider.hasSubMenu()) { 
                                menuState.addSubMenuItem(); 
                            } else { 
                                menuState.addItem(); 
                            } 
                        } 
                    } else if (tagName.equals(XML_MENU)) { 
                        reachedEndOfMenu = true
                    } 
                    break
 
                case XmlPullParser.END_DOCUMENT: 
                    throw new RuntimeException("Unexpected end of document"); 
            } 
 
            eventType = parser.next(); 
        } 
    } 
 
    private static class InflatedOnMenuItemClickListener 
            implements MenuItem.OnMenuItemClickListener { 
        private static final Class<?>[] PARAM_TYPES = new Class[] { MenuItem.class }; 
 
        private Context mContext; 
        private Method mMethod; 
 
        public InflatedOnMenuItemClickListener(Context context, String methodName) { 
            mContext = context; 
            Class<?> c = context.getClass(); 
            try { 
                mMethod = c.getMethod(methodName, PARAM_TYPES); 
            } catch (Exception e) { 
                InflateException ex = new InflateException( 
                        "Couldn't resolve menu item onClick handler " + methodName + 
                        " in class " + c.getName()); 
                ex.initCause(e); 
                throw ex; 
            } 
        } 
 
        public boolean onMenuItemClick(MenuItem item) { 
            try { 
                if (mMethod.getReturnType() == Boolean.TYPE) { 
                    return (Boolean) mMethod.invoke(mContext, item); 
                } else { 
                    mMethod.invoke(mContext, item); 
                    return true
                } 
            } catch (Exception e) { 
                throw new RuntimeException(e); 
            } 
        } 
    } 
 
    /**
     * State for the current menu. 
     * <p> 
     * Groups can not be nested unless there is another menu (which will have 
     * its state class). 
     */
 
    private class MenuState { 
        private Menu menu; 
 
        /*
         * Group state is set on items as they are added, allowing an item to 
         * override its group state. (As opposed to set on items at the group end tag.) 
         */
 
        private int groupId; 
        private int groupCategory; 
        private int groupOrder; 
        private int groupCheckable; 
        private boolean groupVisible; 
        private boolean groupEnabled; 
 
        private boolean itemAdded; 
        private int itemId; 
        private int itemCategoryOrder; 
        private CharSequence itemTitle; 
        private CharSequence itemTitleCondensed; 
        private int itemIconResId; 
        private char itemAlphabeticShortcut; 
        private char itemNumericShortcut; 
        /**
         * Sync to attrs.xml enum: 
         * - 0: none 
         * - 1: all 
         * - 2: exclusive 
         */
 
        private int itemCheckable; 
        private boolean itemChecked; 
        private boolean itemVisible; 
        private boolean itemEnabled; 
 
        /**
         * Sync to attrs.xml enum, values in MenuItem: 
         * - 0: never 
         * - 1: ifRoom 
         * - 2: always 
         * - -1: Safe sentinel for "no value". 
         */
 
        private int itemShowAsAction; 
 
        private int itemActionViewLayout; 
        private String itemActionViewClassName; 
        private String itemActionProviderClassName; 
 
        private String itemListenerMethodName; 
 
        private ActionProvider itemActionProvider; 
 
        private static final int defaultGroupId = NO_ID; 
        private static final int defaultItemId = NO_ID; 
        private static final int defaultItemCategory = 0
        private static final int defaultItemOrder = 0
        private static final int defaultItemCheckable = 0
        private static final boolean defaultItemChecked = false
        private static final boolean defaultItemVisible = true
        private static final boolean defaultItemEnabled = true
 
        public MenuState(final Menu menu) { 
            this.menu = menu; 
 
            resetGroup(); 
        } 
 
        public void resetGroup() { 
            groupId = defaultGroupId; 
            groupCategory = defaultItemCategory; 
            groupOrder = defaultItemOrder; 
            groupCheckable = defaultItemCheckable; 
            groupVisible = defaultItemVisible; 
            groupEnabled = defaultItemEnabled; 
        } 
 
        /**
         * Called when the parser is pointing to a group tag. 
         */
 
        public void readGroup(AttributeSet attrs) { 
            TypedArray a = mContext.obtainStyledAttributes(attrs, 
                    R.styleable.SherlockMenuGroup); 
 
            groupId = a.getResourceId(R.styleable.SherlockMenuGroup_android_id, defaultGroupId); 
            groupCategory = a.getInt(R.styleable.SherlockMenuGroup_android_menuCategory, defaultItemCategory); 
            groupOrder = a.getInt(R.styleable.SherlockMenuGroup_android_orderInCategory, defaultItemOrder); 
            groupCheckable = a.getInt(R.styleable.SherlockMenuGroup_android_checkableBehavior, defaultItemCheckable); 
            groupVisible = a.getBoolean(R.styleable.SherlockMenuGroup_android_visible, defaultItemVisible); 
            groupEnabled = a.getBoolean(R.styleable.SherlockMenuGroup_android_enabled, defaultItemEnabled); 
 
            a.recycle(); 
        } 
 
        /**
         * Called when the parser is pointing to an item tag. 
         */
 
        public void readItem(AttributeSet attrs) { 
            TypedArray a = mContext.obtainStyledAttributes(attrs, 
                    R.styleable.SherlockMenuItem); 
 
            // Inherit attributes from the group as default value 
            itemId = a.getResourceId(R.styleable.SherlockMenuItem_android_id, defaultItemId); 
            final int category = a.getInt(R.styleable.SherlockMenuItem_android_menuCategory, groupCategory); 
            final int order = a.getInt(R.styleable.SherlockMenuItem_android_orderInCategory, groupOrder); 
            itemCategoryOrder = (category & Menu.CATEGORY_MASK) | (order & Menu.USER_MASK); 
            itemTitle = a.getText(R.styleable.SherlockMenuItem_android_title); 
            itemTitleCondensed = a.getText(R.styleable.SherlockMenuItem_android_titleCondensed); 
            itemIconResId = a.getResourceId(R.styleable.SherlockMenuItem_android_icon, 0); 
            itemAlphabeticShortcut = 
                    getShortcut(a.getString(R.styleable.SherlockMenuItem_android_alphabeticShortcut)); 
            itemNumericShortcut = 
                    getShortcut(a.getString(R.styleable.SherlockMenuItem_android_numericShortcut)); 
            if (a.hasValue(R.styleable.SherlockMenuItem_android_checkable)) { 
                // Item has attribute checkable, use it 
                itemCheckable = a.getBoolean(R.styleable.SherlockMenuItem_android_checkable, false) ? 1 : 0
            } else { 
                // Item does not have attribute, use the group's (group can have one more state 
                // for checkable that represents the exclusive checkable) 
                itemCheckable = groupCheckable; 
            } 
 
            itemChecked = a.getBoolean(R.styleable.SherlockMenuItem_android_checked, defaultItemChecked); 
            itemVisible = a.getBoolean(R.styleable.SherlockMenuItem_android_visible, groupVisible); 
            itemEnabled = a.getBoolean(R.styleable.SherlockMenuItem_android_enabled, groupEnabled); 
 
            TypedValue value = new TypedValue(); 
            a.getValue(R.styleable.SherlockMenuItem_android_showAsAction, value); 
            itemShowAsAction = value.type == TypedValue.TYPE_INT_HEX ? value.data : -1
 
            itemListenerMethodName = a.getString(R.styleable.SherlockMenuItem_android_onClick); 
            itemActionViewLayout = a.getResourceId(R.styleable.SherlockMenuItem_android_actionLayout, 0); 
            itemActionViewClassName = a.getString(R.styleable.SherlockMenuItem_android_actionViewClass); 
            itemActionProviderClassName = a.getString(R.styleable.SherlockMenuItem_android_actionProviderClass); 
 
            final boolean hasActionProvider = itemActionProviderClassName != null
            if (hasActionProvider && itemActionViewLayout == 0 && itemActionViewClassName == null) { 
                itemActionProvider = newInstance(itemActionProviderClassName, 
                            ACTION_PROVIDER_CONSTRUCTOR_SIGNATURE, 
                            mActionProviderConstructorArguments); 
            } else { 
                if (hasActionProvider) { 
                    Log.w(LOG_TAG, "Ignoring attribute 'actionProviderClass'." 
                            + " Action view already specified."); 
                } 
                itemActionProvider = null
            } 
 
            a.recycle(); 
 
            itemAdded = false
        } 
 
        private char getShortcut(String shortcutString) { 
            if (shortcutString == null) { 
                return 0
            } else { 
                return shortcutString.charAt(0); 
            } 
        } 
 
        private void setItem(MenuItem item) { 
            item.setChecked(itemChecked) 
                .setVisible(itemVisible) 
                .setEnabled(itemEnabled) 
                .setCheckable(itemCheckable >= 1
                .setTitleCondensed(itemTitleCondensed) 
                .setIcon(itemIconResId) 
                .setAlphabeticShortcut(itemAlphabeticShortcut) 
                .setNumericShortcut(itemNumericShortcut); 
 
            if (itemShowAsAction >= 0) { 
                item.setShowAsAction(itemShowAsAction); 
            } 
 
            if (itemListenerMethodName != null) { 
                if (mContext.isRestricted()) { 
                    throw new IllegalStateException("The android:onClick attribute cannot " 
                            + "be used within a restricted context"); 
                } 
                item.setOnMenuItemClickListener( 
                        new InflatedOnMenuItemClickListener(mContext, itemListenerMethodName)); 
            } 
 
            if (itemCheckable >= 2) { 
                if (item instanceof MenuItemImpl) { 
                    MenuItemImpl impl = (MenuItemImpl) item; 
                    impl.setExclusiveCheckable(true); 
                } else { 
                    menu.setGroupCheckable(groupId, truetrue); 
                } 
            } 
 
            boolean actionViewSpecified = false
            if (itemActionViewClassName != null) { 
                View actionView = (View) newInstance(itemActionViewClassName, 
                        ACTION_VIEW_CONSTRUCTOR_SIGNATURE, mActionViewConstructorArguments); 
                item.setActionView(actionView); 
                actionViewSpecified = true
            } 
            if (itemActionViewLayout > 0) { 
                if (!actionViewSpecified) { 
                    item.setActionView(itemActionViewLayout); 
                    actionViewSpecified = true
                } else { 
                    Log.w(LOG_TAG, "Ignoring attribute 'itemActionViewLayout'." 
                            + " Action view already specified."); 
                } 
            } 
            if (itemActionProvider != null) { 
                item.setActionProvider(itemActionProvider); 
            } 
        } 
 
        public void addItem() { 
            itemAdded = true
            setItem(menu.add(groupId, itemId, itemCategoryOrder, itemTitle)); 
        } 
 
        public SubMenu addSubMenuItem() { 
            itemAdded = true
            SubMenu subMenu = menu.addSubMenu(groupId, itemId, itemCategoryOrder, itemTitle); 
            setItem(subMenu.getItem()); 
            return subMenu; 
        } 
 
        public boolean hasAddedItem() { 
            return itemAdded; 
        } 
 
        @SuppressWarnings("unchecked"
        private <T> T newInstance(String className, Class<?>[] constructorSignature, 
                Object[] arguments) { 
            try { 
                Class<?> clazz = mContext.getClassLoader().loadClass(className); 
                Constructor<?> constructor = clazz.getConstructor(constructorSignature); 
                return (T) constructor.newInstance(arguments); 
            } catch (Exception e) { 
                Log.w(LOG_TAG, "Cannot instantiate class: " + className, e); 
            } 
            return null
        } 
    } 
}