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