Home | History | Annotate | Download | only in menu
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.internal.view.menu;
     18 
     19 import android.content.Context;
     20 import android.content.res.Configuration;
     21 import android.content.res.Resources;
     22 import android.os.Parcel;
     23 import android.os.Parcelable;
     24 import android.transition.Transition;
     25 import android.transition.TransitionManager;
     26 import android.util.SparseBooleanArray;
     27 import android.view.ActionProvider;
     28 import android.view.Gravity;
     29 import android.view.MenuItem;
     30 import android.view.SoundEffectConstants;
     31 import android.view.View;
     32 import android.view.View.MeasureSpec;
     33 import android.view.ViewGroup;
     34 import android.view.accessibility.AccessibilityNodeInfo;
     35 import android.widget.ImageButton;
     36 import android.widget.ListPopupWindow;
     37 import android.widget.ListPopupWindow.ForwardingListener;
     38 import com.android.internal.transition.ActionBarTransition;
     39 import com.android.internal.view.ActionBarPolicy;
     40 import com.android.internal.view.menu.ActionMenuView.ActionMenuChildView;
     41 
     42 import java.util.ArrayList;
     43 
     44 /**
     45  * MenuPresenter for building action menus as seen in the action bar and action modes.
     46  */
     47 public class ActionMenuPresenter extends BaseMenuPresenter
     48         implements ActionProvider.SubUiVisibilityListener {
     49     private static final String TAG = "ActionMenuPresenter";
     50 
     51     private View mOverflowButton;
     52     private boolean mReserveOverflow;
     53     private boolean mReserveOverflowSet;
     54     private int mWidthLimit;
     55     private int mActionItemWidthLimit;
     56     private int mMaxItems;
     57     private boolean mMaxItemsSet;
     58     private boolean mStrictWidthLimit;
     59     private boolean mWidthLimitSet;
     60     private boolean mExpandedActionViewsExclusive;
     61 
     62     private int mMinCellSize;
     63 
     64     // Group IDs that have been added as actions - used temporarily, allocated here for reuse.
     65     private final SparseBooleanArray mActionButtonGroups = new SparseBooleanArray();
     66 
     67     private View mScrapActionButtonView;
     68 
     69     private OverflowPopup mOverflowPopup;
     70     private ActionButtonSubmenu mActionButtonPopup;
     71 
     72     private OpenOverflowRunnable mPostedOpenRunnable;
     73 
     74     final PopupPresenterCallback mPopupPresenterCallback = new PopupPresenterCallback();
     75     int mOpenSubMenuId;
     76 
     77     public ActionMenuPresenter(Context context) {
     78         super(context, com.android.internal.R.layout.action_menu_layout,
     79                 com.android.internal.R.layout.action_menu_item_layout);
     80     }
     81 
     82     @Override
     83     public void initForMenu(Context context, MenuBuilder menu) {
     84         super.initForMenu(context, menu);
     85 
     86         final Resources res = context.getResources();
     87 
     88         final ActionBarPolicy abp = ActionBarPolicy.get(context);
     89         if (!mReserveOverflowSet) {
     90             mReserveOverflow = abp.showsOverflowMenuButton();
     91         }
     92 
     93         if (!mWidthLimitSet) {
     94             mWidthLimit = abp.getEmbeddedMenuWidthLimit();
     95         }
     96 
     97         // Measure for initial configuration
     98         if (!mMaxItemsSet) {
     99             mMaxItems = abp.getMaxActionButtons();
    100         }
    101 
    102         int width = mWidthLimit;
    103         if (mReserveOverflow) {
    104             if (mOverflowButton == null) {
    105                 mOverflowButton = new OverflowMenuButton(mSystemContext);
    106                 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
    107                 mOverflowButton.measure(spec, spec);
    108             }
    109             width -= mOverflowButton.getMeasuredWidth();
    110         } else {
    111             mOverflowButton = null;
    112         }
    113 
    114         mActionItemWidthLimit = width;
    115 
    116         mMinCellSize = (int) (ActionMenuView.MIN_CELL_SIZE * res.getDisplayMetrics().density);
    117 
    118         // Drop a scrap view as it may no longer reflect the proper context/config.
    119         mScrapActionButtonView = null;
    120     }
    121 
    122     public void onConfigurationChanged(Configuration newConfig) {
    123         if (!mMaxItemsSet) {
    124             mMaxItems = mContext.getResources().getInteger(
    125                     com.android.internal.R.integer.max_action_buttons);
    126         }
    127         if (mMenu != null) {
    128             mMenu.onItemsChanged(true);
    129         }
    130     }
    131 
    132     public void setWidthLimit(int width, boolean strict) {
    133         mWidthLimit = width;
    134         mStrictWidthLimit = strict;
    135         mWidthLimitSet = true;
    136     }
    137 
    138     public void setReserveOverflow(boolean reserveOverflow) {
    139         mReserveOverflow = reserveOverflow;
    140         mReserveOverflowSet = true;
    141     }
    142 
    143     public void setItemLimit(int itemCount) {
    144         mMaxItems = itemCount;
    145         mMaxItemsSet = true;
    146     }
    147 
    148     public void setExpandedActionViewsExclusive(boolean isExclusive) {
    149         mExpandedActionViewsExclusive = isExclusive;
    150     }
    151 
    152     @Override
    153     public MenuView getMenuView(ViewGroup root) {
    154         MenuView result = super.getMenuView(root);
    155         ((ActionMenuView) result).setPresenter(this);
    156         return result;
    157     }
    158 
    159     @Override
    160     public View getItemView(final MenuItemImpl item, View convertView, ViewGroup parent) {
    161         View actionView = item.getActionView();
    162         if (actionView == null || item.hasCollapsibleActionView()) {
    163             actionView = super.getItemView(item, convertView, parent);
    164         }
    165         actionView.setVisibility(item.isActionViewExpanded() ? View.GONE : View.VISIBLE);
    166 
    167         if (item.hasSubMenu()) {
    168             actionView.setOnTouchListener(new ForwardingListener(actionView) {
    169                 @Override
    170                 public ListPopupWindow getPopup() {
    171                     return mActionButtonPopup != null ? mActionButtonPopup.getPopup() : null;
    172                 }
    173 
    174                 @Override
    175                 protected boolean onForwardingStarted() {
    176                     return onSubMenuSelected((SubMenuBuilder) item.getSubMenu());
    177                 }
    178 
    179                 @Override
    180                 protected boolean onForwardingStopped() {
    181                     return dismissPopupMenus();
    182                 }
    183             });
    184         } else {
    185             actionView.setOnTouchListener(null);
    186         }
    187 
    188         final ActionMenuView menuParent = (ActionMenuView) parent;
    189         final ViewGroup.LayoutParams lp = actionView.getLayoutParams();
    190         if (!menuParent.checkLayoutParams(lp)) {
    191             actionView.setLayoutParams(menuParent.generateLayoutParams(lp));
    192         }
    193         return actionView;
    194     }
    195 
    196     @Override
    197     public void bindItemView(MenuItemImpl item, MenuView.ItemView itemView) {
    198         itemView.initialize(item, 0);
    199 
    200         final ActionMenuView menuView = (ActionMenuView) mMenuView;
    201         ActionMenuItemView actionItemView = (ActionMenuItemView) itemView;
    202         actionItemView.setItemInvoker(menuView);
    203     }
    204 
    205     @Override
    206     public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
    207         return item.isActionButton();
    208     }
    209 
    210     @Override
    211     public void updateMenuView(boolean cleared) {
    212         final ViewGroup menuViewParent = (ViewGroup) ((View) mMenuView).getParent();
    213         if (menuViewParent != null) {
    214             ActionBarTransition.beginDelayedTransition(menuViewParent);
    215         }
    216         super.updateMenuView(cleared);
    217 
    218         ((View) mMenuView).requestLayout();
    219 
    220         if (mMenu != null) {
    221             final ArrayList<MenuItemImpl> actionItems = mMenu.getActionItems();
    222             final int count = actionItems.size();
    223             for (int i = 0; i < count; i++) {
    224                 final ActionProvider provider = actionItems.get(i).getActionProvider();
    225                 if (provider != null) {
    226                     provider.setSubUiVisibilityListener(this);
    227                 }
    228             }
    229         }
    230 
    231         final ArrayList<MenuItemImpl> nonActionItems = mMenu != null ?
    232                 mMenu.getNonActionItems() : null;
    233 
    234         boolean hasOverflow = false;
    235         if (mReserveOverflow && nonActionItems != null) {
    236             final int count = nonActionItems.size();
    237             if (count == 1) {
    238                 hasOverflow = !nonActionItems.get(0).isActionViewExpanded();
    239             } else {
    240                 hasOverflow = count > 0;
    241             }
    242         }
    243 
    244         if (hasOverflow) {
    245             if (mOverflowButton == null) {
    246                 mOverflowButton = new OverflowMenuButton(mSystemContext);
    247             }
    248             ViewGroup parent = (ViewGroup) mOverflowButton.getParent();
    249             if (parent != mMenuView) {
    250                 if (parent != null) {
    251                     parent.removeView(mOverflowButton);
    252                 }
    253                 ActionMenuView menuView = (ActionMenuView) mMenuView;
    254                 menuView.addView(mOverflowButton, menuView.generateOverflowButtonLayoutParams());
    255             }
    256         } else if (mOverflowButton != null && mOverflowButton.getParent() == mMenuView) {
    257             ((ViewGroup) mMenuView).removeView(mOverflowButton);
    258         }
    259 
    260         ((ActionMenuView) mMenuView).setOverflowReserved(mReserveOverflow);
    261     }
    262 
    263     @Override
    264     public boolean filterLeftoverView(ViewGroup parent, int childIndex) {
    265         if (parent.getChildAt(childIndex) == mOverflowButton) return false;
    266         return super.filterLeftoverView(parent, childIndex);
    267     }
    268 
    269     public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
    270         if (!subMenu.hasVisibleItems()) return false;
    271 
    272         SubMenuBuilder topSubMenu = subMenu;
    273         while (topSubMenu.getParentMenu() != mMenu) {
    274             topSubMenu = (SubMenuBuilder) topSubMenu.getParentMenu();
    275         }
    276         View anchor = findViewForItem(topSubMenu.getItem());
    277         if (anchor == null) {
    278             if (mOverflowButton == null) return false;
    279             anchor = mOverflowButton;
    280         }
    281 
    282         mOpenSubMenuId = subMenu.getItem().getItemId();
    283         mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu);
    284         mActionButtonPopup.setAnchorView(anchor);
    285         mActionButtonPopup.show();
    286         super.onSubMenuSelected(subMenu);
    287         return true;
    288     }
    289 
    290     private View findViewForItem(MenuItem item) {
    291         final ViewGroup parent = (ViewGroup) mMenuView;
    292         if (parent == null) return null;
    293 
    294         final int count = parent.getChildCount();
    295         for (int i = 0; i < count; i++) {
    296             final View child = parent.getChildAt(i);
    297             if (child instanceof MenuView.ItemView &&
    298                     ((MenuView.ItemView) child).getItemData() == item) {
    299                 return child;
    300             }
    301         }
    302         return null;
    303     }
    304 
    305     /**
    306      * Display the overflow menu if one is present.
    307      * @return true if the overflow menu was shown, false otherwise.
    308      */
    309     public boolean showOverflowMenu() {
    310         if (mReserveOverflow && !isOverflowMenuShowing() && mMenu != null && mMenuView != null &&
    311                 mPostedOpenRunnable == null && !mMenu.getNonActionItems().isEmpty()) {
    312             OverflowPopup popup = new OverflowPopup(mContext, mMenu, mOverflowButton, true);
    313             mPostedOpenRunnable = new OpenOverflowRunnable(popup);
    314             // Post this for later; we might still need a layout for the anchor to be right.
    315             ((View) mMenuView).post(mPostedOpenRunnable);
    316 
    317             // ActionMenuPresenter uses null as a callback argument here
    318             // to indicate overflow is opening.
    319             super.onSubMenuSelected(null);
    320 
    321             return true;
    322         }
    323         return false;
    324     }
    325 
    326     /**
    327      * Hide the overflow menu if it is currently showing.
    328      *
    329      * @return true if the overflow menu was hidden, false otherwise.
    330      */
    331     public boolean hideOverflowMenu() {
    332         if (mPostedOpenRunnable != null && mMenuView != null) {
    333             ((View) mMenuView).removeCallbacks(mPostedOpenRunnable);
    334             mPostedOpenRunnable = null;
    335             return true;
    336         }
    337 
    338         MenuPopupHelper popup = mOverflowPopup;
    339         if (popup != null) {
    340             popup.dismiss();
    341             return true;
    342         }
    343         return false;
    344     }
    345 
    346     /**
    347      * Dismiss all popup menus - overflow and submenus.
    348      * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
    349      */
    350     public boolean dismissPopupMenus() {
    351         boolean result = hideOverflowMenu();
    352         result |= hideSubMenus();
    353         return result;
    354     }
    355 
    356     /**
    357      * Dismiss all submenu popups.
    358      *
    359      * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
    360      */
    361     public boolean hideSubMenus() {
    362         if (mActionButtonPopup != null) {
    363             mActionButtonPopup.dismiss();
    364             return true;
    365         }
    366         return false;
    367     }
    368 
    369     /**
    370      * @return true if the overflow menu is currently showing
    371      */
    372     public boolean isOverflowMenuShowing() {
    373         return mOverflowPopup != null && mOverflowPopup.isShowing();
    374     }
    375 
    376     public boolean isOverflowMenuShowPending() {
    377         return mPostedOpenRunnable != null || isOverflowMenuShowing();
    378     }
    379 
    380     /**
    381      * @return true if space has been reserved in the action menu for an overflow item.
    382      */
    383     public boolean isOverflowReserved() {
    384         return mReserveOverflow;
    385     }
    386 
    387     public boolean flagActionItems() {
    388         final ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems();
    389         final int itemsSize = visibleItems.size();
    390         int maxActions = mMaxItems;
    391         int widthLimit = mActionItemWidthLimit;
    392         final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
    393         final ViewGroup parent = (ViewGroup) mMenuView;
    394 
    395         int requiredItems = 0;
    396         int requestedItems = 0;
    397         int firstActionWidth = 0;
    398         boolean hasOverflow = false;
    399         for (int i = 0; i < itemsSize; i++) {
    400             MenuItemImpl item = visibleItems.get(i);
    401             if (item.requiresActionButton()) {
    402                 requiredItems++;
    403             } else if (item.requestsActionButton()) {
    404                 requestedItems++;
    405             } else {
    406                 hasOverflow = true;
    407             }
    408             if (mExpandedActionViewsExclusive && item.isActionViewExpanded()) {
    409                 // Overflow everything if we have an expanded action view and we're
    410                 // space constrained.
    411                 maxActions = 0;
    412             }
    413         }
    414 
    415         // Reserve a spot for the overflow item if needed.
    416         if (mReserveOverflow &&
    417                 (hasOverflow || requiredItems + requestedItems > maxActions)) {
    418             maxActions--;
    419         }
    420         maxActions -= requiredItems;
    421 
    422         final SparseBooleanArray seenGroups = mActionButtonGroups;
    423         seenGroups.clear();
    424 
    425         int cellSize = 0;
    426         int cellsRemaining = 0;
    427         if (mStrictWidthLimit) {
    428             cellsRemaining = widthLimit / mMinCellSize;
    429             final int cellSizeRemaining = widthLimit % mMinCellSize;
    430             cellSize = mMinCellSize + cellSizeRemaining / cellsRemaining;
    431         }
    432 
    433         // Flag as many more requested items as will fit.
    434         for (int i = 0; i < itemsSize; i++) {
    435             MenuItemImpl item = visibleItems.get(i);
    436 
    437             if (item.requiresActionButton()) {
    438                 View v = getItemView(item, mScrapActionButtonView, parent);
    439                 if (mScrapActionButtonView == null) {
    440                     mScrapActionButtonView = v;
    441                 }
    442                 if (mStrictWidthLimit) {
    443                     cellsRemaining -= ActionMenuView.measureChildForCells(v,
    444                             cellSize, cellsRemaining, querySpec, 0);
    445                 } else {
    446                     v.measure(querySpec, querySpec);
    447                 }
    448                 final int measuredWidth = v.getMeasuredWidth();
    449                 widthLimit -= measuredWidth;
    450                 if (firstActionWidth == 0) {
    451                     firstActionWidth = measuredWidth;
    452                 }
    453                 final int groupId = item.getGroupId();
    454                 if (groupId != 0) {
    455                     seenGroups.put(groupId, true);
    456                 }
    457                 item.setIsActionButton(true);
    458             } else if (item.requestsActionButton()) {
    459                 // Items in a group with other items that already have an action slot
    460                 // can break the max actions rule, but not the width limit.
    461                 final int groupId = item.getGroupId();
    462                 final boolean inGroup = seenGroups.get(groupId);
    463                 boolean isAction = (maxActions > 0 || inGroup) && widthLimit > 0 &&
    464                         (!mStrictWidthLimit || cellsRemaining > 0);
    465 
    466                 if (isAction) {
    467                     View v = getItemView(item, mScrapActionButtonView, parent);
    468                     if (mScrapActionButtonView == null) {
    469                         mScrapActionButtonView = v;
    470                     }
    471                     if (mStrictWidthLimit) {
    472                         final int cells = ActionMenuView.measureChildForCells(v,
    473                                 cellSize, cellsRemaining, querySpec, 0);
    474                         cellsRemaining -= cells;
    475                         if (cells == 0) {
    476                             isAction = false;
    477                         }
    478                     } else {
    479                         v.measure(querySpec, querySpec);
    480                     }
    481                     final int measuredWidth = v.getMeasuredWidth();
    482                     widthLimit -= measuredWidth;
    483                     if (firstActionWidth == 0) {
    484                         firstActionWidth = measuredWidth;
    485                     }
    486 
    487                     if (mStrictWidthLimit) {
    488                         isAction &= widthLimit >= 0;
    489                     } else {
    490                         // Did this push the entire first item past the limit?
    491                         isAction &= widthLimit + firstActionWidth > 0;
    492                     }
    493                 }
    494 
    495                 if (isAction && groupId != 0) {
    496                     seenGroups.put(groupId, true);
    497                 } else if (inGroup) {
    498                     // We broke the width limit. Demote the whole group, they all overflow now.
    499                     seenGroups.put(groupId, false);
    500                     for (int j = 0; j < i; j++) {
    501                         MenuItemImpl areYouMyGroupie = visibleItems.get(j);
    502                         if (areYouMyGroupie.getGroupId() == groupId) {
    503                             // Give back the action slot
    504                             if (areYouMyGroupie.isActionButton()) maxActions++;
    505                             areYouMyGroupie.setIsActionButton(false);
    506                         }
    507                     }
    508                 }
    509 
    510                 if (isAction) maxActions--;
    511 
    512                 item.setIsActionButton(isAction);
    513             } else {
    514                 // Neither requires nor requests an action button.
    515                 item.setIsActionButton(false);
    516             }
    517         }
    518         return true;
    519     }
    520 
    521     @Override
    522     public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
    523         dismissPopupMenus();
    524         super.onCloseMenu(menu, allMenusAreClosing);
    525     }
    526 
    527     @Override
    528     public Parcelable onSaveInstanceState() {
    529         SavedState state = new SavedState();
    530         state.openSubMenuId = mOpenSubMenuId;
    531         return state;
    532     }
    533 
    534     @Override
    535     public void onRestoreInstanceState(Parcelable state) {
    536         SavedState saved = (SavedState) state;
    537         if (saved.openSubMenuId > 0) {
    538             MenuItem item = mMenu.findItem(saved.openSubMenuId);
    539             if (item != null) {
    540                 SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu();
    541                 onSubMenuSelected(subMenu);
    542             }
    543         }
    544     }
    545 
    546     @Override
    547     public void onSubUiVisibilityChanged(boolean isVisible) {
    548         if (isVisible) {
    549             // Not a submenu, but treat it like one.
    550             super.onSubMenuSelected(null);
    551         } else {
    552             mMenu.close(false);
    553         }
    554     }
    555 
    556     private static class SavedState implements Parcelable {
    557         public int openSubMenuId;
    558 
    559         SavedState() {
    560         }
    561 
    562         SavedState(Parcel in) {
    563             openSubMenuId = in.readInt();
    564         }
    565 
    566         @Override
    567         public int describeContents() {
    568             return 0;
    569         }
    570 
    571         @Override
    572         public void writeToParcel(Parcel dest, int flags) {
    573             dest.writeInt(openSubMenuId);
    574         }
    575 
    576         public static final Parcelable.Creator<SavedState> CREATOR
    577                 = new Parcelable.Creator<SavedState>() {
    578             public SavedState createFromParcel(Parcel in) {
    579                 return new SavedState(in);
    580             }
    581 
    582             public SavedState[] newArray(int size) {
    583                 return new SavedState[size];
    584             }
    585         };
    586     }
    587 
    588     private class OverflowMenuButton extends ImageButton implements ActionMenuChildView {
    589         public OverflowMenuButton(Context context) {
    590             super(context, null, com.android.internal.R.attr.actionOverflowButtonStyle);
    591 
    592             setClickable(true);
    593             setFocusable(true);
    594             setVisibility(VISIBLE);
    595             setEnabled(true);
    596 
    597             setOnTouchListener(new ForwardingListener(this) {
    598                 @Override
    599                 public ListPopupWindow getPopup() {
    600                     if (mOverflowPopup == null) {
    601                         return null;
    602                     }
    603 
    604                     return mOverflowPopup.getPopup();
    605                 }
    606 
    607                 @Override
    608                 public boolean onForwardingStarted() {
    609                     showOverflowMenu();
    610                     return true;
    611                 }
    612 
    613                 @Override
    614                 public boolean onForwardingStopped() {
    615                     // Displaying the popup occurs asynchronously, so wait for
    616                     // the runnable to finish before deciding whether to stop
    617                     // forwarding.
    618                     if (mPostedOpenRunnable != null) {
    619                         return false;
    620                     }
    621 
    622                     hideOverflowMenu();
    623                     return true;
    624                 }
    625             });
    626         }
    627 
    628         @Override
    629         public boolean performClick() {
    630             if (super.performClick()) {
    631                 return true;
    632             }
    633 
    634             playSoundEffect(SoundEffectConstants.CLICK);
    635             showOverflowMenu();
    636             return true;
    637         }
    638 
    639         @Override
    640         public boolean needsDividerBefore() {
    641             return false;
    642         }
    643 
    644         @Override
    645         public boolean needsDividerAfter() {
    646             return false;
    647         }
    648 
    649         @Override
    650         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    651             if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
    652                 // Fill available height
    653                 heightMeasureSpec = MeasureSpec.makeMeasureSpec(
    654                         MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.EXACTLY);
    655             }
    656             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    657         }
    658 
    659         @Override
    660         public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
    661             super.onInitializeAccessibilityNodeInfo(info);
    662             info.setCanOpenPopup(true);
    663         }
    664     }
    665 
    666     private class OverflowPopup extends MenuPopupHelper {
    667         public OverflowPopup(Context context, MenuBuilder menu, View anchorView,
    668                 boolean overflowOnly) {
    669             super(context, menu, anchorView, overflowOnly);
    670             setGravity(Gravity.END);
    671             setCallback(mPopupPresenterCallback);
    672         }
    673 
    674         @Override
    675         public void onDismiss() {
    676             super.onDismiss();
    677             mMenu.close();
    678             mOverflowPopup = null;
    679         }
    680     }
    681 
    682     private class ActionButtonSubmenu extends MenuPopupHelper {
    683         private SubMenuBuilder mSubMenu;
    684 
    685         public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu) {
    686             super(context, subMenu);
    687             mSubMenu = subMenu;
    688 
    689             MenuItemImpl item = (MenuItemImpl) subMenu.getItem();
    690             if (!item.isActionButton()) {
    691                 // Give a reasonable anchor to nested submenus.
    692                 setAnchorView(mOverflowButton == null ? (View) mMenuView : mOverflowButton);
    693             }
    694 
    695             setCallback(mPopupPresenterCallback);
    696 
    697             boolean preserveIconSpacing = false;
    698             final int count = subMenu.size();
    699             for (int i = 0; i < count; i++) {
    700                 MenuItem childItem = subMenu.getItem(i);
    701                 if (childItem.isVisible() && childItem.getIcon() != null) {
    702                     preserveIconSpacing = true;
    703                     break;
    704                 }
    705             }
    706             setForceShowIcon(preserveIconSpacing);
    707         }
    708 
    709         @Override
    710         public void onDismiss() {
    711             super.onDismiss();
    712             mActionButtonPopup = null;
    713             mOpenSubMenuId = 0;
    714         }
    715     }
    716 
    717     private class PopupPresenterCallback implements MenuPresenter.Callback {
    718 
    719         @Override
    720         public boolean onOpenSubMenu(MenuBuilder subMenu) {
    721             if (subMenu == null) return false;
    722 
    723             mOpenSubMenuId = ((SubMenuBuilder) subMenu).getItem().getItemId();
    724             final MenuPresenter.Callback cb = getCallback();
    725             return cb != null ? cb.onOpenSubMenu(subMenu) : false;
    726         }
    727 
    728         @Override
    729         public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
    730             if (menu instanceof SubMenuBuilder) {
    731                 ((SubMenuBuilder) menu).getRootMenu().close(false);
    732             }
    733             final MenuPresenter.Callback cb = getCallback();
    734             if (cb != null) {
    735                 cb.onCloseMenu(menu, allMenusAreClosing);
    736             }
    737         }
    738     }
    739 
    740     private class OpenOverflowRunnable implements Runnable {
    741         private OverflowPopup mPopup;
    742 
    743         public OpenOverflowRunnable(OverflowPopup popup) {
    744             mPopup = popup;
    745         }
    746 
    747         public void run() {
    748             mMenu.changeMenuMode();
    749             final View menuView = (View) mMenuView;
    750             if (menuView != null && menuView.getWindowToken() != null && mPopup.tryShow()) {
    751                 mOverflowPopup = mPopup;
    752             }
    753             mPostedOpenRunnable = null;
    754         }
    755     }
    756 }
    757