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