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