• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 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 
17 package com.android.internal.view.menu;
18 
19 import android.annotation.Nullable;
20 import android.compat.annotation.UnsupportedAppUsage;
21 import android.content.ActivityNotFoundException;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.res.ColorStateList;
25 import android.content.res.Resources;
26 import android.graphics.PorterDuff;
27 import android.graphics.drawable.Drawable;
28 import android.os.Build;
29 import android.util.Log;
30 import android.view.ActionProvider;
31 import android.view.ContextMenu.ContextMenuInfo;
32 import android.view.KeyEvent;
33 import android.view.LayoutInflater;
34 import android.view.MenuItem;
35 import android.view.SubMenu;
36 import android.view.View;
37 import android.view.ViewConfiguration;
38 import android.view.ViewDebug;
39 import android.widget.LinearLayout;
40 
41 import com.android.internal.view.menu.MenuView.ItemView;
42 
43 /**
44  * @hide
45  */
46 public final class MenuItemImpl implements MenuItem {
47     private static final String TAG = "MenuItemImpl";
48 
49     private static final int SHOW_AS_ACTION_MASK = SHOW_AS_ACTION_NEVER |
50             SHOW_AS_ACTION_IF_ROOM |
51             SHOW_AS_ACTION_ALWAYS;
52 
53     private final int mId;
54     private final int mGroup;
55     private final int mCategoryOrder;
56     private final int mOrdering;
57     private CharSequence mTitle;
58     private CharSequence mTitleCondensed;
59     private Intent mIntent;
60     private char mShortcutNumericChar;
61     private int mShortcutNumericModifiers = KeyEvent.META_CTRL_ON;
62     private char mShortcutAlphabeticChar;
63     private int mShortcutAlphabeticModifiers = KeyEvent.META_CTRL_ON;
64 
65     /** The icon's drawable which is only created as needed */
66     private Drawable mIconDrawable;
67     /**
68      * The icon's resource ID which is used to get the Drawable when it is
69      * needed (if the Drawable isn't already obtained--only one of the two is
70      * needed).
71      */
72     @UnsupportedAppUsage
73     private int mIconResId = NO_ICON;
74 
75     private ColorStateList mIconTintList = null;
76     private PorterDuff.Mode mIconTintMode = null;
77     private boolean mHasIconTint = false;
78     private boolean mHasIconTintMode = false;
79     private boolean mNeedToApplyIconTint = false;
80 
81     /** The menu to which this item belongs */
82     private MenuBuilder mMenu;
83     /** If this item should launch a sub menu, this is the sub menu to launch */
84     private SubMenuBuilder mSubMenu;
85 
86     private Runnable mItemCallback;
87     private MenuItem.OnMenuItemClickListener mClickListener;
88 
89     private int mFlags = ENABLED;
90     private static final int CHECKABLE      = 0x00000001;
91     private static final int CHECKED        = 0x00000002;
92     private static final int EXCLUSIVE      = 0x00000004;
93     private static final int HIDDEN         = 0x00000008;
94     private static final int ENABLED        = 0x00000010;
95     private static final int IS_ACTION      = 0x00000020;
96 
97     private int mShowAsAction = SHOW_AS_ACTION_NEVER;
98 
99     private View mActionView;
100     private ActionProvider mActionProvider;
101     private OnActionExpandListener mOnActionExpandListener;
102     private boolean mIsActionViewExpanded = false;
103 
104     /** Used for the icon resource ID if this item does not have an icon */
105     static final int NO_ICON = 0;
106 
107     /**
108      * Current use case is for context menu: Extra information linked to the
109      * View that added this item to the context menu.
110      */
111     private ContextMenuInfo mMenuInfo;
112 
113     private CharSequence mContentDescription;
114     private CharSequence mTooltipText;
115 
116     /**
117      * Instantiates this menu item.
118      *
119      * @param menu
120      * @param group Item ordering grouping control. The item will be added after
121      *            all other items whose order is <= this number, and before any
122      *            that are larger than it. This can also be used to define
123      *            groups of items for batch state changes. Normally use 0.
124      * @param id Unique item ID. Use 0 if you do not need a unique ID.
125      * @param categoryOrder The ordering for this item.
126      * @param title The text to display for the item.
127      */
MenuItemImpl(MenuBuilder menu, int group, int id, int categoryOrder, int ordering, CharSequence title, int showAsAction)128     MenuItemImpl(MenuBuilder menu, int group, int id, int categoryOrder, int ordering,
129             CharSequence title, int showAsAction) {
130 
131         mMenu = menu;
132         mId = id;
133         mGroup = group;
134         mCategoryOrder = categoryOrder;
135         mOrdering = ordering;
136         mTitle = title;
137         mShowAsAction = showAsAction;
138     }
139 
140     /**
141      * Invokes the item by calling various listeners or callbacks.
142      *
143      * @return true if the invocation was handled, false otherwise
144      */
145     @UnsupportedAppUsage
invoke()146     public boolean invoke() {
147         if (mClickListener != null &&
148             mClickListener.onMenuItemClick(this)) {
149             return true;
150         }
151 
152         if (mMenu.dispatchMenuItemSelected(mMenu, this)) {
153             return true;
154         }
155 
156         if (mItemCallback != null) {
157             mItemCallback.run();
158             return true;
159         }
160 
161         if (mIntent != null) {
162             try {
163                 mMenu.getContext().startActivity(mIntent);
164                 return true;
165             } catch (ActivityNotFoundException e) {
166                 Log.e(TAG, "Can't find activity to handle intent; ignoring", e);
167             }
168         }
169 
170         if (mActionProvider != null && mActionProvider.onPerformDefaultAction()) {
171             return true;
172         }
173 
174         return false;
175     }
176 
isEnabled()177     public boolean isEnabled() {
178         return (mFlags & ENABLED) != 0;
179     }
180 
setEnabled(boolean enabled)181     public MenuItem setEnabled(boolean enabled) {
182         if (enabled) {
183             mFlags |= ENABLED;
184         } else {
185             mFlags &= ~ENABLED;
186         }
187 
188         mMenu.onItemsChanged(false);
189 
190         return this;
191     }
192 
getGroupId()193     public int getGroupId() {
194         return mGroup;
195     }
196 
197     @ViewDebug.CapturedViewProperty
getItemId()198     public int getItemId() {
199         return mId;
200     }
201 
getOrder()202     public int getOrder() {
203         return mCategoryOrder;
204     }
205 
getOrdering()206     public int getOrdering() {
207         return mOrdering;
208     }
209 
getIntent()210     public Intent getIntent() {
211         return mIntent;
212     }
213 
setIntent(Intent intent)214     public MenuItem setIntent(Intent intent) {
215         mIntent = intent;
216         return this;
217     }
218 
getCallback()219     Runnable getCallback() {
220         return mItemCallback;
221     }
222 
setCallback(Runnable callback)223     public MenuItem setCallback(Runnable callback) {
224         mItemCallback = callback;
225         return this;
226     }
227 
228     @Override
getAlphabeticShortcut()229     public char getAlphabeticShortcut() {
230         return mShortcutAlphabeticChar;
231     }
232 
233     @Override
getAlphabeticModifiers()234     public int getAlphabeticModifiers() {
235         return mShortcutAlphabeticModifiers;
236     }
237 
238     @Override
setAlphabeticShortcut(char alphaChar)239     public MenuItem setAlphabeticShortcut(char alphaChar) {
240         if (mShortcutAlphabeticChar == alphaChar) return this;
241 
242         mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
243 
244         mMenu.onItemsChanged(false);
245 
246         return this;
247     }
248 
249     @Override
setAlphabeticShortcut(char alphaChar, int alphaModifiers)250     public MenuItem setAlphabeticShortcut(char alphaChar, int alphaModifiers){
251         if (mShortcutAlphabeticChar == alphaChar &&
252                 mShortcutAlphabeticModifiers == alphaModifiers) {
253             return this;
254         }
255 
256         mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
257         mShortcutAlphabeticModifiers = KeyEvent.normalizeMetaState(alphaModifiers);
258 
259         mMenu.onItemsChanged(false);
260 
261         return this;
262     }
263 
264     @Override
getNumericShortcut()265     public char getNumericShortcut() {
266         return mShortcutNumericChar;
267     }
268 
269     @Override
getNumericModifiers()270     public int getNumericModifiers() {
271         return mShortcutNumericModifiers;
272     }
273 
274     @Override
setNumericShortcut(char numericChar)275     public MenuItem setNumericShortcut(char numericChar) {
276         if (mShortcutNumericChar == numericChar) return this;
277 
278         mShortcutNumericChar = numericChar;
279 
280         mMenu.onItemsChanged(false);
281 
282         return this;
283     }
284 
285     @Override
setNumericShortcut(char numericChar, int numericModifiers)286     public MenuItem setNumericShortcut(char numericChar, int numericModifiers){
287         if (mShortcutNumericChar == numericChar && mShortcutNumericModifiers == numericModifiers) {
288             return this;
289         }
290 
291         mShortcutNumericChar = numericChar;
292         mShortcutNumericModifiers = KeyEvent.normalizeMetaState(numericModifiers);
293 
294         mMenu.onItemsChanged(false);
295 
296         return this;
297     }
298 
299     @Override
setShortcut(char numericChar, char alphaChar)300     public MenuItem setShortcut(char numericChar, char alphaChar) {
301         mShortcutNumericChar = numericChar;
302         mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
303 
304         mMenu.onItemsChanged(false);
305 
306         return this;
307     }
308 
309     @Override
setShortcut(char numericChar, char alphaChar, int numericModifiers, int alphaModifiers)310     public MenuItem setShortcut(char numericChar, char alphaChar, int numericModifiers,
311             int alphaModifiers) {
312         mShortcutNumericChar = numericChar;
313         mShortcutNumericModifiers = KeyEvent.normalizeMetaState(numericModifiers);
314         mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
315         mShortcutAlphabeticModifiers = KeyEvent.normalizeMetaState(alphaModifiers);
316 
317         mMenu.onItemsChanged(false);
318 
319         return this;
320     }
321 
322     /**
323      * @return The active shortcut (based on QWERTY-mode of the menu).
324      */
getShortcut()325     char getShortcut() {
326         return (mMenu.isQwertyMode() ? mShortcutAlphabeticChar : mShortcutNumericChar);
327     }
328 
329     /**
330      * @return The label to show for the shortcut. This includes the chording
331      *         key (for example 'Menu+a'). Also, any non-human readable
332      *         characters should be human readable (for example 'Menu+enter').
333      */
getShortcutLabel()334     String getShortcutLabel() {
335 
336         char shortcut = getShortcut();
337         if (shortcut == 0) {
338             return "";
339         }
340 
341         final Resources res = mMenu.getContext().getResources();
342 
343         StringBuilder sb = new StringBuilder();
344         if (ViewConfiguration.get(mMenu.getContext()).hasPermanentMenuKey()) {
345             // Only prepend "Menu+" if there is a hardware menu key.
346             sb.append(res.getString(
347                 com.android.internal.R.string.prepend_shortcut_label));
348         }
349 
350         final int modifiers =
351             mMenu.isQwertyMode() ? mShortcutAlphabeticModifiers : mShortcutNumericModifiers;
352         appendModifier(sb, modifiers, KeyEvent.META_META_ON, res.getString(
353             com.android.internal.R.string.menu_meta_shortcut_label));
354         appendModifier(sb, modifiers, KeyEvent.META_CTRL_ON, res.getString(
355             com.android.internal.R.string.menu_ctrl_shortcut_label));
356         appendModifier(sb, modifiers, KeyEvent.META_ALT_ON, res.getString(
357             com.android.internal.R.string.menu_alt_shortcut_label));
358         appendModifier(sb, modifiers, KeyEvent.META_SHIFT_ON, res.getString(
359             com.android.internal.R.string.menu_shift_shortcut_label));
360         appendModifier(sb, modifiers, KeyEvent.META_SYM_ON, res.getString(
361             com.android.internal.R.string.menu_sym_shortcut_label));
362         appendModifier(sb, modifiers, KeyEvent.META_FUNCTION_ON, res.getString(
363             com.android.internal.R.string.menu_function_shortcut_label));
364 
365         switch (shortcut) {
366 
367             case '\n':
368                 sb.append(res.getString(
369                     com.android.internal.R.string.menu_enter_shortcut_label));
370                 break;
371 
372             case '\b':
373                 sb.append(res.getString(
374                     com.android.internal.R.string.menu_delete_shortcut_label));
375                 break;
376 
377             case ' ':
378                 sb.append(res.getString(
379                     com.android.internal.R.string.menu_space_shortcut_label));
380                 break;
381 
382             default:
383                 sb.append(shortcut);
384                 break;
385         }
386 
387         return sb.toString();
388     }
389 
appendModifier(StringBuilder sb, int mask, int modifier, String label)390     private static void appendModifier(StringBuilder sb, int mask, int modifier, String label) {
391         if ((mask & modifier) == modifier) {
392             sb.append(label);
393         }
394     }
395 
396     /**
397      * @return Whether this menu item should be showing shortcuts (depends on
398      *         whether the menu should show shortcuts and whether this item has
399      *         a shortcut defined)
400      */
shouldShowShortcut()401     boolean shouldShowShortcut() {
402         // Show shortcuts if the menu is supposed to show shortcuts AND this item has a shortcut
403         return mMenu.isShortcutsVisible() && (getShortcut() != 0);
404     }
405 
getSubMenu()406     public SubMenu getSubMenu() {
407         return mSubMenu;
408     }
409 
hasSubMenu()410     public boolean hasSubMenu() {
411         return mSubMenu != null;
412     }
413 
setSubMenu(SubMenuBuilder subMenu)414     void setSubMenu(SubMenuBuilder subMenu) {
415         mSubMenu = subMenu;
416 
417         subMenu.setHeaderTitle(getTitle());
418     }
419 
420     @ViewDebug.CapturedViewProperty
getTitle()421     public CharSequence getTitle() {
422         return mTitle;
423     }
424 
425     /**
426      * Gets the title for a particular {@link ItemView}
427      *
428      * @param itemView The ItemView that is receiving the title
429      * @return Either the title or condensed title based on what the ItemView
430      *         prefers
431      */
getTitleForItemView(MenuView.ItemView itemView)432     CharSequence getTitleForItemView(MenuView.ItemView itemView) {
433         return ((itemView != null) && itemView.prefersCondensedTitle())
434                 ? getTitleCondensed()
435                 : getTitle();
436     }
437 
setTitle(CharSequence title)438     public MenuItem setTitle(CharSequence title) {
439         mTitle = title;
440 
441         mMenu.onItemsChanged(false);
442 
443         if (mSubMenu != null) {
444             mSubMenu.setHeaderTitle(title);
445         }
446 
447         return this;
448     }
449 
setTitle(int title)450     public MenuItem setTitle(int title) {
451         return setTitle(mMenu.getContext().getString(title));
452     }
453 
getTitleCondensed()454     public CharSequence getTitleCondensed() {
455         return mTitleCondensed != null ? mTitleCondensed : mTitle;
456     }
457 
setTitleCondensed(CharSequence title)458     public MenuItem setTitleCondensed(CharSequence title) {
459         mTitleCondensed = title;
460 
461         // Could use getTitle() in the loop below, but just cache what it would do here
462         if (title == null) {
463             title = mTitle;
464         }
465 
466         mMenu.onItemsChanged(false);
467 
468         return this;
469     }
470 
getIcon()471     public Drawable getIcon() {
472         if (mIconDrawable != null) {
473             return applyIconTintIfNecessary(mIconDrawable);
474         }
475 
476         if (mIconResId != NO_ICON) {
477             Drawable icon =  mMenu.getContext().getDrawable(mIconResId);
478             mIconResId = NO_ICON;
479             mIconDrawable = icon;
480             return applyIconTintIfNecessary(icon);
481         }
482 
483         return null;
484     }
485 
setIcon(Drawable icon)486     public MenuItem setIcon(Drawable icon) {
487         mIconResId = NO_ICON;
488         mIconDrawable = icon;
489         mNeedToApplyIconTint = true;
490         mMenu.onItemsChanged(false);
491 
492         return this;
493     }
494 
setIcon(int iconResId)495     public MenuItem setIcon(int iconResId) {
496         mIconDrawable = null;
497         mIconResId = iconResId;
498         mNeedToApplyIconTint = true;
499 
500         // If we have a view, we need to push the Drawable to them
501         mMenu.onItemsChanged(false);
502 
503         return this;
504     }
505 
506     @Override
setIconTintList(@ullable ColorStateList iconTintList)507     public MenuItem setIconTintList(@Nullable ColorStateList iconTintList) {
508         mIconTintList = iconTintList;
509         mHasIconTint = true;
510         mNeedToApplyIconTint = true;
511 
512         mMenu.onItemsChanged(false);
513 
514         return this;
515     }
516 
517     @Nullable
518     @Override
getIconTintList()519     public ColorStateList getIconTintList() {
520         return mIconTintList;
521     }
522 
523     @Override
setIconTintMode(PorterDuff.Mode iconTintMode)524     public MenuItem setIconTintMode(PorterDuff.Mode iconTintMode) {
525         mIconTintMode = iconTintMode;
526         mHasIconTintMode = true;
527         mNeedToApplyIconTint = true;
528 
529         mMenu.onItemsChanged(false);
530 
531         return this;
532     }
533 
534     @Nullable
535     @Override
getIconTintMode()536     public PorterDuff.Mode getIconTintMode() {
537         return mIconTintMode;
538     }
539 
applyIconTintIfNecessary(Drawable icon)540     private Drawable applyIconTintIfNecessary(Drawable icon) {
541         if (icon != null && mNeedToApplyIconTint && (mHasIconTint || mHasIconTintMode)) {
542             icon = icon.mutate();
543 
544             if (mHasIconTint) {
545                 icon.setTintList(mIconTintList);
546             }
547 
548             if (mHasIconTintMode) {
549                 icon.setTintMode(mIconTintMode);
550             }
551 
552             mNeedToApplyIconTint = false;
553         }
554 
555         return icon;
556     }
557 
isCheckable()558     public boolean isCheckable() {
559         return (mFlags & CHECKABLE) == CHECKABLE;
560     }
561 
setCheckable(boolean checkable)562     public MenuItem setCheckable(boolean checkable) {
563         final int oldFlags = mFlags;
564         mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0);
565         if (oldFlags != mFlags) {
566             mMenu.onItemsChanged(false);
567         }
568 
569         return this;
570     }
571 
572     @UnsupportedAppUsage
setExclusiveCheckable(boolean exclusive)573     public void setExclusiveCheckable(boolean exclusive) {
574         mFlags = (mFlags & ~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
575     }
576 
isExclusiveCheckable()577     public boolean isExclusiveCheckable() {
578         return (mFlags & EXCLUSIVE) != 0;
579     }
580 
isChecked()581     public boolean isChecked() {
582         return (mFlags & CHECKED) == CHECKED;
583     }
584 
setChecked(boolean checked)585     public MenuItem setChecked(boolean checked) {
586         if ((mFlags & EXCLUSIVE) != 0) {
587             // Call the method on the Menu since it knows about the others in this
588             // exclusive checkable group
589             mMenu.setExclusiveItemChecked(this);
590         } else {
591             setCheckedInt(checked);
592         }
593 
594         return this;
595     }
596 
setCheckedInt(boolean checked)597     void setCheckedInt(boolean checked) {
598         final int oldFlags = mFlags;
599         mFlags = (mFlags & ~CHECKED) | (checked ? CHECKED : 0);
600         if (oldFlags != mFlags) {
601             mMenu.onItemsChanged(false);
602         }
603     }
604 
isVisible()605     public boolean isVisible() {
606         if (mActionProvider != null && mActionProvider.overridesItemVisibility()) {
607             return (mFlags & HIDDEN) == 0 && mActionProvider.isVisible();
608         }
609         return (mFlags & HIDDEN) == 0;
610     }
611 
612     /**
613      * Changes the visibility of the item. This method DOES NOT notify the
614      * parent menu of a change in this item, so this should only be called from
615      * methods that will eventually trigger this change.  If unsure, use {@link #setVisible(boolean)}
616      * instead.
617      *
618      * @param shown Whether to show (true) or hide (false).
619      * @return Whether the item's shown state was changed
620      */
setVisibleInt(boolean shown)621     boolean setVisibleInt(boolean shown) {
622         final int oldFlags = mFlags;
623         mFlags = (mFlags & ~HIDDEN) | (shown ? 0 : HIDDEN);
624         return oldFlags != mFlags;
625     }
626 
setVisible(boolean shown)627     public MenuItem setVisible(boolean shown) {
628         // Try to set the shown state to the given state. If the shown state was changed
629         // (i.e. the previous state isn't the same as given state), notify the parent menu that
630         // the shown state has changed for this item
631         if (setVisibleInt(shown)) mMenu.onItemVisibleChanged(this);
632 
633         return this;
634     }
635 
setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener clickListener)636    public MenuItem setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener clickListener) {
637         mClickListener = clickListener;
638         return this;
639     }
640 
641     @Override
toString()642     public String toString() {
643         return mTitle != null ? mTitle.toString() : null;
644     }
645 
646     @UnsupportedAppUsage
setMenuInfo(ContextMenuInfo menuInfo)647     void setMenuInfo(ContextMenuInfo menuInfo) {
648         mMenuInfo = menuInfo;
649     }
650 
getMenuInfo()651     public ContextMenuInfo getMenuInfo() {
652         return mMenuInfo;
653     }
654 
actionFormatChanged()655     public void actionFormatChanged() {
656         mMenu.onItemActionRequestChanged(this);
657     }
658 
659     /**
660      * @return Whether the menu should show icons for menu items.
661      */
shouldShowIcon()662     public boolean shouldShowIcon() {
663         return mMenu.getOptionalIconsVisible();
664     }
665 
666     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
isActionButton()667     public boolean isActionButton() {
668         return (mFlags & IS_ACTION) == IS_ACTION;
669     }
670 
671     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
requestsActionButton()672     public boolean requestsActionButton() {
673         return (mShowAsAction & SHOW_AS_ACTION_IF_ROOM) == SHOW_AS_ACTION_IF_ROOM;
674     }
675 
676     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
requiresActionButton()677     public boolean requiresActionButton() {
678         return (mShowAsAction & SHOW_AS_ACTION_ALWAYS) == SHOW_AS_ACTION_ALWAYS;
679     }
680 
681     @Override
requiresOverflow()682     public boolean requiresOverflow() {
683         return !requiresActionButton() && !requestsActionButton();
684     }
685 
setIsActionButton(boolean isActionButton)686     public void setIsActionButton(boolean isActionButton) {
687         if (isActionButton) {
688             mFlags |= IS_ACTION;
689         } else {
690             mFlags &= ~IS_ACTION;
691         }
692     }
693 
showsTextAsAction()694     public boolean showsTextAsAction() {
695         return (mShowAsAction & SHOW_AS_ACTION_WITH_TEXT) == SHOW_AS_ACTION_WITH_TEXT;
696     }
697 
setShowAsAction(int actionEnum)698     public void setShowAsAction(int actionEnum) {
699         switch (actionEnum & SHOW_AS_ACTION_MASK) {
700             case SHOW_AS_ACTION_ALWAYS:
701             case SHOW_AS_ACTION_IF_ROOM:
702             case SHOW_AS_ACTION_NEVER:
703                 // Looks good!
704                 break;
705 
706             default:
707                 // Mutually exclusive options selected!
708                 throw new IllegalArgumentException("SHOW_AS_ACTION_ALWAYS, SHOW_AS_ACTION_IF_ROOM,"
709                         + " and SHOW_AS_ACTION_NEVER are mutually exclusive.");
710         }
711         mShowAsAction = actionEnum;
712         mMenu.onItemActionRequestChanged(this);
713     }
714 
setActionView(View view)715     public MenuItem setActionView(View view) {
716         mActionView = view;
717         mActionProvider = null;
718         if (view != null && view.getId() == View.NO_ID && mId > 0) {
719             view.setId(mId);
720         }
721         mMenu.onItemActionRequestChanged(this);
722         return this;
723     }
724 
setActionView(int resId)725     public MenuItem setActionView(int resId) {
726         final Context context = mMenu.getContext();
727         final LayoutInflater inflater = LayoutInflater.from(context);
728         setActionView(inflater.inflate(resId, new LinearLayout(context), false));
729         return this;
730     }
731 
getActionView()732     public View getActionView() {
733         if (mActionView != null) {
734             return mActionView;
735         } else if (mActionProvider != null) {
736             mActionView = mActionProvider.onCreateActionView(this);
737             return mActionView;
738         } else {
739             return null;
740         }
741     }
742 
getActionProvider()743     public ActionProvider getActionProvider() {
744         return mActionProvider;
745     }
746 
setActionProvider(ActionProvider actionProvider)747     public MenuItem setActionProvider(ActionProvider actionProvider) {
748         if (mActionProvider != null) {
749             mActionProvider.reset();
750         }
751         mActionView = null;
752         mActionProvider = actionProvider;
753         mMenu.onItemsChanged(true); // Measurement can be changed
754         if (mActionProvider != null) {
755             mActionProvider.setVisibilityListener(new ActionProvider.VisibilityListener() {
756                 @Override public void onActionProviderVisibilityChanged(boolean isVisible) {
757                     mMenu.onItemVisibleChanged(MenuItemImpl.this);
758                 }
759             });
760         }
761         return this;
762     }
763 
764     @Override
setShowAsActionFlags(int actionEnum)765     public MenuItem setShowAsActionFlags(int actionEnum) {
766         setShowAsAction(actionEnum);
767         return this;
768     }
769 
770     @Override
expandActionView()771     public boolean expandActionView() {
772         if (!hasCollapsibleActionView()) {
773             return false;
774         }
775 
776         if (mOnActionExpandListener == null ||
777                 mOnActionExpandListener.onMenuItemActionExpand(this)) {
778             return mMenu.expandItemActionView(this);
779         }
780 
781         return false;
782     }
783 
784     @Override
collapseActionView()785     public boolean collapseActionView() {
786         if ((mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) == 0) {
787             return false;
788         }
789         if (mActionView == null) {
790             // We're already collapsed if we have no action view.
791             return true;
792         }
793 
794         if (mOnActionExpandListener == null ||
795                 mOnActionExpandListener.onMenuItemActionCollapse(this)) {
796             return mMenu.collapseItemActionView(this);
797         }
798 
799         return false;
800     }
801 
802     @Override
setOnActionExpandListener(OnActionExpandListener listener)803     public MenuItem setOnActionExpandListener(OnActionExpandListener listener) {
804         mOnActionExpandListener = listener;
805         return this;
806     }
807 
hasCollapsibleActionView()808     public boolean hasCollapsibleActionView() {
809         if ((mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) != 0) {
810             if (mActionView == null && mActionProvider != null) {
811                 mActionView = mActionProvider.onCreateActionView(this);
812             }
813             return mActionView != null;
814         }
815         return false;
816     }
817 
818     @UnsupportedAppUsage
setActionViewExpanded(boolean isExpanded)819     public void setActionViewExpanded(boolean isExpanded) {
820         mIsActionViewExpanded = isExpanded;
821         mMenu.onItemsChanged(false);
822     }
823 
isActionViewExpanded()824     public boolean isActionViewExpanded() {
825         return mIsActionViewExpanded;
826     }
827 
828     @Override
setContentDescription(CharSequence contentDescription)829     public MenuItem setContentDescription(CharSequence contentDescription) {
830         mContentDescription = contentDescription;
831 
832         mMenu.onItemsChanged(false);
833 
834         return this;
835     }
836 
837     @Override
getContentDescription()838     public CharSequence getContentDescription() {
839         return mContentDescription;
840     }
841 
842     @Override
setTooltipText(CharSequence tooltipText)843     public MenuItem setTooltipText(CharSequence tooltipText) {
844         mTooltipText = tooltipText;
845 
846         mMenu.onItemsChanged(false);
847 
848         return this;
849     }
850 
851     @Override
getTooltipText()852     public CharSequence getTooltipText() {
853         return mTooltipText;
854     }
855 }
856