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