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