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