ActionMenuPresenter.java revision 7541e4bfa0c9fff31bcb12fc5a94365565388715
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        if (!(state instanceof SavedState)) {
578            return;
579        }
580
581        SavedState saved = (SavedState) state;
582        if (saved.openSubMenuId > 0) {
583            MenuItem item = mMenu.findItem(saved.openSubMenuId);
584            if (item != null) {
585                SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu();
586                onSubMenuSelected(subMenu);
587            }
588        }
589    }
590
591    @Override
592    public void onSubUiVisibilityChanged(boolean isVisible) {
593        if (isVisible) {
594            // Not a submenu, but treat it like one.
595            super.onSubMenuSelected(null);
596        } else if (mMenu != null) {
597            mMenu.close(false /* closeAllMenus */);
598        }
599    }
600
601    public void setMenuView(ActionMenuView menuView) {
602        mMenuView = menuView;
603        menuView.initialize(mMenu);
604    }
605
606    private static class SavedState implements Parcelable {
607        public int openSubMenuId;
608
609        SavedState() {
610        }
611
612        SavedState(Parcel in) {
613            openSubMenuId = in.readInt();
614        }
615
616        @Override
617        public int describeContents() {
618            return 0;
619        }
620
621        @Override
622        public void writeToParcel(Parcel dest, int flags) {
623            dest.writeInt(openSubMenuId);
624        }
625
626        public static final Parcelable.Creator<SavedState> CREATOR
627                = new Parcelable.Creator<SavedState>() {
628            public SavedState createFromParcel(Parcel in) {
629                return new SavedState(in);
630            }
631
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            setOnTouchListener(new ForwardingListener(this) {
651                @Override
652                public ShowableListMenu getPopup() {
653                    if (mOverflowPopup == null) {
654                        return null;
655                    }
656
657                    return mOverflowPopup.getPopup();
658                }
659
660                @Override
661                public boolean onForwardingStarted() {
662                    showOverflowMenu();
663                    return true;
664                }
665
666                @Override
667                public boolean onForwardingStopped() {
668                    // Displaying the popup occurs asynchronously, so wait for
669                    // the runnable to finish before deciding whether to stop
670                    // forwarding.
671                    if (mPostedOpenRunnable != null) {
672                        return false;
673                    }
674
675                    hideOverflowMenu();
676                    return true;
677                }
678            });
679        }
680
681        @Override
682        public boolean performClick() {
683            if (super.performClick()) {
684                return true;
685            }
686
687            playSoundEffect(SoundEffectConstants.CLICK);
688            showOverflowMenu();
689            return true;
690        }
691
692        @Override
693        public boolean needsDividerBefore() {
694            return false;
695        }
696
697        @Override
698        public boolean needsDividerAfter() {
699            return false;
700        }
701
702        @Override
703        protected boolean setFrame(int l, int t, int r, int b) {
704            final boolean changed = super.setFrame(l, t, r, b);
705
706            // Set up the hotspot bounds to be centered on the image.
707            final Drawable d = getDrawable();
708            final Drawable bg = getBackground();
709            if (d != null && bg != null) {
710                final int width = getWidth();
711                final int height = getHeight();
712                final int halfEdge = Math.max(width, height) / 2;
713                final int offsetX = getPaddingLeft() - getPaddingRight();
714                final int offsetY = getPaddingTop() - getPaddingBottom();
715                final int centerX = (width + offsetX) / 2;
716                final int centerY = (height + offsetY) / 2;
717                DrawableCompat.setHotspotBounds(bg, centerX - halfEdge, centerY - halfEdge,
718                        centerX + halfEdge, centerY + halfEdge);
719            }
720
721            return changed;
722        }
723    }
724
725    private class OverflowPopup extends MenuPopupHelper {
726        public OverflowPopup(Context context, MenuBuilder menu, View anchorView,
727                boolean overflowOnly) {
728            super(context, menu, anchorView, overflowOnly, R.attr.actionOverflowMenuStyle);
729            setGravity(GravityCompat.END);
730            setPresenterCallback(mPopupPresenterCallback);
731        }
732
733        @Override
734        protected void onDismiss() {
735            if (mMenu != null) {
736                mMenu.close();
737            }
738            mOverflowPopup = null;
739
740            super.onDismiss();
741        }
742    }
743
744    private class ActionButtonSubmenu extends MenuPopupHelper {
745        public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu, View anchorView) {
746            super(context, subMenu, anchorView, false, R.attr.actionOverflowMenuStyle);
747
748            MenuItemImpl item = (MenuItemImpl) subMenu.getItem();
749            if (!item.isActionButton()) {
750                // Give a reasonable anchor to nested submenus.
751                setAnchorView(mOverflowButton == null ? (View) mMenuView : mOverflowButton);
752            }
753
754            setPresenterCallback(mPopupPresenterCallback);
755        }
756
757        @Override
758        protected void onDismiss() {
759            mActionButtonPopup = null;
760            mOpenSubMenuId = 0;
761
762            super.onDismiss();
763        }
764    }
765
766    private class PopupPresenterCallback implements Callback {
767        @Override
768        public boolean onOpenSubMenu(MenuBuilder subMenu) {
769            if (subMenu == null) return false;
770
771            mOpenSubMenuId = ((SubMenuBuilder) subMenu).getItem().getItemId();
772            final Callback cb = getCallback();
773            return cb != null ? cb.onOpenSubMenu(subMenu) : false;
774        }
775
776        @Override
777        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
778            if (menu instanceof SubMenuBuilder) {
779                menu.getRootMenu().close(false /* closeAllMenus */);
780            }
781            final Callback cb = getCallback();
782            if (cb != null) {
783                cb.onCloseMenu(menu, allMenusAreClosing);
784            }
785        }
786    }
787
788    private class OpenOverflowRunnable implements Runnable {
789        private OverflowPopup mPopup;
790
791        public OpenOverflowRunnable(OverflowPopup popup) {
792            mPopup = popup;
793        }
794
795        public void run() {
796            if (mMenu != null) {
797                mMenu.changeMenuMode();
798            }
799            final View menuView = (View) mMenuView;
800            if (menuView != null && menuView.getWindowToken() != null && mPopup.tryShow()) {
801                mOverflowPopup = mPopup;
802            }
803            mPostedOpenRunnable = null;
804        }
805    }
806
807    private class ActionMenuPopupCallback extends ActionMenuItemView.PopupCallback {
808        @Override
809        public ShowableListMenu getPopup() {
810            return mActionButtonPopup != null ? mActionButtonPopup.getPopup() : null;
811        }
812    }
813}
814