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