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.Nullable;
20import android.annotation.StyleRes;
21import android.content.Context;
22import android.content.res.Configuration;
23import android.graphics.drawable.Drawable;
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    /**
545     * Set the icon to use for the overflow button.
546     *
547     * @param icon Drawable to set, may be null to clear the icon
548     */
549    public void setOverflowIcon(@Nullable Drawable icon) {
550        getMenu();
551        mPresenter.setOverflowIcon(icon);
552    }
553
554    /**
555     * Return the current drawable used as the overflow icon.
556     *
557     * @return The overflow icon drawable
558     */
559    @Nullable
560    public Drawable getOverflowIcon() {
561        getMenu();
562        return mPresenter.getOverflowIcon();
563    }
564
565    /** @hide */
566    public boolean isOverflowReserved() {
567        return mReserveOverflow;
568    }
569
570    /** @hide */
571    public void setOverflowReserved(boolean reserveOverflow) {
572        mReserveOverflow = reserveOverflow;
573    }
574
575    @Override
576    protected LayoutParams generateDefaultLayoutParams() {
577        LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
578                LayoutParams.WRAP_CONTENT);
579        params.gravity = Gravity.CENTER_VERTICAL;
580        return params;
581    }
582
583    @Override
584    public LayoutParams generateLayoutParams(AttributeSet attrs) {
585        return new LayoutParams(getContext(), attrs);
586    }
587
588    @Override
589    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
590        if (p != null) {
591            final LayoutParams result = p instanceof LayoutParams
592                    ? new LayoutParams((LayoutParams) p)
593                    : new LayoutParams(p);
594            if (result.gravity <= Gravity.NO_GRAVITY) {
595                result.gravity = Gravity.CENTER_VERTICAL;
596            }
597            return result;
598        }
599        return generateDefaultLayoutParams();
600    }
601
602    @Override
603    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
604        return p != null && p instanceof LayoutParams;
605    }
606
607    /** @hide */
608    public LayoutParams generateOverflowButtonLayoutParams() {
609        LayoutParams result = generateDefaultLayoutParams();
610        result.isOverflowButton = true;
611        return result;
612    }
613
614    /** @hide */
615    public boolean invokeItem(MenuItemImpl item) {
616        return mMenu.performItemAction(item, 0);
617    }
618
619    /** @hide */
620    public int getWindowAnimations() {
621        return 0;
622    }
623
624    /** @hide */
625    public void initialize(@Nullable MenuBuilder menu) {
626        mMenu = menu;
627    }
628
629    /**
630     * Returns the Menu object that this ActionMenuView is currently presenting.
631     *
632     * <p>Applications should use this method to obtain the ActionMenuView's Menu object
633     * and inflate or add content to it as necessary.</p>
634     *
635     * @return the Menu presented by this view
636     */
637    public Menu getMenu() {
638        if (mMenu == null) {
639            final Context context = getContext();
640            mMenu = new MenuBuilder(context);
641            mMenu.setCallback(new MenuBuilderCallback());
642            mPresenter = new ActionMenuPresenter(context);
643            mPresenter.setReserveOverflow(true);
644            mPresenter.setCallback(mActionMenuPresenterCallback != null
645                    ? mActionMenuPresenterCallback : new ActionMenuPresenterCallback());
646            mMenu.addMenuPresenter(mPresenter, mPopupContext);
647            mPresenter.setMenuView(this);
648        }
649
650        return mMenu;
651    }
652
653    /**
654     * Must be called before the first call to getMenu()
655     * @hide
656     */
657    public void setMenuCallbacks(MenuPresenter.Callback pcb, MenuBuilder.Callback mcb) {
658        mActionMenuPresenterCallback = pcb;
659        mMenuBuilderCallback = mcb;
660    }
661
662    /**
663     * Returns the current menu or null if one has not yet been configured.
664     * @hide Internal use only for action bar integration
665     */
666    public MenuBuilder peekMenu() {
667        return mMenu;
668    }
669
670    /**
671     * Show the overflow items from the associated menu.
672     *
673     * @return true if the menu was able to be shown, false otherwise
674     */
675    public boolean showOverflowMenu() {
676        return mPresenter != null && mPresenter.showOverflowMenu();
677    }
678
679    /**
680     * Hide the overflow items from the associated menu.
681     *
682     * @return true if the menu was able to be hidden, false otherwise
683     */
684    public boolean hideOverflowMenu() {
685        return mPresenter != null && mPresenter.hideOverflowMenu();
686    }
687
688    /**
689     * Check whether the overflow menu is currently showing. This may not reflect
690     * a pending show operation in progress.
691     *
692     * @return true if the overflow menu is currently showing
693     */
694    public boolean isOverflowMenuShowing() {
695        return mPresenter != null && mPresenter.isOverflowMenuShowing();
696    }
697
698    /** @hide */
699    public boolean isOverflowMenuShowPending() {
700        return mPresenter != null && mPresenter.isOverflowMenuShowPending();
701    }
702
703    /**
704     * Dismiss any popups associated with this menu view.
705     */
706    public void dismissPopupMenus() {
707        if (mPresenter != null) {
708            mPresenter.dismissPopupMenus();
709        }
710    }
711
712    /**
713     * @hide Private LinearLayout (superclass) API. Un-hide if LinearLayout API is made public.
714     */
715    @Override
716    protected boolean hasDividerBeforeChildAt(int childIndex) {
717        if (childIndex == 0) {
718            return false;
719        }
720        final View childBefore = getChildAt(childIndex - 1);
721        final View child = getChildAt(childIndex);
722        boolean result = false;
723        if (childIndex < getChildCount() && childBefore instanceof ActionMenuChildView) {
724            result |= ((ActionMenuChildView) childBefore).needsDividerAfter();
725        }
726        if (childIndex > 0 && child instanceof ActionMenuChildView) {
727            result |= ((ActionMenuChildView) child).needsDividerBefore();
728        }
729        return result;
730    }
731
732    /** @hide */
733    public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
734        return false;
735    }
736
737    /** @hide */
738    public void setExpandedActionViewsExclusive(boolean exclusive) {
739        mPresenter.setExpandedActionViewsExclusive(exclusive);
740    }
741
742    /**
743     * Interface responsible for receiving menu item click events if the items themselves
744     * do not have individual item click listeners.
745     */
746    public interface OnMenuItemClickListener {
747        /**
748         * This method will be invoked when a menu item is clicked if the item itself did
749         * not already handle the event.
750         *
751         * @param item {@link MenuItem} that was clicked
752         * @return <code>true</code> if the event was handled, <code>false</code> otherwise.
753         */
754        public boolean onMenuItemClick(MenuItem item);
755    }
756
757    private class MenuBuilderCallback implements MenuBuilder.Callback {
758        @Override
759        public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
760            return mOnMenuItemClickListener != null &&
761                    mOnMenuItemClickListener.onMenuItemClick(item);
762        }
763
764        @Override
765        public void onMenuModeChange(MenuBuilder menu) {
766            if (mMenuBuilderCallback != null) {
767                mMenuBuilderCallback.onMenuModeChange(menu);
768            }
769        }
770    }
771
772    private class ActionMenuPresenterCallback implements ActionMenuPresenter.Callback {
773        @Override
774        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
775        }
776
777        @Override
778        public boolean onOpenSubMenu(MenuBuilder subMenu) {
779            return false;
780        }
781    }
782
783    /** @hide */
784    public interface ActionMenuChildView {
785        public boolean needsDividerBefore();
786        public boolean needsDividerAfter();
787    }
788
789    public static class LayoutParams extends LinearLayout.LayoutParams {
790        /** @hide */
791        @ViewDebug.ExportedProperty(category = "layout")
792        public boolean isOverflowButton;
793
794        /** @hide */
795        @ViewDebug.ExportedProperty(category = "layout")
796        public int cellsUsed;
797
798        /** @hide */
799        @ViewDebug.ExportedProperty(category = "layout")
800        public int extraPixels;
801
802        /** @hide */
803        @ViewDebug.ExportedProperty(category = "layout")
804        public boolean expandable;
805
806        /** @hide */
807        @ViewDebug.ExportedProperty(category = "layout")
808        public boolean preventEdgeOffset;
809
810        /** @hide */
811        public boolean expanded;
812
813        public LayoutParams(Context c, AttributeSet attrs) {
814            super(c, attrs);
815        }
816
817        public LayoutParams(ViewGroup.LayoutParams other) {
818            super(other);
819        }
820
821        public LayoutParams(LayoutParams other) {
822            super((LinearLayout.LayoutParams) other);
823            isOverflowButton = other.isOverflowButton;
824        }
825
826        public LayoutParams(int width, int height) {
827            super(width, height);
828            isOverflowButton = false;
829        }
830
831        /** @hide */
832        public LayoutParams(int width, int height, boolean isOverflowButton) {
833            super(width, height);
834            this.isOverflowButton = isOverflowButton;
835        }
836
837        /** @hide */
838        @Override
839        protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
840            super.encodeProperties(encoder);
841
842            encoder.addProperty("layout:overFlowButton", isOverflowButton);
843            encoder.addProperty("layout:cellsUsed", cellsUsed);
844            encoder.addProperty("layout:extraPixels", extraPixels);
845            encoder.addProperty("layout:expandable", expandable);
846            encoder.addProperty("layout:preventEdgeOffset", preventEdgeOffset);
847        }
848    }
849}
850