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