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