ActionMenuPresenter.java revision 6142a54baae3289f734947c6b5375b12eb0fb722
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
17package android.support.v7.widget;
18
19import android.content.Context;
20import android.content.res.Configuration;
21import android.content.res.Resources;
22import android.graphics.drawable.Drawable;
23import android.os.Parcel;
24import android.os.Parcelable;
25import android.support.annotation.NonNull;
26import android.support.annotation.Nullable;
27import android.support.v4.graphics.drawable.DrawableCompat;
28import android.support.v4.view.ActionProvider;
29import android.support.v4.view.GravityCompat;
30import android.support.v7.appcompat.R;
31import android.support.v7.transition.ActionBarTransition;
32import android.support.v7.view.ActionBarPolicy;
33import android.support.v7.view.menu.ActionMenuItemView;
34import android.support.v7.view.menu.BaseMenuPresenter;
35import android.support.v7.view.menu.MenuBuilder;
36import android.support.v7.view.menu.MenuItemImpl;
37import android.support.v7.view.menu.MenuPopupHelper;
38import android.support.v7.view.menu.MenuView;
39import android.support.v7.view.menu.ShowableListMenu;
40import android.support.v7.view.menu.SubMenuBuilder;
41import android.util.SparseBooleanArray;
42import android.view.MenuItem;
43import android.view.SoundEffectConstants;
44import android.view.View;
45import android.view.View.MeasureSpec;
46import android.view.ViewGroup;
47
48import java.util.ArrayList;
49
50/**
51 * MenuPresenter for building action menus as seen in the action bar and action modes.
52 */
53class ActionMenuPresenter extends BaseMenuPresenter
54        implements ActionProvider.SubUiVisibilityListener {
55
56    private static final String TAG = "ActionMenuPresenter";
57
58    private 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    private OverflowPopup mOverflowPopup;
79    private ActionButtonSubmenu mActionButtonPopup;
80
81    private 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        final ViewGroup menuViewParent = (ViewGroup) ((View) mMenuView).getParent();
231        if (menuViewParent != null) {
232            ActionBarTransition.beginDelayedTransition(menuViewParent);
233        }
234        super.updateMenuView(cleared);
235
236        ((View) mMenuView).requestLayout();
237
238        if (mMenu != null) {
239            final ArrayList<MenuItemImpl> actionItems = mMenu.getActionItems();
240            final int count = actionItems.size();
241            for (int i = 0; i < count; i++) {
242                final ActionProvider provider = actionItems.get(i).getSupportActionProvider();
243                if (provider != null) {
244                    provider.setSubUiVisibilityListener(this);
245                }
246            }
247        }
248
249        final ArrayList<MenuItemImpl> nonActionItems = mMenu != null ?
250                mMenu.getNonActionItems() : null;
251
252        boolean hasOverflow = false;
253        if (mReserveOverflow && nonActionItems != null) {
254            final int count = nonActionItems.size();
255            if (count == 1) {
256                hasOverflow = !nonActionItems.get(0).isActionViewExpanded();
257            } else {
258                hasOverflow = count > 0;
259            }
260        }
261
262        if (hasOverflow) {
263            if (mOverflowButton == null) {
264                mOverflowButton = new OverflowMenuButton(mSystemContext);
265            }
266            ViewGroup parent = (ViewGroup) mOverflowButton.getParent();
267            if (parent != mMenuView) {
268                if (parent != null) {
269                    parent.removeView(mOverflowButton);
270                }
271                ActionMenuView menuView = (ActionMenuView) mMenuView;
272                menuView.addView(mOverflowButton, menuView.generateOverflowButtonLayoutParams());
273            }
274        } else if (mOverflowButton != null && mOverflowButton.getParent() == mMenuView) {
275            ((ViewGroup) mMenuView).removeView(mOverflowButton);
276        }
277
278        ((ActionMenuView) mMenuView).setOverflowReserved(mReserveOverflow);
279    }
280
281    @Override
282    public boolean filterLeftoverView(ViewGroup parent, int childIndex) {
283        if (parent.getChildAt(childIndex) == mOverflowButton) return false;
284        return super.filterLeftoverView(parent, childIndex);
285    }
286
287    public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
288        if (!subMenu.hasVisibleItems()) return false;
289
290        SubMenuBuilder topSubMenu = subMenu;
291        while (topSubMenu.getParentMenu() != mMenu) {
292            topSubMenu = (SubMenuBuilder) topSubMenu.getParentMenu();
293        }
294        View anchor = findViewForItem(topSubMenu.getItem());
295        if (anchor == null) {
296            // This means the submenu was opened from an overflow menu item, indicating the
297            // MenuPopupHelper will handle opening the submenu via its MenuPopup. Return false to
298            // ensure that the MenuPopup acts as presenter for the submenu, and acts on its
299            // responsibility to display the new submenu.
300            return false;
301        }
302
303        mOpenSubMenuId = subMenu.getItem().getItemId();
304
305        boolean preserveIconSpacing = false;
306        final int count = subMenu.size();
307        for (int i = 0; i < count; i++) {
308            MenuItem childItem = subMenu.getItem(i);
309            if (childItem.isVisible() && childItem.getIcon() != null) {
310                preserveIconSpacing = true;
311                break;
312            }
313        }
314
315        mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu, anchor);
316        mActionButtonPopup.setForceShowIcon(preserveIconSpacing);
317        mActionButtonPopup.show();
318
319        super.onSubMenuSelected(subMenu);
320        return true;
321    }
322
323    private View findViewForItem(MenuItem item) {
324        final ViewGroup parent = (ViewGroup) mMenuView;
325        if (parent == null) return null;
326
327        final int count = parent.getChildCount();
328        for (int i = 0; i < count; i++) {
329            final View child = parent.getChildAt(i);
330            if (child instanceof MenuView.ItemView &&
331                    ((MenuView.ItemView) child).getItemData() == item) {
332                return child;
333            }
334        }
335        return null;
336    }
337
338    /**
339     * Display the overflow menu if one is present.
340     * @return true if the overflow menu was shown, false otherwise.
341     */
342    public boolean showOverflowMenu() {
343        if (mReserveOverflow && !isOverflowMenuShowing() && mMenu != null && mMenuView != null &&
344                mPostedOpenRunnable == null && !mMenu.getNonActionItems().isEmpty()) {
345            OverflowPopup popup = new OverflowPopup(mContext, mMenu, mOverflowButton, true);
346            mPostedOpenRunnable = new OpenOverflowRunnable(popup);
347            // Post this for later; we might still need a layout for the anchor to be right.
348            ((View) mMenuView).post(mPostedOpenRunnable);
349
350            // ActionMenuPresenter uses null as a callback argument here
351            // to indicate overflow is opening.
352            super.onSubMenuSelected(null);
353
354            return true;
355        }
356        return false;
357    }
358
359    /**
360     * Hide the overflow menu if it is currently showing.
361     *
362     * @return true if the overflow menu was hidden, false otherwise.
363     */
364    public boolean hideOverflowMenu() {
365        if (mPostedOpenRunnable != null && mMenuView != null) {
366            ((View) mMenuView).removeCallbacks(mPostedOpenRunnable);
367            mPostedOpenRunnable = null;
368            return true;
369        }
370
371        MenuPopupHelper popup = mOverflowPopup;
372        if (popup != null) {
373            popup.dismiss();
374            return true;
375        }
376        return false;
377    }
378
379    /**
380     * Dismiss all popup menus - overflow and submenus.
381     * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
382     */
383    public boolean dismissPopupMenus() {
384        boolean result = hideOverflowMenu();
385        result |= hideSubMenus();
386        return result;
387    }
388
389    /**
390     * Dismiss all submenu popups.
391     *
392     * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
393     */
394    public boolean hideSubMenus() {
395        if (mActionButtonPopup != null) {
396            mActionButtonPopup.dismiss();
397            return true;
398        }
399        return false;
400    }
401
402    /**
403     * @return true if the overflow menu is currently showing
404     */
405    public boolean isOverflowMenuShowing() {
406        return mOverflowPopup != null && mOverflowPopup.isShowing();
407    }
408
409    public boolean isOverflowMenuShowPending() {
410        return mPostedOpenRunnable != null || isOverflowMenuShowing();
411    }
412
413    /**
414     * @return true if space has been reserved in the action menu for an overflow item.
415     */
416    public boolean isOverflowReserved() {
417        return mReserveOverflow;
418    }
419
420    public boolean flagActionItems() {
421        final ArrayList<MenuItemImpl> visibleItems;
422        final int itemsSize;
423        if (mMenu != null) {
424            visibleItems = mMenu.getVisibleItems();
425            itemsSize = visibleItems.size();
426        } else {
427            visibleItems = null;
428            itemsSize = 0;
429        }
430
431        int maxActions = mMaxItems;
432        int widthLimit = mActionItemWidthLimit;
433        final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
434        final ViewGroup parent = (ViewGroup) mMenuView;
435
436        int requiredItems = 0;
437        int requestedItems = 0;
438        int firstActionWidth = 0;
439        boolean hasOverflow = false;
440        for (int i = 0; i < itemsSize; i++) {
441            MenuItemImpl item = visibleItems.get(i);
442            if (item.requiresActionButton()) {
443                requiredItems++;
444            } else if (item.requestsActionButton()) {
445                requestedItems++;
446            } else {
447                hasOverflow = true;
448            }
449            if (mExpandedActionViewsExclusive && item.isActionViewExpanded()) {
450                // Overflow everything if we have an expanded action view and we're
451                // space constrained.
452                maxActions = 0;
453            }
454        }
455
456        // Reserve a spot for the overflow item if needed.
457        if (mReserveOverflow &&
458                (hasOverflow || requiredItems + requestedItems > maxActions)) {
459            maxActions--;
460        }
461        maxActions -= requiredItems;
462
463        final SparseBooleanArray seenGroups = mActionButtonGroups;
464        seenGroups.clear();
465
466        int cellSize = 0;
467        int cellsRemaining = 0;
468        if (mStrictWidthLimit) {
469            cellsRemaining = widthLimit / mMinCellSize;
470            final int cellSizeRemaining = widthLimit % mMinCellSize;
471            cellSize = mMinCellSize + cellSizeRemaining / cellsRemaining;
472        }
473
474        // Flag as many more requested items as will fit.
475        for (int i = 0; i < itemsSize; i++) {
476            MenuItemImpl item = visibleItems.get(i);
477
478            if (item.requiresActionButton()) {
479                View v = getItemView(item, mScrapActionButtonView, parent);
480                if (mScrapActionButtonView == null) {
481                    mScrapActionButtonView = v;
482                }
483                if (mStrictWidthLimit) {
484                    cellsRemaining -= ActionMenuView.measureChildForCells(v,
485                            cellSize, cellsRemaining, querySpec, 0);
486                } else {
487                    v.measure(querySpec, querySpec);
488                }
489                final int measuredWidth = v.getMeasuredWidth();
490                widthLimit -= measuredWidth;
491                if (firstActionWidth == 0) {
492                    firstActionWidth = measuredWidth;
493                }
494                final int groupId = item.getGroupId();
495                if (groupId != 0) {
496                    seenGroups.put(groupId, true);
497                }
498                item.setIsActionButton(true);
499            } else if (item.requestsActionButton()) {
500                // Items in a group with other items that already have an action slot
501                // can break the max actions rule, but not the width limit.
502                final int groupId = item.getGroupId();
503                final boolean inGroup = seenGroups.get(groupId);
504                boolean isAction = (maxActions > 0 || inGroup) && widthLimit > 0 &&
505                        (!mStrictWidthLimit || cellsRemaining > 0);
506
507                if (isAction) {
508                    View v = getItemView(item, mScrapActionButtonView, parent);
509                    if (mScrapActionButtonView == null) {
510                        mScrapActionButtonView = v;
511                    }
512                    if (mStrictWidthLimit) {
513                        final int cells = ActionMenuView.measureChildForCells(v,
514                                cellSize, cellsRemaining, querySpec, 0);
515                        cellsRemaining -= cells;
516                        if (cells == 0) {
517                            isAction = false;
518                        }
519                    } else {
520                        v.measure(querySpec, querySpec);
521                    }
522                    final int measuredWidth = v.getMeasuredWidth();
523                    widthLimit -= measuredWidth;
524                    if (firstActionWidth == 0) {
525                        firstActionWidth = measuredWidth;
526                    }
527
528                    if (mStrictWidthLimit) {
529                        isAction &= widthLimit >= 0;
530                    } else {
531                        // Did this push the entire first item past the limit?
532                        isAction &= widthLimit + firstActionWidth > 0;
533                    }
534                }
535
536                if (isAction && groupId != 0) {
537                    seenGroups.put(groupId, true);
538                } else if (inGroup) {
539                    // We broke the width limit. Demote the whole group, they all overflow now.
540                    seenGroups.put(groupId, false);
541                    for (int j = 0; j < i; j++) {
542                        MenuItemImpl areYouMyGroupie = visibleItems.get(j);
543                        if (areYouMyGroupie.getGroupId() == groupId) {
544                            // Give back the action slot
545                            if (areYouMyGroupie.isActionButton()) maxActions++;
546                            areYouMyGroupie.setIsActionButton(false);
547                        }
548                    }
549                }
550
551                if (isAction) maxActions--;
552
553                item.setIsActionButton(isAction);
554            } else {
555                // Neither requires nor requests an action button.
556                item.setIsActionButton(false);
557            }
558        }
559        return true;
560    }
561
562    @Override
563    public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
564        dismissPopupMenus();
565        super.onCloseMenu(menu, allMenusAreClosing);
566    }
567
568    @Override
569    public Parcelable onSaveInstanceState() {
570        SavedState state = new SavedState();
571        state.openSubMenuId = mOpenSubMenuId;
572        return state;
573    }
574
575    @Override
576    public void onRestoreInstanceState(Parcelable state) {
577        SavedState saved = (SavedState) state;
578        if (saved.openSubMenuId > 0) {
579            MenuItem item = mMenu.findItem(saved.openSubMenuId);
580            if (item != null) {
581                SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu();
582                onSubMenuSelected(subMenu);
583            }
584        }
585    }
586
587    @Override
588    public void onSubUiVisibilityChanged(boolean isVisible) {
589        if (isVisible) {
590            // Not a submenu, but treat it like one.
591            super.onSubMenuSelected(null);
592        } else if (mMenu != null) {
593            mMenu.close(false /* closeAllMenus */);
594        }
595    }
596
597    public void setMenuView(ActionMenuView menuView) {
598        mMenuView = menuView;
599        menuView.initialize(mMenu);
600    }
601
602    private static class SavedState implements Parcelable {
603        public int openSubMenuId;
604
605        SavedState() {
606        }
607
608        SavedState(Parcel in) {
609            openSubMenuId = in.readInt();
610        }
611
612        @Override
613        public int describeContents() {
614            return 0;
615        }
616
617        @Override
618        public void writeToParcel(Parcel dest, int flags) {
619            dest.writeInt(openSubMenuId);
620        }
621
622        public static final Parcelable.Creator<SavedState> CREATOR
623                = new Parcelable.Creator<SavedState>() {
624            public SavedState createFromParcel(Parcel in) {
625                return new SavedState(in);
626            }
627
628            public SavedState[] newArray(int size) {
629                return new SavedState[size];
630            }
631        };
632    }
633
634    private class OverflowMenuButton extends AppCompatImageView
635            implements ActionMenuView.ActionMenuChildView {
636        private final float[] mTempPts = new float[2];
637
638        public OverflowMenuButton(Context context) {
639            super(context, null, R.attr.actionOverflowButtonStyle);
640
641            setClickable(true);
642            setFocusable(true);
643            setVisibility(VISIBLE);
644            setEnabled(true);
645
646            setOnTouchListener(new ForwardingListener(this) {
647                @Override
648                public ShowableListMenu getPopup() {
649                    if (mOverflowPopup == null) {
650                        return null;
651                    }
652
653                    return mOverflowPopup.getPopup();
654                }
655
656                @Override
657                public boolean onForwardingStarted() {
658                    showOverflowMenu();
659                    return true;
660                }
661
662                @Override
663                public boolean onForwardingStopped() {
664                    // Displaying the popup occurs asynchronously, so wait for
665                    // the runnable to finish before deciding whether to stop
666                    // forwarding.
667                    if (mPostedOpenRunnable != null) {
668                        return false;
669                    }
670
671                    hideOverflowMenu();
672                    return true;
673                }
674            });
675        }
676
677        @Override
678        public boolean performClick() {
679            if (super.performClick()) {
680                return true;
681            }
682
683            playSoundEffect(SoundEffectConstants.CLICK);
684            showOverflowMenu();
685            return true;
686        }
687
688        @Override
689        public boolean needsDividerBefore() {
690            return false;
691        }
692
693        @Override
694        public boolean needsDividerAfter() {
695            return false;
696        }
697
698        @Override
699        protected boolean setFrame(int l, int t, int r, int b) {
700            final boolean changed = super.setFrame(l, t, r, b);
701
702            // Set up the hotspot bounds to be centered on the image.
703            final Drawable d = getDrawable();
704            final Drawable bg = getBackground();
705            if (d != null && bg != null) {
706                final int width = getWidth();
707                final int height = getHeight();
708                final int halfEdge = Math.max(width, height) / 2;
709                final int offsetX = getPaddingLeft() - getPaddingRight();
710                final int offsetY = getPaddingTop() - getPaddingBottom();
711                final int centerX = (width + offsetX) / 2;
712                final int centerY = (height + offsetY) / 2;
713                DrawableCompat.setHotspotBounds(bg, centerX - halfEdge, centerY - halfEdge,
714                        centerX + halfEdge, centerY + halfEdge);
715            }
716
717            return changed;
718        }
719    }
720
721    private class OverflowPopup extends MenuPopupHelper {
722        public OverflowPopup(Context context, MenuBuilder menu, View anchorView,
723                boolean overflowOnly) {
724            super(context, menu, anchorView, overflowOnly, R.attr.actionOverflowMenuStyle);
725            setGravity(GravityCompat.END);
726            setPresenterCallback(mPopupPresenterCallback);
727        }
728
729        @Override
730        protected void onDismiss() {
731            if (mMenu != null) {
732                mMenu.close();
733            }
734            mOverflowPopup = null;
735
736            super.onDismiss();
737        }
738    }
739
740    private class ActionButtonSubmenu extends MenuPopupHelper {
741        public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu, View anchorView) {
742            super(context, subMenu, anchorView, false, R.attr.actionOverflowMenuStyle);
743
744            MenuItemImpl item = (MenuItemImpl) subMenu.getItem();
745            if (!item.isActionButton()) {
746                // Give a reasonable anchor to nested submenus.
747                setAnchorView(mOverflowButton == null ? (View) mMenuView : mOverflowButton);
748            }
749
750            setPresenterCallback(mPopupPresenterCallback);
751        }
752
753        @Override
754        protected void onDismiss() {
755            mActionButtonPopup = null;
756            mOpenSubMenuId = 0;
757
758            super.onDismiss();
759        }
760    }
761
762    private class PopupPresenterCallback implements Callback {
763        @Override
764        public boolean onOpenSubMenu(MenuBuilder subMenu) {
765            if (subMenu == null) return false;
766
767            mOpenSubMenuId = ((SubMenuBuilder) subMenu).getItem().getItemId();
768            final Callback cb = getCallback();
769            return cb != null ? cb.onOpenSubMenu(subMenu) : false;
770        }
771
772        @Override
773        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
774            if (menu instanceof SubMenuBuilder) {
775                menu.getRootMenu().close(false /* closeAllMenus */);
776            }
777            final Callback cb = getCallback();
778            if (cb != null) {
779                cb.onCloseMenu(menu, allMenusAreClosing);
780            }
781        }
782    }
783
784    private class OpenOverflowRunnable implements Runnable {
785        private OverflowPopup mPopup;
786
787        public OpenOverflowRunnable(OverflowPopup popup) {
788            mPopup = popup;
789        }
790
791        public void run() {
792            if (mMenu != null) {
793                mMenu.changeMenuMode();
794            }
795            final View menuView = (View) mMenuView;
796            if (menuView != null && menuView.getWindowToken() != null && mPopup.tryShow()) {
797                mOverflowPopup = mPopup;
798            }
799            mPostedOpenRunnable = null;
800        }
801    }
802
803    private class ActionMenuPopupCallback extends ActionMenuItemView.PopupCallback {
804        @Override
805        public ShowableListMenu getPopup() {
806            return mActionButtonPopup != null ? mActionButtonPopup.getPopup() : null;
807        }
808    }
809}
810