package android.view;
import android.annotation.MenuRes;
import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;
import com.android.internal.view.menu.MenuItemImpl;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class MenuInflater {
private static final String LOG_TAG = "MenuInflater";
private static final String XML_MENU = "menu";
private static final String XML_GROUP = "group";
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;
private Object mRealOwner;
public MenuInflater(Context context) {
mContext = context;
mActionViewConstructorArguments = new Object[] {context};
mActionProviderConstructorArguments = mActionViewConstructorArguments;
}
public MenuInflater(Context context, Object realOwner) {
mContext = context;
mRealOwner = realOwner;
mActionViewConstructorArguments = new Object[] {context};
mActionProviderConstructorArguments = mActionViewConstructorArguments;
}
public void inflate(@MenuRes 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();
}
}
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;
do {
if (eventType == XmlPullParser.START_TAG) {
tagName = parser.getName();
if (tagName.equals(XML_MENU)) {
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)) {
SubMenu subMenu = menuState.addSubMenuItem();
registerMenu(subMenu, attrs);
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)) {
if (!menuState.hasAddedItem()) {
if (menuState.itemActionProvider != null &&
menuState.itemActionProvider.hasSubMenu()) {
registerMenu(menuState.addSubMenuItem(), attrs);
} else {
registerMenu(menuState.addItem(), attrs);
}
}
} else if (tagName.equals(XML_MENU)) {
reachedEndOfMenu = true;
}
break;
case XmlPullParser.END_DOCUMENT:
throw new RuntimeException("Unexpected end of document");
}
eventType = parser.next();
}
}
private void registerMenu(@SuppressWarnings("unused") MenuItem item,
@SuppressWarnings("unused") AttributeSet set) {
}
private void registerMenu(@SuppressWarnings("unused") SubMenu subMenu,
@SuppressWarnings("unused") AttributeSet set) {
}
Context getContext() {
return mContext;
}
private static class InflatedOnMenuItemClickListener
implements MenuItem.OnMenuItemClickListener {
private static final Class<?>[] PARAM_TYPES = new Class[] { MenuItem.class };
private Object mRealOwner;
private Method mMethod;
public InflatedOnMenuItemClickListener(Object realOwner, String methodName) {
mRealOwner = realOwner;
Class<?> c = realOwner.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(mRealOwner, item);
} else {
mMethod.invoke(mRealOwner, item);
return true;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
private Object getRealOwner() {
if (mRealOwner == null) {
mRealOwner = findRealOwner(mContext);
}
return mRealOwner;
}
private Object findRealOwner(Object owner) {
if (owner instanceof Activity) {
return owner;
}
if (owner instanceof ContextWrapper) {
return findRealOwner(((ContextWrapper) owner).getBaseContext());
}
return owner;
}
private class MenuState {
private Menu menu;
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 ColorStateList itemIconTintList = null;
private PorterDuff.Mode itemIconTintMode = null;
private char itemAlphabeticShortcut;
private int itemAlphabeticModifiers;
private char itemNumericShortcut;
private int itemNumericModifiers;
private int itemCheckable;
private boolean itemChecked;
private boolean itemVisible;
private boolean itemEnabled;
private int itemShowAsAction;
private int itemActionViewLayout;
private String itemActionViewClassName;
private String itemActionProviderClassName;
private String itemListenerMethodName;
private ActionProvider itemActionProvider;
private CharSequence itemContentDescription;
private CharSequence itemTooltipText;
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;
}
public void readGroup(AttributeSet attrs) {
TypedArray a = mContext.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.MenuGroup);
groupId = a.getResourceId(com.android.internal.R.styleable.MenuGroup_id, defaultGroupId);
groupCategory = a.getInt(com.android.internal.R.styleable.MenuGroup_menuCategory, defaultItemCategory);
groupOrder = a.getInt(com.android.internal.R.styleable.MenuGroup_orderInCategory, defaultItemOrder);
groupCheckable = a.getInt(com.android.internal.R.styleable.MenuGroup_checkableBehavior, defaultItemCheckable);
groupVisible = a.getBoolean(com.android.internal.R.styleable.MenuGroup_visible, defaultItemVisible);
groupEnabled = a.getBoolean(com.android.internal.R.styleable.MenuGroup_enabled, defaultItemEnabled);
a.recycle();
}
public void readItem(AttributeSet attrs) {
TypedArray a = mContext.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.MenuItem);
itemId = a.getResourceId(com.android.internal.R.styleable.MenuItem_id, defaultItemId);
final int category = a.getInt(com.android.internal.R.styleable.MenuItem_menuCategory, groupCategory);
final int order = a.getInt(com.android.internal.R.styleable.MenuItem_orderInCategory, groupOrder);
itemCategoryOrder = (category & Menu.CATEGORY_MASK) | (order & Menu.USER_MASK);
itemTitle = a.getText(com.android.internal.R.styleable.MenuItem_title);
itemTitleCondensed = a.getText(com.android.internal.R.styleable.MenuItem_titleCondensed);
itemIconResId = a.getResourceId(com.android.internal.R.styleable.MenuItem_icon, 0);
if (a.hasValue(com.android.internal.R.styleable.MenuItem_iconTintMode)) {
itemIconTintMode = Drawable.parseTintMode(a.getInt(
com.android.internal.R.styleable.MenuItem_iconTintMode, -1),
itemIconTintMode);
} else {
itemIconTintMode = null;
}
if (a.hasValue(com.android.internal.R.styleable.MenuItem_iconTint)) {
itemIconTintList = a.getColorStateList(
com.android.internal.R.styleable.MenuItem_iconTint);
} else {
itemIconTintList = null;
}
itemAlphabeticShortcut =
getShortcut(a.getString(com.android.internal.R.styleable.MenuItem_alphabeticShortcut));
itemAlphabeticModifiers =
a.getInt(com.android.internal.R.styleable.MenuItem_alphabeticModifiers,
KeyEvent.META_CTRL_ON);
itemNumericShortcut =
getShortcut(a.getString(com.android.internal.R.styleable.MenuItem_numericShortcut));
itemNumericModifiers =
a.getInt(com.android.internal.R.styleable.MenuItem_numericModifiers,
KeyEvent.META_CTRL_ON);
if (a.hasValue(com.android.internal.R.styleable.MenuItem_checkable)) {
itemCheckable = a.getBoolean(com.android.internal.R.styleable.MenuItem_checkable, false) ? 1 : 0;
} else {
itemCheckable = groupCheckable;
}
itemChecked = a.getBoolean(com.android.internal.R.styleable.MenuItem_checked, defaultItemChecked);
itemVisible = a.getBoolean(com.android.internal.R.styleable.MenuItem_visible, groupVisible);
itemEnabled = a.getBoolean(com.android.internal.R.styleable.MenuItem_enabled, groupEnabled);
itemShowAsAction = a.getInt(com.android.internal.R.styleable.MenuItem_showAsAction, -1);
itemListenerMethodName = a.getString(com.android.internal.R.styleable.MenuItem_onClick);
itemActionViewLayout = a.getResourceId(com.android.internal.R.styleable.MenuItem_actionLayout, 0);
itemActionViewClassName = a.getString(com.android.internal.R.styleable.MenuItem_actionViewClass);
itemActionProviderClassName = a.getString(com.android.internal.R.styleable.MenuItem_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;
}
itemContentDescription =
a.getText(com.android.internal.R.styleable.MenuItem_contentDescription);
itemTooltipText = a.getText(com.android.internal.R.styleable.MenuItem_tooltipText);
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, itemAlphabeticModifiers)
.setNumericShortcut(itemNumericShortcut, itemNumericModifiers);
if (itemShowAsAction >= 0) {
item.setShowAsAction(itemShowAsAction);
}
if (itemIconTintMode != null) {
item.setIconTintMode(itemIconTintMode);
}
if (itemIconTintList != null) {
item.setIconTintList(itemIconTintList);
}
if (itemListenerMethodName != null) {
if (mContext.isRestricted()) {
throw new IllegalStateException("The android:onClick attribute cannot "
+ "be used within a restricted context");
}
item.setOnMenuItemClickListener(
new InflatedOnMenuItemClickListener(getRealOwner(), itemListenerMethodName));
}
if (item instanceof MenuItemImpl) {
MenuItemImpl impl = (MenuItemImpl) item;
if (itemCheckable >= 2) {
impl.setExclusiveCheckable(true);
}
}
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);
}
item.setContentDescription(itemContentDescription);
item.setTooltipText(itemTooltipText);
}
public MenuItem addItem() {
itemAdded = true;
MenuItem item = menu.add(groupId, itemId, itemCategoryOrder, itemTitle);
setItem(item);
return item;
}
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);
constructor.setAccessible(true);
return (T) constructor.newInstance(arguments);
} catch (Exception e) {
Log.w(LOG_TAG, "Cannot instantiate class: " + className, e);
}
return null;
}
}
}