MenuBuilder.java revision 4192e38827b27416410516d4bb0d545c36f4660e
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
17package com.android.internal.view.menu;
18
19
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.pm.PackageManager;
24import android.content.pm.ResolveInfo;
25import android.content.res.Configuration;
26import android.content.res.Resources;
27import android.graphics.drawable.Drawable;
28import android.os.Bundle;
29import android.os.Parcelable;
30import android.util.SparseArray;
31import android.util.SparseBooleanArray;
32import android.util.TypedValue;
33import android.view.ContextMenu.ContextMenuInfo;
34import android.view.ContextThemeWrapper;
35import android.view.KeyCharacterMap;
36import android.view.KeyEvent;
37import android.view.LayoutInflater;
38import android.view.Menu;
39import android.view.MenuItem;
40import android.view.SubMenu;
41import android.view.View;
42import android.view.View.MeasureSpec;
43import android.view.ViewGroup;
44import android.widget.AdapterView;
45import android.widget.BaseAdapter;
46
47import java.lang.ref.WeakReference;
48import java.util.ArrayList;
49import java.util.List;
50import java.util.Vector;
51
52/**
53 * Implementation of the {@link android.view.Menu} interface for creating a
54 * standard menu UI.
55 */
56public class MenuBuilder implements Menu {
57    private static final String LOGTAG = "MenuBuilder";
58
59    /** The number of different menu types */
60    public static final int NUM_TYPES = 5;
61    /** The menu type that represents the icon menu view */
62    public static final int TYPE_ICON = 0;
63    /** The menu type that represents the expanded menu view */
64    public static final int TYPE_EXPANDED = 1;
65    /**
66     * The menu type that represents a menu dialog. Examples are context and sub
67     * menus. This menu type will not have a corresponding MenuView, but it will
68     * have an ItemView.
69     */
70    public static final int TYPE_DIALOG = 2;
71    /**
72     * The menu type that represents a button in the application's action bar.
73     */
74    public static final int TYPE_ACTION_BUTTON = 3;
75    /**
76     * The menu type that represents a menu popup.
77     */
78    public static final int TYPE_POPUP = 4;
79
80    private static final String VIEWS_TAG = "android:views";
81
82    private static final int THEME_SYSTEM_DEFAULT = 0;
83    private static final int THEME_APPLICATION = -1;
84    private static final int THEME_ALERT_DIALOG = -2;
85
86    // Order must be the same order as the TYPE_*
87    static final int THEME_RES_FOR_TYPE[] = new int[] {
88        com.android.internal.R.style.Theme_IconMenu,
89        com.android.internal.R.style.Theme_ExpandedMenu,
90        THEME_ALERT_DIALOG,
91        THEME_APPLICATION,
92        THEME_APPLICATION,
93    };
94
95    // Order must be the same order as the TYPE_*
96    static final int LAYOUT_RES_FOR_TYPE[] = new int[] {
97        com.android.internal.R.layout.icon_menu_layout,
98        com.android.internal.R.layout.expanded_menu_layout,
99        0,
100        com.android.internal.R.layout.action_menu_layout,
101        0,
102    };
103
104    // Order must be the same order as the TYPE_*
105    static final int ITEM_LAYOUT_RES_FOR_TYPE[] = new int[] {
106        com.android.internal.R.layout.icon_menu_item_layout,
107        com.android.internal.R.layout.list_menu_item_layout,
108        com.android.internal.R.layout.list_menu_item_layout,
109        com.android.internal.R.layout.action_menu_item_layout,
110        com.android.internal.R.layout.popup_menu_item_layout,
111    };
112
113    private static final int[]  sCategoryToOrder = new int[] {
114        1, /* No category */
115        4, /* CONTAINER */
116        5, /* SYSTEM */
117        3, /* SECONDARY */
118        2, /* ALTERNATIVE */
119        0, /* SELECTED_ALTERNATIVE */
120    };
121
122    private final Context mContext;
123    private final Resources mResources;
124
125    /**
126     * Whether the shortcuts should be qwerty-accessible. Use isQwertyMode()
127     * instead of accessing this directly.
128     */
129    private boolean mQwertyMode;
130
131    /**
132     * Whether the shortcuts should be visible on menus. Use isShortcutsVisible()
133     * instead of accessing this directly.
134     */
135    private boolean mShortcutsVisible;
136
137    /**
138     * Callback that will receive the various menu-related events generated by
139     * this class. Use getCallback to get a reference to the callback.
140     */
141    private Callback mCallback;
142
143    /** Contains all of the items for this menu */
144    private ArrayList<MenuItemImpl> mItems;
145
146    /** Contains only the items that are currently visible.  This will be created/refreshed from
147     * {@link #getVisibleItems()} */
148    private ArrayList<MenuItemImpl> mVisibleItems;
149    /**
150     * Whether or not the items (or any one item's shown state) has changed since it was last
151     * fetched from {@link #getVisibleItems()}
152     */
153    private boolean mIsVisibleItemsStale;
154
155    /**
156     * Contains only the items that should appear in the Action Bar, if present.
157     */
158    private ArrayList<MenuItemImpl> mActionItems;
159    /**
160     * Contains items that should NOT appear in the Action Bar, if present.
161     */
162    private ArrayList<MenuItemImpl> mNonActionItems;
163    /**
164     * The number of visible action buttons permitted in this menu
165     */
166    private int mMaxActionItems;
167    /**
168     * The total width limit in pixels for all action items within a menu
169     */
170    private int mActionWidthLimit;
171    /**
172     * Whether or not the items (or any one item's action state) has changed since it was
173     * last fetched.
174     */
175    private boolean mIsActionItemsStale;
176
177    /**
178     * Whether the process of granting space as action items should reserve a space for
179     * an overflow option in the action list.
180     */
181    private boolean mReserveActionOverflow;
182
183    /**
184     * Default value for how added items should show in the action list.
185     */
186    private int mDefaultShowAsAction = MenuItem.SHOW_AS_ACTION_NEVER;
187
188    /**
189     * Current use case is Context Menus: As Views populate the context menu, each one has
190     * extra information that should be passed along.  This is the current menu info that
191     * should be set on all items added to this menu.
192     */
193    private ContextMenuInfo mCurrentMenuInfo;
194
195    /** Header title for menu types that have a header (context and submenus) */
196    CharSequence mHeaderTitle;
197    /** Header icon for menu types that have a header and support icons (context) */
198    Drawable mHeaderIcon;
199    /** Header custom view for menu types that have a header and support custom views (context) */
200    View mHeaderView;
201
202    /**
203     * Contains the state of the View hierarchy for all menu views when the menu
204     * was frozen.
205     */
206    private SparseArray<Parcelable> mFrozenViewStates;
207
208    /**
209     * Prevents onItemsChanged from doing its junk, useful for batching commands
210     * that may individually call onItemsChanged.
211     */
212    private boolean mPreventDispatchingItemsChanged = false;
213
214    private boolean mOptionalIconsVisible = false;
215
216    private ViewGroup mMeasureActionButtonParent;
217
218    // Group IDs that have been added as actions - used temporarily, allocated here for reuse.
219    private final SparseBooleanArray mActionButtonGroups = new SparseBooleanArray();
220
221    private static int getAlertDialogTheme(Context context) {
222        TypedValue outValue = new TypedValue();
223        context.getTheme().resolveAttribute(com.android.internal.R.attr.alertDialogTheme,
224                outValue, true);
225        return outValue.resourceId;
226    }
227
228    private MenuType[] mMenuTypes;
229    class MenuType {
230        private int mMenuType;
231
232        /** The layout inflater that uses the menu type's theme */
233        private LayoutInflater mInflater;
234
235        /** The lazily loaded {@link MenuView} */
236        private WeakReference<MenuView> mMenuView;
237
238        MenuType(int menuType) {
239            mMenuType = menuType;
240        }
241
242        LayoutInflater getInflater() {
243            // Create an inflater that uses the given theme for the Views it inflates
244            if (mInflater == null) {
245                Context wrappedContext;
246                int themeResForType = THEME_RES_FOR_TYPE[mMenuType];
247                switch (themeResForType) {
248                    case THEME_APPLICATION:
249                        wrappedContext = mContext;
250                        break;
251                    case THEME_ALERT_DIALOG:
252                        wrappedContext = new ContextThemeWrapper(mContext,
253                                getAlertDialogTheme(mContext));
254                        break;
255                    default:
256                        wrappedContext = new ContextThemeWrapper(mContext, themeResForType);
257                        break;
258                }
259                mInflater = (LayoutInflater) wrappedContext
260                        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
261            }
262
263            return mInflater;
264        }
265
266        MenuView getMenuView(ViewGroup parent) {
267            if (LAYOUT_RES_FOR_TYPE[mMenuType] == 0) {
268                return null;
269            }
270
271            synchronized (this) {
272                MenuView menuView = mMenuView != null ? mMenuView.get() : null;
273
274                if (menuView == null) {
275                    menuView = (MenuView) getInflater().inflate(
276                            LAYOUT_RES_FOR_TYPE[mMenuType], parent, false);
277                    menuView.initialize(MenuBuilder.this, mMenuType);
278
279                    // Cache the view
280                    mMenuView = new WeakReference<MenuView>(menuView);
281
282                    if (mFrozenViewStates != null) {
283                        View view = (View) menuView;
284                        view.restoreHierarchyState(mFrozenViewStates);
285
286                        // Clear this menu type's frozen state, since we just restored it
287                        mFrozenViewStates.remove(view.getId());
288                    }
289                }
290
291                return menuView;
292            }
293        }
294
295        boolean hasMenuView() {
296            return mMenuView != null && mMenuView.get() != null;
297        }
298    }
299
300    /**
301     * Called by menu to notify of close and selection changes
302     */
303    public interface Callback {
304        /**
305         * Called when a menu item is selected.
306         * @param menu The menu that is the parent of the item
307         * @param item The menu item that is selected
308         * @return whether the menu item selection was handled
309         */
310        public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item);
311
312        /**
313         * Called when a menu is closed.
314         * @param menu The menu that was closed.
315         * @param allMenusAreClosing Whether the menus are completely closing (true),
316         *            or whether there is another menu opening shortly
317         *            (false). For example, if the menu is closing because a
318         *            sub menu is about to be shown, <var>allMenusAreClosing</var>
319         *            is false.
320         */
321        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing);
322
323        /**
324         * Called when a sub menu is selected.  This is a cue to open the given sub menu's decor.
325         * @param subMenu the sub menu that is being opened
326         * @return whether the sub menu selection was handled by the callback
327         */
328        public boolean onSubMenuSelected(SubMenuBuilder subMenu);
329
330        /**
331         * Called when a sub menu is closed
332         * @param menu the sub menu that was closed
333         */
334        public void onCloseSubMenu(SubMenuBuilder menu);
335
336        /**
337         * Called when the mode of the menu changes (for example, from icon to expanded).
338         *
339         * @param menu the menu that has changed modes
340         */
341        public void onMenuModeChange(MenuBuilder menu);
342    }
343
344    /**
345     * Called by menu items to execute their associated action
346     */
347    public interface ItemInvoker {
348        public boolean invokeItem(MenuItemImpl item);
349    }
350
351    public MenuBuilder(Context context) {
352        mMenuTypes = new MenuType[NUM_TYPES];
353
354        mContext = context;
355        mResources = context.getResources();
356
357        mItems = new ArrayList<MenuItemImpl>();
358
359        mVisibleItems = new ArrayList<MenuItemImpl>();
360        mIsVisibleItemsStale = true;
361
362        mActionItems = new ArrayList<MenuItemImpl>();
363        mNonActionItems = new ArrayList<MenuItemImpl>();
364        mIsActionItemsStale = true;
365
366        setShortcutsVisibleInner(true);
367    }
368
369    public MenuBuilder setDefaultShowAsAction(int defaultShowAsAction) {
370        mDefaultShowAsAction = defaultShowAsAction;
371        return this;
372    }
373
374    public void setCallback(Callback callback) {
375        mCallback = callback;
376    }
377
378    MenuType getMenuType(int menuType) {
379        if (mMenuTypes[menuType] == null) {
380            mMenuTypes[menuType] = new MenuType(menuType);
381        }
382
383        return mMenuTypes[menuType];
384    }
385
386    /**
387     * Gets a menu View that contains this menu's items.
388     *
389     * @param menuType The type of menu to get a View for (must be one of
390     *            {@link #TYPE_ICON}, {@link #TYPE_EXPANDED},
391     *            {@link #TYPE_DIALOG}).
392     * @param parent The ViewGroup that provides a set of LayoutParams values
393     *            for this menu view
394     * @return A View for the menu of type <var>menuType</var>
395     */
396    public View getMenuView(int menuType, ViewGroup parent) {
397        // The expanded menu depends on the number if items shown in the icon menu (which
398        // is adjustable as setters/XML attributes on IconMenuView [imagine a larger LCD
399        // wanting to show more icons]). If, for example, the activity goes through
400        // an orientation change while the expanded menu is open, the icon menu's view
401        // won't have an instance anymore; so here we make sure we have an icon menu view (matching
402        // the same parent so the layout parameters from the XML are used). This
403        // will create the icon menu view and cache it (if it doesn't already exist).
404        if (menuType == TYPE_EXPANDED
405                && (mMenuTypes[TYPE_ICON] == null || !mMenuTypes[TYPE_ICON].hasMenuView())) {
406            getMenuType(TYPE_ICON).getMenuView(parent);
407        }
408
409        return (View) getMenuType(menuType).getMenuView(parent);
410    }
411
412    private int getNumIconMenuItemsShown() {
413        ViewGroup parent = null;
414
415        if (!mMenuTypes[TYPE_ICON].hasMenuView()) {
416            /*
417             * There isn't an icon menu view instantiated, so when we get it
418             * below, it will lazily instantiate it. We should pass a proper
419             * parent so it uses the layout_ attributes present in the XML
420             * layout file.
421             */
422            if (mMenuTypes[TYPE_EXPANDED].hasMenuView()) {
423                View expandedMenuView = (View) mMenuTypes[TYPE_EXPANDED].getMenuView(null);
424                parent = (ViewGroup) expandedMenuView.getParent();
425            }
426        }
427
428        return ((IconMenuView) getMenuView(TYPE_ICON, parent)).getNumActualItemsShown();
429    }
430
431    /**
432     * Clears the cached menu views. Call this if the menu views need to another
433     * layout (for example, if the screen size has changed).
434     */
435    public void clearMenuViews() {
436        for (int i = NUM_TYPES - 1; i >= 0; i--) {
437            if (mMenuTypes[i] != null) {
438                mMenuTypes[i].mMenuView = null;
439            }
440        }
441
442        for (int i = mItems.size() - 1; i >= 0; i--) {
443            MenuItemImpl item = mItems.get(i);
444            if (item.hasSubMenu()) {
445                ((SubMenuBuilder) item.getSubMenu()).clearMenuViews();
446            }
447            item.clearItemViews();
448        }
449    }
450
451    /**
452     * Adds an item to the menu.  The other add methods funnel to this.
453     */
454    private MenuItem addInternal(int group, int id, int categoryOrder, CharSequence title) {
455        final int ordering = getOrdering(categoryOrder);
456
457        final MenuItemImpl item = new MenuItemImpl(this, group, id, categoryOrder,
458                ordering, title, mDefaultShowAsAction);
459
460        if (mCurrentMenuInfo != null) {
461            // Pass along the current menu info
462            item.setMenuInfo(mCurrentMenuInfo);
463        }
464
465        mItems.add(findInsertIndex(mItems, ordering), item);
466        onItemsChanged(false);
467
468        return item;
469    }
470
471    public MenuItem add(CharSequence title) {
472        return addInternal(0, 0, 0, title);
473    }
474
475    public MenuItem add(int titleRes) {
476        return addInternal(0, 0, 0, mResources.getString(titleRes));
477    }
478
479    public MenuItem add(int group, int id, int categoryOrder, CharSequence title) {
480        return addInternal(group, id, categoryOrder, title);
481    }
482
483    public MenuItem add(int group, int id, int categoryOrder, int title) {
484        return addInternal(group, id, categoryOrder, mResources.getString(title));
485    }
486
487    public SubMenu addSubMenu(CharSequence title) {
488        return addSubMenu(0, 0, 0, title);
489    }
490
491    public SubMenu addSubMenu(int titleRes) {
492        return addSubMenu(0, 0, 0, mResources.getString(titleRes));
493    }
494
495    public SubMenu addSubMenu(int group, int id, int categoryOrder, CharSequence title) {
496        final MenuItemImpl item = (MenuItemImpl) addInternal(group, id, categoryOrder, title);
497        final SubMenuBuilder subMenu = new SubMenuBuilder(mContext, this, item);
498        item.setSubMenu(subMenu);
499
500        return subMenu;
501    }
502
503    public SubMenu addSubMenu(int group, int id, int categoryOrder, int title) {
504        return addSubMenu(group, id, categoryOrder, mResources.getString(title));
505    }
506
507    public int addIntentOptions(int group, int id, int categoryOrder, ComponentName caller,
508            Intent[] specifics, Intent intent, int flags, MenuItem[] outSpecificItems) {
509        PackageManager pm = mContext.getPackageManager();
510        final List<ResolveInfo> lri =
511                pm.queryIntentActivityOptions(caller, specifics, intent, 0);
512        final int N = lri != null ? lri.size() : 0;
513
514        if ((flags & FLAG_APPEND_TO_GROUP) == 0) {
515            removeGroup(group);
516        }
517
518        for (int i=0; i<N; i++) {
519            final ResolveInfo ri = lri.get(i);
520            Intent rintent = new Intent(
521                ri.specificIndex < 0 ? intent : specifics[ri.specificIndex]);
522            rintent.setComponent(new ComponentName(
523                    ri.activityInfo.applicationInfo.packageName,
524                    ri.activityInfo.name));
525            final MenuItem item = add(group, id, categoryOrder, ri.loadLabel(pm))
526                    .setIcon(ri.loadIcon(pm))
527                    .setIntent(rintent);
528            if (outSpecificItems != null && ri.specificIndex >= 0) {
529                outSpecificItems[ri.specificIndex] = item;
530            }
531        }
532
533        return N;
534    }
535
536    public void removeItem(int id) {
537        removeItemAtInt(findItemIndex(id), true);
538    }
539
540    public void removeGroup(int group) {
541        final int i = findGroupIndex(group);
542
543        if (i >= 0) {
544            final int maxRemovable = mItems.size() - i;
545            int numRemoved = 0;
546            while ((numRemoved++ < maxRemovable) && (mItems.get(i).getGroupId() == group)) {
547                // Don't force update for each one, this method will do it at the end
548                removeItemAtInt(i, false);
549            }
550
551            // Notify menu views
552            onItemsChanged(false);
553        }
554    }
555
556    /**
557     * Remove the item at the given index and optionally forces menu views to
558     * update.
559     *
560     * @param index The index of the item to be removed. If this index is
561     *            invalid an exception is thrown.
562     * @param updateChildrenOnMenuViews Whether to force update on menu views.
563     *            Please make sure you eventually call this after your batch of
564     *            removals.
565     */
566    private void removeItemAtInt(int index, boolean updateChildrenOnMenuViews) {
567        if ((index < 0) || (index >= mItems.size())) return;
568
569        mItems.remove(index);
570
571        if (updateChildrenOnMenuViews) onItemsChanged(false);
572    }
573
574    public void removeItemAt(int index) {
575        removeItemAtInt(index, true);
576    }
577
578    public void clearAll() {
579        mPreventDispatchingItemsChanged = true;
580        clear();
581        clearHeader();
582        mPreventDispatchingItemsChanged = false;
583        onItemsChanged(true);
584    }
585
586    public void clear() {
587        mItems.clear();
588
589        onItemsChanged(true);
590    }
591
592    void setExclusiveItemChecked(MenuItem item) {
593        final int group = item.getGroupId();
594
595        final int N = mItems.size();
596        for (int i = 0; i < N; i++) {
597            MenuItemImpl curItem = mItems.get(i);
598            if (curItem.getGroupId() == group) {
599                if (!curItem.isExclusiveCheckable()) continue;
600                if (!curItem.isCheckable()) continue;
601
602                // Check the item meant to be checked, uncheck the others (that are in the group)
603                curItem.setCheckedInt(curItem == item);
604            }
605        }
606    }
607
608    public void setGroupCheckable(int group, boolean checkable, boolean exclusive) {
609        final int N = mItems.size();
610
611        for (int i = 0; i < N; i++) {
612            MenuItemImpl item = mItems.get(i);
613            if (item.getGroupId() == group) {
614                item.setExclusiveCheckable(exclusive);
615                item.setCheckable(checkable);
616            }
617        }
618    }
619
620    public void setGroupVisible(int group, boolean visible) {
621        final int N = mItems.size();
622
623        // We handle the notification of items being changed ourselves, so we use setVisibleInt rather
624        // than setVisible and at the end notify of items being changed
625
626        boolean changedAtLeastOneItem = false;
627        for (int i = 0; i < N; i++) {
628            MenuItemImpl item = mItems.get(i);
629            if (item.getGroupId() == group) {
630                if (item.setVisibleInt(visible)) changedAtLeastOneItem = true;
631            }
632        }
633
634        if (changedAtLeastOneItem) onItemsChanged(false);
635    }
636
637    public void setGroupEnabled(int group, boolean enabled) {
638        final int N = mItems.size();
639
640        for (int i = 0; i < N; i++) {
641            MenuItemImpl item = mItems.get(i);
642            if (item.getGroupId() == group) {
643                item.setEnabled(enabled);
644            }
645        }
646    }
647
648    public boolean hasVisibleItems() {
649        final int size = size();
650
651        for (int i = 0; i < size; i++) {
652            MenuItemImpl item = mItems.get(i);
653            if (item.isVisible()) {
654                return true;
655            }
656        }
657
658        return false;
659    }
660
661    public MenuItem findItem(int id) {
662        final int size = size();
663        for (int i = 0; i < size; i++) {
664            MenuItemImpl item = mItems.get(i);
665            if (item.getItemId() == id) {
666                return item;
667            } else if (item.hasSubMenu()) {
668                MenuItem possibleItem = item.getSubMenu().findItem(id);
669
670                if (possibleItem != null) {
671                    return possibleItem;
672                }
673            }
674        }
675
676        return null;
677    }
678
679    public int findItemIndex(int id) {
680        final int size = size();
681
682        for (int i = 0; i < size; i++) {
683            MenuItemImpl item = mItems.get(i);
684            if (item.getItemId() == id) {
685                return i;
686            }
687        }
688
689        return -1;
690    }
691
692    public int findGroupIndex(int group) {
693        return findGroupIndex(group, 0);
694    }
695
696    public int findGroupIndex(int group, int start) {
697        final int size = size();
698
699        if (start < 0) {
700            start = 0;
701        }
702
703        for (int i = start; i < size; i++) {
704            final MenuItemImpl item = mItems.get(i);
705
706            if (item.getGroupId() == group) {
707                return i;
708            }
709        }
710
711        return -1;
712    }
713
714    public int size() {
715        return mItems.size();
716    }
717
718    /** {@inheritDoc} */
719    public MenuItem getItem(int index) {
720        return mItems.get(index);
721    }
722
723    public MenuItem getOverflowItem(int index) {
724        flagActionItems(true);
725        return mNonActionItems.get(index);
726    }
727
728    public boolean isShortcutKey(int keyCode, KeyEvent event) {
729        return findItemWithShortcutForKey(keyCode, event) != null;
730    }
731
732    public void setQwertyMode(boolean isQwerty) {
733        mQwertyMode = isQwerty;
734
735        refreshShortcuts(isShortcutsVisible(), isQwerty);
736    }
737
738    /**
739     * Returns the ordering across all items. This will grab the category from
740     * the upper bits, find out how to order the category with respect to other
741     * categories, and combine it with the lower bits.
742     *
743     * @param categoryOrder The category order for a particular item (if it has
744     *            not been or/add with a category, the default category is
745     *            assumed).
746     * @return An ordering integer that can be used to order this item across
747     *         all the items (even from other categories).
748     */
749    private static int getOrdering(int categoryOrder)
750    {
751        final int index = (categoryOrder & CATEGORY_MASK) >> CATEGORY_SHIFT;
752
753        if (index < 0 || index >= sCategoryToOrder.length) {
754            throw new IllegalArgumentException("order does not contain a valid category.");
755        }
756
757        return (sCategoryToOrder[index] << CATEGORY_SHIFT) | (categoryOrder & USER_MASK);
758    }
759
760    /**
761     * @return whether the menu shortcuts are in qwerty mode or not
762     */
763    boolean isQwertyMode() {
764        return mQwertyMode;
765    }
766
767    /**
768     * Refreshes the shortcut labels on each of the displayed items.  Passes the arguments
769     * so submenus don't need to call their parent menu for the same values.
770     */
771    private void refreshShortcuts(boolean shortcutsVisible, boolean qwertyMode) {
772        MenuItemImpl item;
773        for (int i = mItems.size() - 1; i >= 0; i--) {
774            item = mItems.get(i);
775
776            if (item.hasSubMenu()) {
777                ((MenuBuilder) item.getSubMenu()).refreshShortcuts(shortcutsVisible, qwertyMode);
778            }
779
780            item.refreshShortcutOnItemViews(shortcutsVisible, qwertyMode);
781        }
782    }
783
784    /**
785     * Sets whether the shortcuts should be visible on menus.  Devices without hardware
786     * key input will never make shortcuts visible even if this method is passed 'true'.
787     *
788     * @param shortcutsVisible Whether shortcuts should be visible (if true and a
789     *            menu item does not have a shortcut defined, that item will
790     *            still NOT show a shortcut)
791     */
792    public void setShortcutsVisible(boolean shortcutsVisible) {
793        if (mShortcutsVisible == shortcutsVisible) return;
794
795        setShortcutsVisibleInner(shortcutsVisible);
796        refreshShortcuts(mShortcutsVisible, isQwertyMode());
797    }
798
799    private void setShortcutsVisibleInner(boolean shortcutsVisible) {
800        mShortcutsVisible = shortcutsVisible
801                && mResources.getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS
802                && mResources.getBoolean(
803                        com.android.internal.R.bool.config_showMenuShortcutsWhenKeyboardPresent);
804    }
805
806    /**
807     * @return Whether shortcuts should be visible on menus.
808     */
809    public boolean isShortcutsVisible() {
810        return mShortcutsVisible;
811    }
812
813    Resources getResources() {
814        return mResources;
815    }
816
817    public Callback getCallback() {
818        return mCallback;
819    }
820
821    public Context getContext() {
822        return mContext;
823    }
824
825    private static int findInsertIndex(ArrayList<MenuItemImpl> items, int ordering) {
826        for (int i = items.size() - 1; i >= 0; i--) {
827            MenuItemImpl item = items.get(i);
828            if (item.getOrdering() <= ordering) {
829                return i + 1;
830            }
831        }
832
833        return 0;
834    }
835
836    public boolean performShortcut(int keyCode, KeyEvent event, int flags) {
837        final MenuItemImpl item = findItemWithShortcutForKey(keyCode, event);
838
839        boolean handled = false;
840
841        if (item != null) {
842            handled = performItemAction(item, flags);
843        }
844
845        if ((flags & FLAG_ALWAYS_PERFORM_CLOSE) != 0) {
846            close(true);
847        }
848
849        return handled;
850    }
851
852    /*
853     * This function will return all the menu and sub-menu items that can
854     * be directly (the shortcut directly corresponds) and indirectly
855     * (the ALT-enabled char corresponds to the shortcut) associated
856     * with the keyCode.
857     */
858    List<MenuItemImpl> findItemsWithShortcutForKey(int keyCode, KeyEvent event) {
859        final boolean qwerty = isQwertyMode();
860        final int metaState = event.getMetaState();
861        final KeyCharacterMap.KeyData possibleChars = new KeyCharacterMap.KeyData();
862        // Get the chars associated with the keyCode (i.e using any chording combo)
863        final boolean isKeyCodeMapped = event.getKeyData(possibleChars);
864        // The delete key is not mapped to '\b' so we treat it specially
865        if (!isKeyCodeMapped && (keyCode != KeyEvent.KEYCODE_DEL)) {
866            return null;
867        }
868
869        Vector<MenuItemImpl> items = new Vector();
870        // Look for an item whose shortcut is this key.
871        final int N = mItems.size();
872        for (int i = 0; i < N; i++) {
873            MenuItemImpl item = mItems.get(i);
874            if (item.hasSubMenu()) {
875                List<MenuItemImpl> subMenuItems = ((MenuBuilder)item.getSubMenu())
876                    .findItemsWithShortcutForKey(keyCode, event);
877                items.addAll(subMenuItems);
878            }
879            final char shortcutChar = qwerty ? item.getAlphabeticShortcut() : item.getNumericShortcut();
880            if (((metaState & (KeyEvent.META_SHIFT_ON | KeyEvent.META_SYM_ON)) == 0) &&
881                  (shortcutChar != 0) &&
882                  (shortcutChar == possibleChars.meta[0]
883                      || shortcutChar == possibleChars.meta[2]
884                      || (qwerty && shortcutChar == '\b' &&
885                          keyCode == KeyEvent.KEYCODE_DEL)) &&
886                  item.isEnabled()) {
887                items.add(item);
888            }
889        }
890        return items;
891    }
892
893    /*
894     * We want to return the menu item associated with the key, but if there is no
895     * ambiguity (i.e. there is only one menu item corresponding to the key) we want
896     * to return it even if it's not an exact match; this allow the user to
897     * _not_ use the ALT key for example, making the use of shortcuts slightly more
898     * user-friendly. An example is on the G1, '!' and '1' are on the same key, and
899     * in Gmail, Menu+1 will trigger Menu+! (the actual shortcut).
900     *
901     * On the other hand, if two (or more) shortcuts corresponds to the same key,
902     * we have to only return the exact match.
903     */
904    MenuItemImpl findItemWithShortcutForKey(int keyCode, KeyEvent event) {
905        // Get all items that can be associated directly or indirectly with the keyCode
906        List<MenuItemImpl> items = findItemsWithShortcutForKey(keyCode, event);
907
908        if (items == null) {
909            return null;
910        }
911
912        final int metaState = event.getMetaState();
913        final KeyCharacterMap.KeyData possibleChars = new KeyCharacterMap.KeyData();
914        // Get the chars associated with the keyCode (i.e using any chording combo)
915        event.getKeyData(possibleChars);
916
917        // If we have only one element, we can safely returns it
918        if (items.size() == 1) {
919            return items.get(0);
920        }
921
922        final boolean qwerty = isQwertyMode();
923        // If we found more than one item associated with the key,
924        // we have to return the exact match
925        for (MenuItemImpl item : items) {
926            final char shortcutChar = qwerty ? item.getAlphabeticShortcut() : item.getNumericShortcut();
927            if ((shortcutChar == possibleChars.meta[0] &&
928                    (metaState & KeyEvent.META_ALT_ON) == 0)
929                || (shortcutChar == possibleChars.meta[2] &&
930                    (metaState & KeyEvent.META_ALT_ON) != 0)
931                || (qwerty && shortcutChar == '\b' &&
932                    keyCode == KeyEvent.KEYCODE_DEL)) {
933                return item;
934            }
935        }
936        return null;
937    }
938
939    public boolean performIdentifierAction(int id, int flags) {
940        // Look for an item whose identifier is the id.
941        return performItemAction(findItem(id), flags);
942    }
943
944    public boolean performItemAction(MenuItem item, int flags) {
945        MenuItemImpl itemImpl = (MenuItemImpl) item;
946
947        if (itemImpl == null || !itemImpl.isEnabled()) {
948            return false;
949        }
950
951        boolean invoked = itemImpl.invoke();
952
953        if (item.hasSubMenu()) {
954            close(false);
955
956            if (mCallback != null) {
957                // Return true if the sub menu was invoked or the item was invoked previously
958                invoked = mCallback.onSubMenuSelected((SubMenuBuilder) item.getSubMenu())
959                        || invoked;
960            }
961        } else {
962            if ((flags & FLAG_PERFORM_NO_CLOSE) == 0) {
963                close(true);
964            }
965        }
966
967        return invoked;
968    }
969
970    /**
971     * Closes the visible menu.
972     *
973     * @param allMenusAreClosing Whether the menus are completely closing (true),
974     *            or whether there is another menu coming in this menu's place
975     *            (false). For example, if the menu is closing because a
976     *            sub menu is about to be shown, <var>allMenusAreClosing</var>
977     *            is false.
978     */
979    final void close(boolean allMenusAreClosing) {
980        Callback callback = getCallback();
981        if (callback != null) {
982            callback.onCloseMenu(this, allMenusAreClosing);
983        }
984    }
985
986    /** {@inheritDoc} */
987    public void close() {
988        close(true);
989    }
990
991    /**
992     * Called when an item is added or removed.
993     *
994     * @param cleared Whether the items were cleared or just changed.
995     */
996    private void onItemsChanged(boolean cleared) {
997        if (!mPreventDispatchingItemsChanged) {
998            if (mIsVisibleItemsStale == false) mIsVisibleItemsStale = true;
999            if (mIsActionItemsStale == false) mIsActionItemsStale = true;
1000
1001            MenuType[] menuTypes = mMenuTypes;
1002            for (int i = 0; i < NUM_TYPES; i++) {
1003                if ((menuTypes[i] != null) && (menuTypes[i].hasMenuView())) {
1004                    MenuView menuView = menuTypes[i].mMenuView.get();
1005                    menuView.updateChildren(cleared);
1006                }
1007            }
1008        }
1009    }
1010
1011    /**
1012     * Called by {@link MenuItemImpl} when its visible flag is changed.
1013     * @param item The item that has gone through a visibility change.
1014     */
1015    void onItemVisibleChanged(MenuItemImpl item) {
1016        // Notify of items being changed
1017        onItemsChanged(false);
1018    }
1019
1020    /**
1021     * Called by {@link MenuItemImpl} when its action request status is changed.
1022     * @param item The item that has gone through a change in action request status.
1023     */
1024    void onItemActionRequestChanged(MenuItemImpl item) {
1025        // Notify of items being changed
1026        onItemsChanged(false);
1027    }
1028
1029    ArrayList<MenuItemImpl> getVisibleItems() {
1030        if (!mIsVisibleItemsStale) return mVisibleItems;
1031
1032        // Refresh the visible items
1033        mVisibleItems.clear();
1034
1035        final int itemsSize = mItems.size();
1036        MenuItemImpl item;
1037        for (int i = 0; i < itemsSize; i++) {
1038            item = mItems.get(i);
1039            if (item.isVisible()) mVisibleItems.add(item);
1040        }
1041
1042        mIsVisibleItemsStale = false;
1043        mIsActionItemsStale = true;
1044
1045        return mVisibleItems;
1046    }
1047
1048    /**
1049     * @return A fake action button parent view for obtaining child views.
1050     */
1051    private ViewGroup getMeasureActionButtonParent() {
1052        if (mMeasureActionButtonParent == null) {
1053            mMeasureActionButtonParent = (ViewGroup) getMenuType(TYPE_ACTION_BUTTON).getInflater()
1054                    .inflate(LAYOUT_RES_FOR_TYPE[TYPE_ACTION_BUTTON], null, false);
1055        }
1056        return mMeasureActionButtonParent;
1057    }
1058
1059    /**
1060     * This method determines which menu items get to be 'action items' that will appear
1061     * in an action bar and which items should be 'overflow items' in a secondary menu.
1062     * The rules are as follows:
1063     *
1064     * <p>Items are considered for inclusion in the order specified within the menu.
1065     * There is a limit of mMaxActionItems as a total count, optionally including the overflow
1066     * menu button itself. This is a soft limit; if an item shares a group ID with an item
1067     * previously included as an action item, the new item will stay with its group and become
1068     * an action item itself even if it breaks the max item count limit. This is done to
1069     * limit the conceptual complexity of the items presented within an action bar. Only a few
1070     * unrelated concepts should be presented to the user in this space, and groups are treated
1071     * as a single concept.
1072     *
1073     * <p>There is also a hard limit of consumed measurable space: mActionWidthLimit. This
1074     * limit may be broken by a single item that exceeds the remaining space, but no further
1075     * items may be added. If an item that is part of a group cannot fit within the remaining
1076     * measured width, the entire group will be demoted to overflow. This is done to ensure room
1077     * for navigation and other affordances in the action bar as well as reduce general UI clutter.
1078     *
1079     * <p>The space freed by demoting a full group cannot be consumed by future menu items.
1080     * Once items begin to overflow, all future items become overflow items as well. This is
1081     * to avoid inadvertent reordering that may break the app's intended design.
1082     *
1083     * @param reserveActionOverflow true if an overflow button should consume one space
1084     *                              in the available item count
1085     */
1086    private void flagActionItems(boolean reserveActionOverflow) {
1087        if (reserveActionOverflow != mReserveActionOverflow) {
1088            mReserveActionOverflow = reserveActionOverflow;
1089            mIsActionItemsStale = true;
1090        }
1091
1092        if (!mIsActionItemsStale) {
1093            return;
1094        }
1095
1096        final ArrayList<MenuItemImpl> visibleItems = getVisibleItems();
1097        final int itemsSize = visibleItems.size();
1098        int maxActions = mMaxActionItems;
1099        int widthLimit = mActionWidthLimit;
1100        final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1101        final ViewGroup parent = getMeasureActionButtonParent();
1102
1103        int requiredItems = 0;
1104        int requestedItems = 0;
1105        int firstActionWidth = 0;
1106        boolean hasOverflow = false;
1107        for (int i = 0; i < itemsSize; i++) {
1108            MenuItemImpl item = visibleItems.get(i);
1109            if (item.requiresActionButton()) {
1110                requiredItems++;
1111            } else if (item.requestsActionButton()) {
1112                requestedItems++;
1113            } else {
1114                hasOverflow = true;
1115            }
1116        }
1117
1118        // Reserve a spot for the overflow item if needed.
1119        if (reserveActionOverflow &&
1120                (hasOverflow || requiredItems + requestedItems > maxActions)) {
1121            maxActions--;
1122        }
1123        maxActions -= requiredItems;
1124
1125        final SparseBooleanArray seenGroups = mActionButtonGroups;
1126        seenGroups.clear();
1127
1128        // Flag as many more requested items as will fit.
1129        for (int i = 0; i < itemsSize; i++) {
1130            MenuItemImpl item = visibleItems.get(i);
1131
1132            if (item.requiresActionButton()) {
1133                View v = item.getActionView();
1134                if (v == null) {
1135                    v = item.getItemView(TYPE_ACTION_BUTTON, parent);
1136                }
1137                v.measure(querySpec, querySpec);
1138                final int measuredWidth = v.getMeasuredWidth();
1139                widthLimit -= measuredWidth;
1140                if (firstActionWidth == 0) {
1141                    firstActionWidth = measuredWidth;
1142                }
1143                final int groupId = item.getGroupId();
1144                if (groupId != 0) {
1145                    seenGroups.put(groupId, true);
1146                }
1147            } else if (item.requestsActionButton()) {
1148                // Items in a group with other items that already have an action slot
1149                // can break the max actions rule, but not the width limit.
1150                final int groupId = item.getGroupId();
1151                final boolean inGroup = seenGroups.get(groupId);
1152                boolean isAction = (maxActions > 0 || inGroup) && widthLimit > 0;
1153                maxActions--;
1154
1155                if (isAction) {
1156                    View v = item.getActionView();
1157                    if (v == null) {
1158                        v = item.getItemView(TYPE_ACTION_BUTTON, parent);
1159                    }
1160                    v.measure(querySpec, querySpec);
1161                    final int measuredWidth = v.getMeasuredWidth();
1162                    widthLimit -= measuredWidth;
1163                    if (firstActionWidth == 0) {
1164                        firstActionWidth = measuredWidth;
1165                    }
1166
1167                    // Did this push the entire first item past halfway?
1168                    if (widthLimit + firstActionWidth <= 0) {
1169                        isAction = false;
1170                    }
1171                }
1172
1173                if (isAction && groupId != 0) {
1174                    seenGroups.put(groupId, true);
1175                } else if (inGroup) {
1176                    // We broke the width limit. Demote the whole group, they all overflow now.
1177                    seenGroups.put(groupId, false);
1178                    for (int j = 0; j < i; j++) {
1179                        MenuItemImpl areYouMyGroupie = visibleItems.get(j);
1180                        if (areYouMyGroupie.getGroupId() == groupId) {
1181                            areYouMyGroupie.setIsActionButton(false);
1182                        }
1183                    }
1184                }
1185
1186                item.setIsActionButton(isAction);
1187            }
1188        }
1189
1190        mActionItems.clear();
1191        mNonActionItems.clear();
1192        for (int i = 0; i < itemsSize; i++) {
1193            MenuItemImpl item = visibleItems.get(i);
1194            if (item.isActionButton()) {
1195                mActionItems.add(item);
1196            } else {
1197                mNonActionItems.add(item);
1198            }
1199        }
1200
1201        mIsActionItemsStale = false;
1202    }
1203
1204    ArrayList<MenuItemImpl> getActionItems(boolean reserveActionOverflow) {
1205        flagActionItems(reserveActionOverflow);
1206        return mActionItems;
1207    }
1208
1209    ArrayList<MenuItemImpl> getNonActionItems(boolean reserveActionOverflow) {
1210        flagActionItems(reserveActionOverflow);
1211        return mNonActionItems;
1212    }
1213
1214    void setMaxActionItems(int maxActionItems) {
1215        mMaxActionItems = maxActionItems;
1216        mIsActionItemsStale = true;
1217    }
1218
1219    void setActionWidthLimit(int widthLimit) {
1220        mActionWidthLimit = widthLimit;
1221        mIsActionItemsStale = true;
1222    }
1223
1224    public void clearHeader() {
1225        mHeaderIcon = null;
1226        mHeaderTitle = null;
1227        mHeaderView = null;
1228
1229        onItemsChanged(false);
1230    }
1231
1232    private void setHeaderInternal(final int titleRes, final CharSequence title, final int iconRes,
1233            final Drawable icon, final View view) {
1234        final Resources r = getResources();
1235
1236        if (view != null) {
1237            mHeaderView = view;
1238
1239            // If using a custom view, then the title and icon aren't used
1240            mHeaderTitle = null;
1241            mHeaderIcon = null;
1242        } else {
1243            if (titleRes > 0) {
1244                mHeaderTitle = r.getText(titleRes);
1245            } else if (title != null) {
1246                mHeaderTitle = title;
1247            }
1248
1249            if (iconRes > 0) {
1250                mHeaderIcon = r.getDrawable(iconRes);
1251            } else if (icon != null) {
1252                mHeaderIcon = icon;
1253            }
1254
1255            // If using the title or icon, then a custom view isn't used
1256            mHeaderView = null;
1257        }
1258
1259        // Notify of change
1260        onItemsChanged(false);
1261    }
1262
1263    /**
1264     * Sets the header's title. This replaces the header view. Called by the
1265     * builder-style methods of subclasses.
1266     *
1267     * @param title The new title.
1268     * @return This MenuBuilder so additional setters can be called.
1269     */
1270    protected MenuBuilder setHeaderTitleInt(CharSequence title) {
1271        setHeaderInternal(0, title, 0, null, null);
1272        return this;
1273    }
1274
1275    /**
1276     * Sets the header's title. This replaces the header view. Called by the
1277     * builder-style methods of subclasses.
1278     *
1279     * @param titleRes The new title (as a resource ID).
1280     * @return This MenuBuilder so additional setters can be called.
1281     */
1282    protected MenuBuilder setHeaderTitleInt(int titleRes) {
1283        setHeaderInternal(titleRes, null, 0, null, null);
1284        return this;
1285    }
1286
1287    /**
1288     * Sets the header's icon. This replaces the header view. Called by the
1289     * builder-style methods of subclasses.
1290     *
1291     * @param icon The new icon.
1292     * @return This MenuBuilder so additional setters can be called.
1293     */
1294    protected MenuBuilder setHeaderIconInt(Drawable icon) {
1295        setHeaderInternal(0, null, 0, icon, null);
1296        return this;
1297    }
1298
1299    /**
1300     * Sets the header's icon. This replaces the header view. Called by the
1301     * builder-style methods of subclasses.
1302     *
1303     * @param iconRes The new icon (as a resource ID).
1304     * @return This MenuBuilder so additional setters can be called.
1305     */
1306    protected MenuBuilder setHeaderIconInt(int iconRes) {
1307        setHeaderInternal(0, null, iconRes, null, null);
1308        return this;
1309    }
1310
1311    /**
1312     * Sets the header's view. This replaces the title and icon. Called by the
1313     * builder-style methods of subclasses.
1314     *
1315     * @param view The new view.
1316     * @return This MenuBuilder so additional setters can be called.
1317     */
1318    protected MenuBuilder setHeaderViewInt(View view) {
1319        setHeaderInternal(0, null, 0, null, view);
1320        return this;
1321    }
1322
1323    public CharSequence getHeaderTitle() {
1324        return mHeaderTitle;
1325    }
1326
1327    public Drawable getHeaderIcon() {
1328        return mHeaderIcon;
1329    }
1330
1331    public View getHeaderView() {
1332        return mHeaderView;
1333    }
1334
1335    /**
1336     * Gets the root menu (if this is a submenu, find its root menu).
1337     * @return The root menu.
1338     */
1339    public MenuBuilder getRootMenu() {
1340        return this;
1341    }
1342
1343    /**
1344     * Sets the current menu info that is set on all items added to this menu
1345     * (until this is called again with different menu info, in which case that
1346     * one will be added to all subsequent item additions).
1347     *
1348     * @param menuInfo The extra menu information to add.
1349     */
1350    public void setCurrentMenuInfo(ContextMenuInfo menuInfo) {
1351        mCurrentMenuInfo = menuInfo;
1352    }
1353
1354    /**
1355     * Gets an adapter for providing items and their views.
1356     *
1357     * @param menuType The type of menu to get an adapter for.
1358     * @return A {@link MenuAdapter} for this menu with the given menu type.
1359     */
1360    public MenuAdapter getMenuAdapter(int menuType) {
1361        return new MenuAdapter(menuType);
1362    }
1363
1364    /**
1365     * Gets an adapter for providing overflow (non-action) items and their views.
1366     *
1367     * @param menuType The type of menu to get an adapter for.
1368     * @return A {@link MenuAdapter} for this menu with the given menu type.
1369     */
1370    public MenuAdapter getOverflowMenuAdapter(int menuType) {
1371        return new OverflowMenuAdapter(menuType);
1372    }
1373
1374    void setOptionalIconsVisible(boolean visible) {
1375        mOptionalIconsVisible = visible;
1376    }
1377
1378    boolean getOptionalIconsVisible() {
1379        return mOptionalIconsVisible;
1380    }
1381
1382    public void saveHierarchyState(Bundle outState) {
1383        SparseArray<Parcelable> viewStates = new SparseArray<Parcelable>();
1384
1385        MenuType[] menuTypes = mMenuTypes;
1386        for (int i = NUM_TYPES - 1; i >= 0; i--) {
1387            if (menuTypes[i] == null) {
1388                continue;
1389            }
1390
1391            if (menuTypes[i].hasMenuView()) {
1392                ((View) menuTypes[i].getMenuView(null)).saveHierarchyState(viewStates);
1393            }
1394        }
1395
1396        outState.putSparseParcelableArray(VIEWS_TAG, viewStates);
1397    }
1398
1399    public void restoreHierarchyState(Bundle inState) {
1400        // Save this for menu views opened later
1401        SparseArray<Parcelable> viewStates = mFrozenViewStates = inState
1402                .getSparseParcelableArray(VIEWS_TAG);
1403
1404        // Thaw those menu views already open
1405        MenuType[] menuTypes = mMenuTypes;
1406        for (int i = NUM_TYPES - 1; i >= 0; i--) {
1407            if (menuTypes[i] == null) {
1408                continue;
1409            }
1410
1411            if (menuTypes[i].hasMenuView()) {
1412                ((View) menuTypes[i].getMenuView(null)).restoreHierarchyState(viewStates);
1413            }
1414        }
1415    }
1416
1417    /**
1418     * An adapter that allows an {@link AdapterView} to use this {@link MenuBuilder} as a data
1419     * source.  This adapter will use only the visible/shown items from the menu.
1420     */
1421    public class MenuAdapter extends BaseAdapter {
1422        private int mMenuType;
1423
1424        public MenuAdapter(int menuType) {
1425            mMenuType = menuType;
1426        }
1427
1428        public int getOffset() {
1429            if (mMenuType == TYPE_EXPANDED) {
1430                return getNumIconMenuItemsShown();
1431            } else {
1432                return 0;
1433            }
1434        }
1435
1436        public int getCount() {
1437            return getVisibleItems().size() - getOffset();
1438        }
1439
1440        public MenuItemImpl getItem(int position) {
1441            return getVisibleItems().get(position + getOffset());
1442        }
1443
1444        public long getItemId(int position) {
1445            // Since a menu item's ID is optional, we'll use the position as an
1446            // ID for the item in the AdapterView
1447            return position;
1448        }
1449
1450        public View getView(int position, View convertView, ViewGroup parent) {
1451            if (convertView != null) {
1452                MenuView.ItemView itemView = (MenuView.ItemView) convertView;
1453                itemView.getItemData().setItemView(mMenuType, null);
1454
1455                MenuItemImpl item = (MenuItemImpl) getItem(position);
1456                itemView.initialize(item, mMenuType);
1457                item.setItemView(mMenuType, itemView);
1458                return convertView;
1459            } else {
1460                MenuItemImpl item = (MenuItemImpl) getItem(position);
1461                item.setItemView(mMenuType, null);
1462                return item.getItemView(mMenuType, parent);
1463            }
1464        }
1465    }
1466
1467    /**
1468     * An adapter that allows an {@link AdapterView} to use this {@link MenuBuilder} as a data
1469     * source for overflow menu items that do not fit in the list of action items.
1470     */
1471    private class OverflowMenuAdapter extends MenuAdapter {
1472        private ArrayList<MenuItemImpl> mOverflowItems;
1473
1474        public OverflowMenuAdapter(int menuType) {
1475            super(menuType);
1476            mOverflowItems = getNonActionItems(true);
1477        }
1478
1479        @Override
1480        public MenuItemImpl getItem(int position) {
1481            return mOverflowItems.get(position);
1482        }
1483
1484        @Override
1485        public int getCount() {
1486            return mOverflowItems.size();
1487        }
1488    }
1489}
1490