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