ActionMenuView.java revision 1970cfd4ef97a5a1b5dc385b114d615d858c359b
1/*
2 * Copyright (C) 2010 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 */
16package android.widget;
17
18import android.content.Context;
19import android.content.res.Configuration;
20import android.util.AttributeSet;
21import android.view.ContextThemeWrapper;
22import android.view.Gravity;
23import android.view.Menu;
24import android.view.MenuItem;
25import android.view.View;
26import android.view.ViewDebug;
27import android.view.ViewGroup;
28import android.view.accessibility.AccessibilityEvent;
29import com.android.internal.view.menu.ActionMenuItemView;
30import com.android.internal.view.menu.MenuBuilder;
31import com.android.internal.view.menu.MenuItemImpl;
32import com.android.internal.view.menu.MenuPresenter;
33import com.android.internal.view.menu.MenuView;
34
35/**
36 * ActionMenuView is a presentation of a series of menu options as a View. It provides
37 * several top level options as action buttons while spilling remaining options over as
38 * items in an overflow menu. This allows applications to present packs of actions inline with
39 * specific or repeating content.
40 */
41public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvoker, MenuView {
42    private static final String TAG = "ActionMenuView";
43
44    static final int MIN_CELL_SIZE = 56; // dips
45    static final int GENERATED_ITEM_PADDING = 4; // dips
46
47    private MenuBuilder mMenu;
48
49    /** Context against which to inflate popup menus. */
50    private Context mPopupContext;
51
52    /** Theme resource against which to inflate popup menus. */
53    private int mPopupTheme;
54
55    private boolean mReserveOverflow;
56    private ActionMenuPresenter mPresenter;
57    private MenuPresenter.Callback mActionMenuPresenterCallback;
58    private MenuBuilder.Callback mMenuBuilderCallback;
59    private boolean mFormatItems;
60    private int mFormatItemsWidth;
61    private int mMinCellSize;
62    private int mGeneratedItemPadding;
63
64    private OnMenuItemClickListener mOnMenuItemClickListener;
65
66    public ActionMenuView(Context context) {
67        this(context, null);
68    }
69
70    public ActionMenuView(Context context, AttributeSet attrs) {
71        super(context, attrs);
72        setBaselineAligned(false);
73        final float density = context.getResources().getDisplayMetrics().density;
74        mMinCellSize = (int) (MIN_CELL_SIZE * density);
75        mGeneratedItemPadding = (int) (GENERATED_ITEM_PADDING * density);
76        mPopupContext = context;
77        mPopupTheme = 0;
78    }
79
80    /**
81     * Specifies the theme to use when inflating popup menus. By default, uses
82     * the same theme as the action menu view itself.
83     *
84     * @param resId theme used to inflate popup menus
85     * @see #getPopupTheme()
86     */
87    public void setPopupTheme(int resId) {
88        if (mPopupTheme != resId) {
89            mPopupTheme = resId;
90            if (resId == 0) {
91                mPopupContext = mContext;
92            } else {
93                mPopupContext = new ContextThemeWrapper(mContext, resId);
94            }
95        }
96    }
97
98    /**
99     * @return resource identifier of the theme used to inflate popup menus, or
100     *         0 if menus are inflated against the action menu view theme
101     * @see #setPopupTheme(int)
102     */
103    public int getPopupTheme() {
104        return mPopupTheme;
105    }
106
107    /**
108     * @param presenter Menu presenter used to display popup menu
109     * @hide
110     */
111    public void setPresenter(ActionMenuPresenter presenter) {
112        mPresenter = presenter;
113        mPresenter.setMenuView(this);
114    }
115
116    @Override
117    public void onConfigurationChanged(Configuration newConfig) {
118        super.onConfigurationChanged(newConfig);
119        mPresenter.updateMenuView(false);
120
121        if (mPresenter != null && mPresenter.isOverflowMenuShowing()) {
122            mPresenter.hideOverflowMenu();
123            mPresenter.showOverflowMenu();
124        }
125    }
126
127    public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
128        mOnMenuItemClickListener = listener;
129    }
130
131    @Override
132    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
133        // If we've been given an exact size to match, apply special formatting during layout.
134        final boolean wasFormatted = mFormatItems;
135        mFormatItems = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY;
136
137        if (wasFormatted != mFormatItems) {
138            mFormatItemsWidth = 0; // Reset this when switching modes
139        }
140
141        // Special formatting can change whether items can fit as action buttons.
142        // Kick the menu and update presenters when this changes.
143        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
144        if (mFormatItems && mMenu != null && widthSize != mFormatItemsWidth) {
145            mFormatItemsWidth = widthSize;
146            mMenu.onItemsChanged(true);
147        }
148
149        final int childCount = getChildCount();
150        if (mFormatItems && childCount > 0) {
151            onMeasureExactFormat(widthMeasureSpec, heightMeasureSpec);
152        } else {
153            // Previous measurement at exact format may have set margins - reset them.
154            for (int i = 0; i < childCount; i++) {
155                final View child = getChildAt(i);
156                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
157                lp.leftMargin = lp.rightMargin = 0;
158            }
159            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
160        }
161    }
162
163    private void onMeasureExactFormat(int widthMeasureSpec, int heightMeasureSpec) {
164        // We already know the width mode is EXACTLY if we're here.
165        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
166        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
167        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
168
169        final int widthPadding = getPaddingLeft() + getPaddingRight();
170        final int heightPadding = getPaddingTop() + getPaddingBottom();
171
172        final int itemHeightSpec = getChildMeasureSpec(heightMeasureSpec, heightPadding,
173                ViewGroup.LayoutParams.WRAP_CONTENT);
174
175        widthSize -= widthPadding;
176
177        // Divide the view into cells.
178        final int cellCount = widthSize / mMinCellSize;
179        final int cellSizeRemaining = widthSize % mMinCellSize;
180
181        if (cellCount == 0) {
182            // Give up, nothing fits.
183            setMeasuredDimension(widthSize, 0);
184            return;
185        }
186
187        final int cellSize = mMinCellSize + cellSizeRemaining / cellCount;
188
189        int cellsRemaining = cellCount;
190        int maxChildHeight = 0;
191        int maxCellsUsed = 0;
192        int expandableItemCount = 0;
193        int visibleItemCount = 0;
194        boolean hasOverflow = false;
195
196        // This is used as a bitfield to locate the smallest items present. Assumes childCount < 64.
197        long smallestItemsAt = 0;
198
199        final int childCount = getChildCount();
200        for (int i = 0; i < childCount; i++) {
201            final View child = getChildAt(i);
202            if (child.getVisibility() == GONE) continue;
203
204            final boolean isGeneratedItem = child instanceof ActionMenuItemView;
205            visibleItemCount++;
206
207            if (isGeneratedItem) {
208                // Reset padding for generated menu item views; it may change below
209                // and views are recycled.
210                child.setPadding(mGeneratedItemPadding, 0, mGeneratedItemPadding, 0);
211            }
212
213            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
214            lp.expanded = false;
215            lp.extraPixels = 0;
216            lp.cellsUsed = 0;
217            lp.expandable = false;
218            lp.leftMargin = 0;
219            lp.rightMargin = 0;
220            lp.preventEdgeOffset = isGeneratedItem && ((ActionMenuItemView) child).hasText();
221
222            // Overflow always gets 1 cell. No more, no less.
223            final int cellsAvailable = lp.isOverflowButton ? 1 : cellsRemaining;
224
225            final int cellsUsed = measureChildForCells(child, cellSize, cellsAvailable,
226                    itemHeightSpec, heightPadding);
227
228            maxCellsUsed = Math.max(maxCellsUsed, cellsUsed);
229            if (lp.expandable) expandableItemCount++;
230            if (lp.isOverflowButton) hasOverflow = true;
231
232            cellsRemaining -= cellsUsed;
233            maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight());
234            if (cellsUsed == 1) smallestItemsAt |= (1 << i);
235        }
236
237        // When we have overflow and a single expanded (text) item, we want to try centering it
238        // visually in the available space even though overflow consumes some of it.
239        final boolean centerSingleExpandedItem = hasOverflow && visibleItemCount == 2;
240
241        // Divide space for remaining cells if we have items that can expand.
242        // Try distributing whole leftover cells to smaller items first.
243
244        boolean needsExpansion = false;
245        while (expandableItemCount > 0 && cellsRemaining > 0) {
246            int minCells = Integer.MAX_VALUE;
247            long minCellsAt = 0; // Bit locations are indices of relevant child views
248            int minCellsItemCount = 0;
249            for (int i = 0; i < childCount; i++) {
250                final View child = getChildAt(i);
251                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
252
253                // Don't try to expand items that shouldn't.
254                if (!lp.expandable) continue;
255
256                // Mark indices of children that can receive an extra cell.
257                if (lp.cellsUsed < minCells) {
258                    minCells = lp.cellsUsed;
259                    minCellsAt = 1 << i;
260                    minCellsItemCount = 1;
261                } else if (lp.cellsUsed == minCells) {
262                    minCellsAt |= 1 << i;
263                    minCellsItemCount++;
264                }
265            }
266
267            // Items that get expanded will always be in the set of smallest items when we're done.
268            smallestItemsAt |= minCellsAt;
269
270            if (minCellsItemCount > cellsRemaining) break; // Couldn't expand anything evenly. Stop.
271
272            // We have enough cells, all minimum size items will be incremented.
273            minCells++;
274
275            for (int i = 0; i < childCount; i++) {
276                final View child = getChildAt(i);
277                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
278                if ((minCellsAt & (1 << i)) == 0) {
279                    // If this item is already at our small item count, mark it for later.
280                    if (lp.cellsUsed == minCells) smallestItemsAt |= 1 << i;
281                    continue;
282                }
283
284                if (centerSingleExpandedItem && lp.preventEdgeOffset && cellsRemaining == 1) {
285                    // Add padding to this item such that it centers.
286                    child.setPadding(mGeneratedItemPadding + cellSize, 0, mGeneratedItemPadding, 0);
287                }
288                lp.cellsUsed++;
289                lp.expanded = true;
290                cellsRemaining--;
291            }
292
293            needsExpansion = true;
294        }
295
296        // Divide any space left that wouldn't divide along cell boundaries
297        // evenly among the smallest items
298
299        final boolean singleItem = !hasOverflow && visibleItemCount == 1;
300        if (cellsRemaining > 0 && smallestItemsAt != 0 &&
301                (cellsRemaining < visibleItemCount - 1 || singleItem || maxCellsUsed > 1)) {
302            float expandCount = Long.bitCount(smallestItemsAt);
303
304            if (!singleItem) {
305                // The items at the far edges may only expand by half in order to pin to either side.
306                if ((smallestItemsAt & 1) != 0) {
307                    LayoutParams lp = (LayoutParams) getChildAt(0).getLayoutParams();
308                    if (!lp.preventEdgeOffset) expandCount -= 0.5f;
309                }
310                if ((smallestItemsAt & (1 << (childCount - 1))) != 0) {
311                    LayoutParams lp = ((LayoutParams) getChildAt(childCount - 1).getLayoutParams());
312                    if (!lp.preventEdgeOffset) expandCount -= 0.5f;
313                }
314            }
315
316            final int extraPixels = expandCount > 0 ?
317                    (int) (cellsRemaining * cellSize / expandCount) : 0;
318
319            for (int i = 0; i < childCount; i++) {
320                if ((smallestItemsAt & (1 << i)) == 0) continue;
321
322                final View child = getChildAt(i);
323                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
324                if (child instanceof ActionMenuItemView) {
325                    // If this is one of our views, expand and measure at the larger size.
326                    lp.extraPixels = extraPixels;
327                    lp.expanded = true;
328                    if (i == 0 && !lp.preventEdgeOffset) {
329                        // First item gets part of its new padding pushed out of sight.
330                        // The last item will get this implicitly from layout.
331                        lp.leftMargin = -extraPixels / 2;
332                    }
333                    needsExpansion = true;
334                } else if (lp.isOverflowButton) {
335                    lp.extraPixels = extraPixels;
336                    lp.expanded = true;
337                    lp.rightMargin = -extraPixels / 2;
338                    needsExpansion = true;
339                } else {
340                    // If we don't know what it is, give it some margins instead
341                    // and let it center within its space. We still want to pin
342                    // against the edges.
343                    if (i != 0) {
344                        lp.leftMargin = extraPixels / 2;
345                    }
346                    if (i != childCount - 1) {
347                        lp.rightMargin = extraPixels / 2;
348                    }
349                }
350            }
351
352            cellsRemaining = 0;
353        }
354
355        // Remeasure any items that have had extra space allocated to them.
356        if (needsExpansion) {
357            for (int i = 0; i < childCount; i++) {
358                final View child = getChildAt(i);
359                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
360
361                if (!lp.expanded) continue;
362
363                final int width = lp.cellsUsed * cellSize + lp.extraPixels;
364                child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
365                        itemHeightSpec);
366            }
367        }
368
369        if (heightMode != MeasureSpec.EXACTLY) {
370            heightSize = maxChildHeight;
371        }
372
373        setMeasuredDimension(widthSize, heightSize);
374    }
375
376    /**
377     * Measure a child view to fit within cell-based formatting. The child's width
378     * will be measured to a whole multiple of cellSize.
379     *
380     * <p>Sets the expandable and cellsUsed fields of LayoutParams.
381     *
382     * @param child Child to measure
383     * @param cellSize Size of one cell
384     * @param cellsRemaining Number of cells remaining that this view can expand to fill
385     * @param parentHeightMeasureSpec MeasureSpec used by the parent view
386     * @param parentHeightPadding Padding present in the parent view
387     * @return Number of cells this child was measured to occupy
388     */
389    static int measureChildForCells(View child, int cellSize, int cellsRemaining,
390            int parentHeightMeasureSpec, int parentHeightPadding) {
391        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
392
393        final int childHeightSize = MeasureSpec.getSize(parentHeightMeasureSpec) -
394                parentHeightPadding;
395        final int childHeightMode = MeasureSpec.getMode(parentHeightMeasureSpec);
396        final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeightSize, childHeightMode);
397
398        final ActionMenuItemView itemView = child instanceof ActionMenuItemView ?
399                (ActionMenuItemView) child : null;
400        final boolean hasText = itemView != null && itemView.hasText();
401
402        int cellsUsed = 0;
403        if (cellsRemaining > 0 && (!hasText || cellsRemaining >= 2)) {
404            final int childWidthSpec = MeasureSpec.makeMeasureSpec(
405                    cellSize * cellsRemaining, MeasureSpec.AT_MOST);
406            child.measure(childWidthSpec, childHeightSpec);
407
408            final int measuredWidth = child.getMeasuredWidth();
409            cellsUsed = measuredWidth / cellSize;
410            if (measuredWidth % cellSize != 0) cellsUsed++;
411            if (hasText && cellsUsed < 2) cellsUsed = 2;
412        }
413
414        final boolean expandable = !lp.isOverflowButton && hasText;
415        lp.expandable = expandable;
416
417        lp.cellsUsed = cellsUsed;
418        final int targetWidth = cellsUsed * cellSize;
419        child.measure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY),
420                childHeightSpec);
421        return cellsUsed;
422    }
423
424    @Override
425    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
426        if (!mFormatItems) {
427            super.onLayout(changed, left, top, right, bottom);
428            return;
429        }
430
431        final int childCount = getChildCount();
432        final int midVertical = (bottom - top) / 2;
433        final int dividerWidth = getDividerWidth();
434        int overflowWidth = 0;
435        int nonOverflowWidth = 0;
436        int nonOverflowCount = 0;
437        int widthRemaining = right - left - getPaddingRight() - getPaddingLeft();
438        boolean hasOverflow = false;
439        final boolean isLayoutRtl = isLayoutRtl();
440        for (int i = 0; i < childCount; i++) {
441            final View v = getChildAt(i);
442            if (v.getVisibility() == GONE) {
443                continue;
444            }
445
446            LayoutParams p = (LayoutParams) v.getLayoutParams();
447            if (p.isOverflowButton) {
448                overflowWidth = v.getMeasuredWidth();
449                if (hasDividerBeforeChildAt(i)) {
450                    overflowWidth += dividerWidth;
451                }
452
453                int height = v.getMeasuredHeight();
454                int r;
455                int l;
456                if (isLayoutRtl) {
457                    l = getPaddingLeft() + p.leftMargin;
458                    r = l + overflowWidth;
459                } else {
460                    r = getWidth() - getPaddingRight() - p.rightMargin;
461                    l = r - overflowWidth;
462                }
463                int t = midVertical - (height / 2);
464                int b = t + height;
465                v.layout(l, t, r, b);
466
467                widthRemaining -= overflowWidth;
468                hasOverflow = true;
469            } else {
470                final int size = v.getMeasuredWidth() + p.leftMargin + p.rightMargin;
471                nonOverflowWidth += size;
472                widthRemaining -= size;
473                if (hasDividerBeforeChildAt(i)) {
474                    nonOverflowWidth += dividerWidth;
475                }
476                nonOverflowCount++;
477            }
478        }
479
480        if (childCount == 1 && !hasOverflow) {
481            // Center a single child
482            final View v = getChildAt(0);
483            final int width = v.getMeasuredWidth();
484            final int height = v.getMeasuredHeight();
485            final int midHorizontal = (right - left) / 2;
486            final int l = midHorizontal - width / 2;
487            final int t = midVertical - height / 2;
488            v.layout(l, t, l + width, t + height);
489            return;
490        }
491
492        final int spacerCount = nonOverflowCount - (hasOverflow ? 0 : 1);
493        final int spacerSize = Math.max(0, spacerCount > 0 ? widthRemaining / spacerCount : 0);
494
495        if (isLayoutRtl) {
496            int startRight = getWidth() - getPaddingRight();
497            for (int i = 0; i < childCount; i++) {
498                final View v = getChildAt(i);
499                final LayoutParams lp = (LayoutParams) v.getLayoutParams();
500                if (v.getVisibility() == GONE || lp.isOverflowButton) {
501                    continue;
502                }
503
504                startRight -= lp.rightMargin;
505                int width = v.getMeasuredWidth();
506                int height = v.getMeasuredHeight();
507                int t = midVertical - height / 2;
508                v.layout(startRight - width, t, startRight, t + height);
509                startRight -= width + lp.leftMargin + spacerSize;
510            }
511        } else {
512            int startLeft = getPaddingLeft();
513            for (int i = 0; i < childCount; i++) {
514                final View v = getChildAt(i);
515                final LayoutParams lp = (LayoutParams) v.getLayoutParams();
516                if (v.getVisibility() == GONE || lp.isOverflowButton) {
517                    continue;
518                }
519
520                startLeft += lp.leftMargin;
521                int width = v.getMeasuredWidth();
522                int height = v.getMeasuredHeight();
523                int t = midVertical - height / 2;
524                v.layout(startLeft, t, startLeft + width, t + height);
525                startLeft += width + lp.rightMargin + spacerSize;
526            }
527        }
528    }
529
530    @Override
531    public void onDetachedFromWindow() {
532        super.onDetachedFromWindow();
533        dismissPopupMenus();
534    }
535
536    /** @hide */
537    public boolean isOverflowReserved() {
538        return mReserveOverflow;
539    }
540
541    /** @hide */
542    public void setOverflowReserved(boolean reserveOverflow) {
543        mReserveOverflow = reserveOverflow;
544    }
545
546    @Override
547    protected LayoutParams generateDefaultLayoutParams() {
548        LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
549                LayoutParams.WRAP_CONTENT);
550        params.gravity = Gravity.CENTER_VERTICAL;
551        return params;
552    }
553
554    @Override
555    public LayoutParams generateLayoutParams(AttributeSet attrs) {
556        return new LayoutParams(getContext(), attrs);
557    }
558
559    @Override
560    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
561        if (p != null) {
562            final LayoutParams result = p instanceof LayoutParams
563                    ? new LayoutParams((LayoutParams) p)
564                    : new LayoutParams(p);
565            if (result.gravity <= Gravity.NO_GRAVITY) {
566                result.gravity = Gravity.CENTER_VERTICAL;
567            }
568            return result;
569        }
570        return generateDefaultLayoutParams();
571    }
572
573    @Override
574    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
575        return p != null && p instanceof LayoutParams;
576    }
577
578    /** @hide */
579    public LayoutParams generateOverflowButtonLayoutParams() {
580        LayoutParams result = generateDefaultLayoutParams();
581        result.isOverflowButton = true;
582        return result;
583    }
584
585    /** @hide */
586    public boolean invokeItem(MenuItemImpl item) {
587        return mMenu.performItemAction(item, 0);
588    }
589
590    /** @hide */
591    public int getWindowAnimations() {
592        return 0;
593    }
594
595    /** @hide */
596    public void initialize(MenuBuilder menu) {
597        mMenu = menu;
598    }
599
600    /**
601     * Returns the Menu object that this ActionMenuView is currently presenting.
602     *
603     * <p>Applications should use this method to obtain the ActionMenuView's Menu object
604     * and inflate or add content to it as necessary.</p>
605     *
606     * @return the Menu presented by this view
607     */
608    public Menu getMenu() {
609        if (mMenu == null) {
610            final Context context = getContext();
611            mMenu = new MenuBuilder(context);
612            mMenu.setCallback(new MenuBuilderCallback());
613            mPresenter = new ActionMenuPresenter(context);
614            mPresenter.setReserveOverflow(true);
615            mPresenter.setCallback(mActionMenuPresenterCallback != null
616                    ? mActionMenuPresenterCallback : new ActionMenuPresenterCallback());
617            mMenu.addMenuPresenter(mPresenter, mPopupContext);
618            mPresenter.setMenuView(this);
619        }
620
621        return mMenu;
622    }
623
624    /**
625     * Must be called before the first call to getMenu()
626     * @hide
627     */
628    public void setMenuCallbacks(MenuPresenter.Callback pcb, MenuBuilder.Callback mcb) {
629        mActionMenuPresenterCallback = pcb;
630        mMenuBuilderCallback = mcb;
631    }
632
633    /**
634     * Returns the current menu or null if one has not yet been configured.
635     * @hide Internal use only for action bar integration
636     */
637    public MenuBuilder peekMenu() {
638        return mMenu;
639    }
640
641    /**
642     * Show the overflow items from the associated menu.
643     *
644     * @return true if the menu was able to be shown, false otherwise
645     */
646    public boolean showOverflowMenu() {
647        return mPresenter != null && mPresenter.showOverflowMenu();
648    }
649
650    /**
651     * Hide the overflow items from the associated menu.
652     *
653     * @return true if the menu was able to be hidden, false otherwise
654     */
655    public boolean hideOverflowMenu() {
656        return mPresenter != null && mPresenter.hideOverflowMenu();
657    }
658
659    /**
660     * Check whether the overflow menu is currently showing. This may not reflect
661     * a pending show operation in progress.
662     *
663     * @return true if the overflow menu is currently showing
664     */
665    public boolean isOverflowMenuShowing() {
666        return mPresenter != null && mPresenter.isOverflowMenuShowing();
667    }
668
669    /** @hide */
670    public boolean isOverflowMenuShowPending() {
671        return mPresenter != null && mPresenter.isOverflowMenuShowPending();
672    }
673
674    /**
675     * Dismiss any popups associated with this menu view.
676     */
677    public void dismissPopupMenus() {
678        if (mPresenter != null) {
679            mPresenter.dismissPopupMenus();
680        }
681    }
682
683    /**
684     * @hide Private LinearLayout (superclass) API. Un-hide if LinearLayout API is made public.
685     */
686    @Override
687    protected boolean hasDividerBeforeChildAt(int childIndex) {
688        if (childIndex == 0) {
689            return false;
690        }
691        final View childBefore = getChildAt(childIndex - 1);
692        final View child = getChildAt(childIndex);
693        boolean result = false;
694        if (childIndex < getChildCount() && childBefore instanceof ActionMenuChildView) {
695            result |= ((ActionMenuChildView) childBefore).needsDividerAfter();
696        }
697        if (childIndex > 0 && child instanceof ActionMenuChildView) {
698            result |= ((ActionMenuChildView) child).needsDividerBefore();
699        }
700        return result;
701    }
702
703    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
704        return false;
705    }
706
707    /** @hide */
708    public void setExpandedActionViewsExclusive(boolean exclusive) {
709        mPresenter.setExpandedActionViewsExclusive(exclusive);
710    }
711
712    /**
713     * Interface responsible for receiving menu item click events if the items themselves
714     * do not have individual item click listeners.
715     */
716    public interface OnMenuItemClickListener {
717        /**
718         * This method will be invoked when a menu item is clicked if the item itself did
719         * not already handle the event.
720         *
721         * @param item {@link MenuItem} that was clicked
722         * @return <code>true</code> if the event was handled, <code>false</code> otherwise.
723         */
724        public boolean onMenuItemClick(MenuItem item);
725    }
726
727    private class MenuBuilderCallback implements MenuBuilder.Callback {
728        @Override
729        public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
730            return mOnMenuItemClickListener != null &&
731                    mOnMenuItemClickListener.onMenuItemClick(item);
732        }
733
734        @Override
735        public void onMenuModeChange(MenuBuilder menu) {
736            if (mMenuBuilderCallback != null) {
737                mMenuBuilderCallback.onMenuModeChange(menu);
738            }
739        }
740    }
741
742    private class ActionMenuPresenterCallback implements ActionMenuPresenter.Callback {
743        @Override
744        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
745        }
746
747        @Override
748        public boolean onOpenSubMenu(MenuBuilder subMenu) {
749            return false;
750        }
751    }
752
753    /** @hide */
754    public interface ActionMenuChildView {
755        public boolean needsDividerBefore();
756        public boolean needsDividerAfter();
757    }
758
759    public static class LayoutParams extends LinearLayout.LayoutParams {
760        /** @hide */
761        @ViewDebug.ExportedProperty(category = "layout")
762        public boolean isOverflowButton;
763
764        /** @hide */
765        @ViewDebug.ExportedProperty(category = "layout")
766        public int cellsUsed;
767
768        /** @hide */
769        @ViewDebug.ExportedProperty(category = "layout")
770        public int extraPixels;
771
772        /** @hide */
773        @ViewDebug.ExportedProperty(category = "layout")
774        public boolean expandable;
775
776        /** @hide */
777        @ViewDebug.ExportedProperty(category = "layout")
778        public boolean preventEdgeOffset;
779
780        /** @hide */
781        public boolean expanded;
782
783        public LayoutParams(Context c, AttributeSet attrs) {
784            super(c, attrs);
785        }
786
787        public LayoutParams(ViewGroup.LayoutParams other) {
788            super(other);
789        }
790
791        public LayoutParams(LayoutParams other) {
792            super((LinearLayout.LayoutParams) other);
793            isOverflowButton = other.isOverflowButton;
794        }
795
796        public LayoutParams(int width, int height) {
797            super(width, height);
798            isOverflowButton = false;
799        }
800
801        /** @hide */
802        public LayoutParams(int width, int height, boolean isOverflowButton) {
803            super(width, height);
804            this.isOverflowButton = isOverflowButton;
805        }
806    }
807}
808