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