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