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