Home | History | Annotate | Download | only in view
      1 /*
      2  * Copyright (C) 2006 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.view;
     18 
     19 import com.android.internal.view.menu.MenuItemImpl;
     20 
     21 import java.io.IOException;
     22 
     23 import org.xmlpull.v1.XmlPullParser;
     24 import org.xmlpull.v1.XmlPullParserException;
     25 
     26 import android.app.Activity;
     27 import android.content.Context;
     28 import android.content.res.TypedArray;
     29 import android.content.res.XmlResourceParser;
     30 import android.util.AttributeSet;
     31 import android.util.Xml;
     32 
     33 /**
     34  * This class is used to instantiate menu XML files into Menu objects.
     35  * <p>
     36  * For performance reasons, menu inflation relies heavily on pre-processing of
     37  * XML files that is done at build time. Therefore, it is not currently possible
     38  * to use MenuInflater with an XmlPullParser over a plain XML file at runtime;
     39  * it only works with an XmlPullParser returned from a compiled resource (R.
     40  * <em>something</em> file.)
     41  */
     42 public class MenuInflater {
     43     /** Menu tag name in XML. */
     44     private static final String XML_MENU = "menu";
     45 
     46     /** Group tag name in XML. */
     47     private static final String XML_GROUP = "group";
     48 
     49     /** Item tag name in XML. */
     50     private static final String XML_ITEM = "item";
     51 
     52     private static final int NO_ID = 0;
     53 
     54     private Context mContext;
     55 
     56     /**
     57      * Constructs a menu inflater.
     58      *
     59      * @see Activity#getMenuInflater()
     60      */
     61     public MenuInflater(Context context) {
     62         mContext = context;
     63     }
     64 
     65     /**
     66      * Inflate a menu hierarchy from the specified XML resource. Throws
     67      * {@link InflateException} if there is an error.
     68      *
     69      * @param menuRes Resource ID for an XML layout resource to load (e.g.,
     70      *            <code>R.menu.main_activity</code>)
     71      * @param menu The Menu to inflate into. The items and submenus will be
     72      *            added to this Menu.
     73      */
     74     public void inflate(int menuRes, Menu menu) {
     75         XmlResourceParser parser = null;
     76         try {
     77             parser = mContext.getResources().getLayout(menuRes);
     78             AttributeSet attrs = Xml.asAttributeSet(parser);
     79 
     80             parseMenu(parser, attrs, menu);
     81         } catch (XmlPullParserException e) {
     82             throw new InflateException("Error inflating menu XML", e);
     83         } catch (IOException e) {
     84             throw new InflateException("Error inflating menu XML", e);
     85         } finally {
     86             if (parser != null) parser.close();
     87         }
     88     }
     89 
     90     /**
     91      * Called internally to fill the given menu. If a sub menu is seen, it will
     92      * call this recursively.
     93      */
     94     private void parseMenu(XmlPullParser parser, AttributeSet attrs, Menu menu)
     95             throws XmlPullParserException, IOException {
     96         MenuState menuState = new MenuState(menu);
     97 
     98         int eventType = parser.getEventType();
     99         String tagName;
    100         boolean lookingForEndOfUnknownTag = false;
    101         String unknownTagName = null;
    102 
    103         // This loop will skip to the menu start tag
    104         do {
    105             if (eventType == XmlPullParser.START_TAG) {
    106                 tagName = parser.getName();
    107                 if (tagName.equals(XML_MENU)) {
    108                     // Go to next tag
    109                     eventType = parser.next();
    110                     break;
    111                 }
    112 
    113                 throw new RuntimeException("Expecting menu, got " + tagName);
    114             }
    115             eventType = parser.next();
    116         } while (eventType != XmlPullParser.END_DOCUMENT);
    117 
    118         boolean reachedEndOfMenu = false;
    119         while (!reachedEndOfMenu) {
    120             switch (eventType) {
    121                 case XmlPullParser.START_TAG:
    122                     if (lookingForEndOfUnknownTag) {
    123                         break;
    124                     }
    125 
    126                     tagName = parser.getName();
    127                     if (tagName.equals(XML_GROUP)) {
    128                         menuState.readGroup(attrs);
    129                     } else if (tagName.equals(XML_ITEM)) {
    130                         menuState.readItem(attrs);
    131                     } else if (tagName.equals(XML_MENU)) {
    132                         // A menu start tag denotes a submenu for an item
    133                         SubMenu subMenu = menuState.addSubMenuItem();
    134 
    135                         // Parse the submenu into returned SubMenu
    136                         parseMenu(parser, attrs, subMenu);
    137                     } else {
    138                         lookingForEndOfUnknownTag = true;
    139                         unknownTagName = tagName;
    140                     }
    141                     break;
    142 
    143                 case XmlPullParser.END_TAG:
    144                     tagName = parser.getName();
    145                     if (lookingForEndOfUnknownTag && tagName.equals(unknownTagName)) {
    146                         lookingForEndOfUnknownTag = false;
    147                         unknownTagName = null;
    148                     } else if (tagName.equals(XML_GROUP)) {
    149                         menuState.resetGroup();
    150                     } else if (tagName.equals(XML_ITEM)) {
    151                         // Add the item if it hasn't been added (if the item was
    152                         // a submenu, it would have been added already)
    153                         if (!menuState.hasAddedItem()) {
    154                             menuState.addItem();
    155                         }
    156                     } else if (tagName.equals(XML_MENU)) {
    157                         reachedEndOfMenu = true;
    158                     }
    159                     break;
    160 
    161                 case XmlPullParser.END_DOCUMENT:
    162                     throw new RuntimeException("Unexpected end of document");
    163             }
    164 
    165             eventType = parser.next();
    166         }
    167     }
    168 
    169     /**
    170      * State for the current menu.
    171      * <p>
    172      * Groups can not be nested unless there is another menu (which will have
    173      * its state class).
    174      */
    175     private class MenuState {
    176         private Menu menu;
    177 
    178         /*
    179          * Group state is set on items as they are added, allowing an item to
    180          * override its group state. (As opposed to set on items at the group end tag.)
    181          */
    182         private int groupId;
    183         private int groupCategory;
    184         private int groupOrder;
    185         private int groupCheckable;
    186         private boolean groupVisible;
    187         private boolean groupEnabled;
    188 
    189         private boolean itemAdded;
    190         private int itemId;
    191         private int itemCategoryOrder;
    192         private String itemTitle;
    193         private String itemTitleCondensed;
    194         private int itemIconResId;
    195         private char itemAlphabeticShortcut;
    196         private char itemNumericShortcut;
    197         /**
    198          * Sync to attrs.xml enum:
    199          * - 0: none
    200          * - 1: all
    201          * - 2: exclusive
    202          */
    203         private int itemCheckable;
    204         private boolean itemChecked;
    205         private boolean itemVisible;
    206         private boolean itemEnabled;
    207 
    208         private static final int defaultGroupId = NO_ID;
    209         private static final int defaultItemId = NO_ID;
    210         private static final int defaultItemCategory = 0;
    211         private static final int defaultItemOrder = 0;
    212         private static final int defaultItemCheckable = 0;
    213         private static final boolean defaultItemChecked = false;
    214         private static final boolean defaultItemVisible = true;
    215         private static final boolean defaultItemEnabled = true;
    216 
    217         public MenuState(final Menu menu) {
    218             this.menu = menu;
    219 
    220             resetGroup();
    221         }
    222 
    223         public void resetGroup() {
    224             groupId = defaultGroupId;
    225             groupCategory = defaultItemCategory;
    226             groupOrder = defaultItemOrder;
    227             groupCheckable = defaultItemCheckable;
    228             groupVisible = defaultItemVisible;
    229             groupEnabled = defaultItemEnabled;
    230         }
    231 
    232         /**
    233          * Called when the parser is pointing to a group tag.
    234          */
    235         public void readGroup(AttributeSet attrs) {
    236             TypedArray a = mContext.obtainStyledAttributes(attrs,
    237                     com.android.internal.R.styleable.MenuGroup);
    238 
    239             groupId = a.getResourceId(com.android.internal.R.styleable.MenuGroup_id, defaultGroupId);
    240             groupCategory = a.getInt(com.android.internal.R.styleable.MenuGroup_menuCategory, defaultItemCategory);
    241             groupOrder = a.getInt(com.android.internal.R.styleable.MenuGroup_orderInCategory, defaultItemOrder);
    242             groupCheckable = a.getInt(com.android.internal.R.styleable.MenuGroup_checkableBehavior, defaultItemCheckable);
    243             groupVisible = a.getBoolean(com.android.internal.R.styleable.MenuGroup_visible, defaultItemVisible);
    244             groupEnabled = a.getBoolean(com.android.internal.R.styleable.MenuGroup_enabled, defaultItemEnabled);
    245 
    246             a.recycle();
    247         }
    248 
    249         /**
    250          * Called when the parser is pointing to an item tag.
    251          */
    252         public void readItem(AttributeSet attrs) {
    253             TypedArray a = mContext.obtainStyledAttributes(attrs,
    254                     com.android.internal.R.styleable.MenuItem);
    255 
    256             // Inherit attributes from the group as default value
    257             itemId = a.getResourceId(com.android.internal.R.styleable.MenuItem_id, defaultItemId);
    258             final int category = a.getInt(com.android.internal.R.styleable.MenuItem_menuCategory, groupCategory);
    259             final int order = a.getInt(com.android.internal.R.styleable.MenuItem_orderInCategory, groupOrder);
    260             itemCategoryOrder = (category & Menu.CATEGORY_MASK) | (order & Menu.USER_MASK);
    261             itemTitle = a.getString(com.android.internal.R.styleable.MenuItem_title);
    262             itemTitleCondensed = a.getString(com.android.internal.R.styleable.MenuItem_titleCondensed);
    263             itemIconResId = a.getResourceId(com.android.internal.R.styleable.MenuItem_icon, 0);
    264             itemAlphabeticShortcut =
    265                     getShortcut(a.getString(com.android.internal.R.styleable.MenuItem_alphabeticShortcut));
    266             itemNumericShortcut =
    267                     getShortcut(a.getString(com.android.internal.R.styleable.MenuItem_numericShortcut));
    268             if (a.hasValue(com.android.internal.R.styleable.MenuItem_checkable)) {
    269                 // Item has attribute checkable, use it
    270                 itemCheckable = a.getBoolean(com.android.internal.R.styleable.MenuItem_checkable, false) ? 1 : 0;
    271             } else {
    272                 // Item does not have attribute, use the group's (group can have one more state
    273                 // for checkable that represents the exclusive checkable)
    274                 itemCheckable = groupCheckable;
    275             }
    276             itemChecked = a.getBoolean(com.android.internal.R.styleable.MenuItem_checked, defaultItemChecked);
    277             itemVisible = a.getBoolean(com.android.internal.R.styleable.MenuItem_visible, groupVisible);
    278             itemEnabled = a.getBoolean(com.android.internal.R.styleable.MenuItem_enabled, groupEnabled);
    279 
    280             a.recycle();
    281 
    282             itemAdded = false;
    283         }
    284 
    285         private char getShortcut(String shortcutString) {
    286             if (shortcutString == null) {
    287                 return 0;
    288             } else {
    289                 return shortcutString.charAt(0);
    290             }
    291         }
    292 
    293         private void setItem(MenuItem item) {
    294             item.setChecked(itemChecked)
    295                 .setVisible(itemVisible)
    296                 .setEnabled(itemEnabled)
    297                 .setCheckable(itemCheckable >= 1)
    298                 .setTitleCondensed(itemTitleCondensed)
    299                 .setIcon(itemIconResId)
    300                 .setAlphabeticShortcut(itemAlphabeticShortcut)
    301                 .setNumericShortcut(itemNumericShortcut);
    302 
    303             if (itemCheckable >= 2) {
    304                 ((MenuItemImpl) item).setExclusiveCheckable(true);
    305             }
    306         }
    307 
    308         public void addItem() {
    309             itemAdded = true;
    310             setItem(menu.add(groupId, itemId, itemCategoryOrder, itemTitle));
    311         }
    312 
    313         public SubMenu addSubMenuItem() {
    314             itemAdded = true;
    315             SubMenu subMenu = menu.addSubMenu(groupId, itemId, itemCategoryOrder, itemTitle);
    316             setItem(subMenu.getItem());
    317             return subMenu;
    318         }
    319 
    320         public boolean hasAddedItem() {
    321             return itemAdded;
    322         }
    323     }
    324 
    325 }
    326