• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
MenuInflater(Context context)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      */
inflate(int menuRes, Menu menu)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      */
parseMenu(XmlPullParser parser, AttributeSet attrs, Menu menu)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 
MenuState(final Menu menu)217         public MenuState(final Menu menu) {
218             this.menu = menu;
219 
220             resetGroup();
221         }
222 
resetGroup()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          */
readGroup(AttributeSet attrs)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          */
readItem(AttributeSet attrs)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 
getShortcut(String shortcutString)285         private char getShortcut(String shortcutString) {
286             if (shortcutString == null) {
287                 return 0;
288             } else {
289                 return shortcutString.charAt(0);
290             }
291         }
292 
setItem(MenuItem item)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 
addItem()308         public void addItem() {
309             itemAdded = true;
310             setItem(menu.add(groupId, itemId, itemCategoryOrder, itemTitle));
311         }
312 
addSubMenuItem()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 
hasAddedItem()320         public boolean hasAddedItem() {
321             return itemAdded;
322         }
323     }
324 
325 }
326