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