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.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 
ActionMenuView(Context context)73     public ActionMenuView(Context context) {
74         this(context, null);
75     }
76 
ActionMenuView(Context context, AttributeSet attrs)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      */
setPopupTheme(@tyleRes int resId)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      */
getPopupTheme()110     public int getPopupTheme() {
111         return mPopupTheme;
112     }
113 
114     /**
115      * @param presenter Menu presenter used to display popup menu
116      * @hide
117      */
setPresenter(ActionMenuPresenter presenter)118     public void setPresenter(ActionMenuPresenter presenter) {
119         mPresenter = presenter;
120         mPresenter.setMenuView(this);
121     }
122 
123     @Override
onConfigurationChanged(Configuration newConfig)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 
setOnMenuItemClickListener(OnMenuItemClickListener listener)137     public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
138         mOnMenuItemClickListener = listener;
139     }
140 
141     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)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 
onMeasureExactFormat(int widthMeasureSpec, int heightMeasureSpec)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      */
measureChildForCells(View child, int cellSize, int cellsRemaining, int parentHeightMeasureSpec, int parentHeightPadding)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
onLayout(boolean changed, int left, int top, int right, int bottom)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
onDetachedFromWindow()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      */
setOverflowIcon(@ullable Drawable icon)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
getOverflowIcon()562     public Drawable getOverflowIcon() {
563         getMenu();
564         return mPresenter.getOverflowIcon();
565     }
566 
567     /** @hide */
568     @UnsupportedAppUsage
isOverflowReserved()569     public boolean isOverflowReserved() {
570         return mReserveOverflow;
571     }
572 
573     /** @hide */
setOverflowReserved(boolean reserveOverflow)574     public void setOverflowReserved(boolean reserveOverflow) {
575         mReserveOverflow = reserveOverflow;
576     }
577 
578     @Override
generateDefaultLayoutParams()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
generateLayoutParams(AttributeSet attrs)587     public LayoutParams generateLayoutParams(AttributeSet attrs) {
588         return new LayoutParams(getContext(), attrs);
589     }
590 
591     @Override
generateLayoutParams(ViewGroup.LayoutParams p)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
checkLayoutParams(ViewGroup.LayoutParams p)606     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
607         return p != null && p instanceof LayoutParams;
608     }
609 
610     /** @hide */
generateOverflowButtonLayoutParams()611     public LayoutParams generateOverflowButtonLayoutParams() {
612         LayoutParams result = generateDefaultLayoutParams();
613         result.isOverflowButton = true;
614         return result;
615     }
616 
617     /** @hide */
invokeItem(MenuItemImpl item)618     public boolean invokeItem(MenuItemImpl item) {
619         return mMenu.performItemAction(item, 0);
620     }
621 
622     /** @hide */
getWindowAnimations()623     public int getWindowAnimations() {
624         return 0;
625     }
626 
627     /** @hide */
initialize(@ullable MenuBuilder menu)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      */
getMenu()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
setMenuCallbacks(MenuPresenter.Callback pcb, MenuBuilder.Callback mcb)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
peekMenu()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      */
showOverflowMenu()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      */
hideOverflowMenu()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      */
isOverflowMenuShowing()699     public boolean isOverflowMenuShowing() {
700         return mPresenter != null && mPresenter.isOverflowMenuShowing();
701     }
702 
703     /** @hide */
704     @UnsupportedAppUsage
isOverflowMenuShowPending()705     public boolean isOverflowMenuShowPending() {
706         return mPresenter != null && mPresenter.isOverflowMenuShowPending();
707     }
708 
709     /**
710      * Dismiss any popups associated with this menu view.
711      */
dismissPopupMenus()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
hasDividerBeforeChildAt(int childIndex)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 */
dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event)740     public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
741         return false;
742     }
743 
744     /** @hide */
745     @UnsupportedAppUsage
setExpandedActionViewsExclusive(boolean exclusive)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          */
onMenuItemClick(MenuItem item)762         public boolean onMenuItemClick(MenuItem item);
763     }
764 
765     private class MenuBuilderCallback implements MenuBuilder.Callback {
766         @Override
onMenuItemSelected(MenuBuilder menu, MenuItem item)767         public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
768             return mOnMenuItemClickListener != null &&
769                     mOnMenuItemClickListener.onMenuItemClick(item);
770         }
771 
772         @Override
onMenuModeChange(MenuBuilder menu)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
onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing)782         public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
783         }
784 
785         @Override
onOpenSubMenu(MenuBuilder subMenu)786         public boolean onOpenSubMenu(MenuBuilder subMenu) {
787             return false;
788         }
789     }
790 
791     /** @hide */
792     public interface ActionMenuChildView {
793         @UnsupportedAppUsage
needsDividerBefore()794         public boolean needsDividerBefore();
needsDividerAfter()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 
LayoutParams(Context c, AttributeSet attrs)828         public LayoutParams(Context c, AttributeSet attrs) {
829             super(c, attrs);
830         }
831 
LayoutParams(ViewGroup.LayoutParams other)832         public LayoutParams(ViewGroup.LayoutParams other) {
833             super(other);
834         }
835 
LayoutParams(LayoutParams other)836         public LayoutParams(LayoutParams other) {
837             super((LinearLayout.LayoutParams) other);
838             isOverflowButton = other.isOverflowButton;
839         }
840 
LayoutParams(int width, int height)841         public LayoutParams(int width, int height) {
842             super(width, height);
843             isOverflowButton = false;
844         }
845 
846         /** @hide */
LayoutParams(int width, int height, boolean isOverflowButton)847         public LayoutParams(int width, int height, boolean isOverflowButton) {
848             super(width, height);
849             this.isOverflowButton = isOverflowButton;
850         }
851 
852         /** @hide */
853         @Override
encodeProperties(@onNull ViewHierarchyEncoder encoder)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