1 /*
2  * Copyright 2018 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 androidx.core.view;
18 
19 import android.content.res.ColorStateList;
20 import android.graphics.PorterDuff;
21 import android.graphics.drawable.Drawable;
22 import android.os.Build;
23 import android.util.Log;
24 import android.view.KeyEvent;
25 import android.view.Menu;
26 import android.view.MenuItem;
27 import android.view.View;
28 
29 import androidx.core.internal.view.SupportMenuItem;
30 
31 /**
32  * Helper for accessing features in {@link android.view.MenuItem}.
33  * <p class="note"><strong>Note:</strong> You cannot get an instance of this class. Instead,
34  * it provides <em>static</em> methods that correspond to the methods in {@link
35  * android.view.MenuItem}, but take a {@link android.view.MenuItem} object as an additional
36  * argument.</p>
37  */
38 public final class MenuItemCompat {
39     private static final String TAG = "MenuItemCompat";
40 
41     /**
42      * Never show this item as a button in an Action Bar.
43      *
44      * @deprecated Use {@link MenuItem#SHOW_AS_ACTION_NEVER} directly.
45      */
46     @Deprecated
47     public static final int SHOW_AS_ACTION_NEVER = 0;
48 
49     /**
50      * Show this item as a button in an Action Bar if the system
51      * decides there is room for it.
52      *
53      * @deprecated Use {@link MenuItem#SHOW_AS_ACTION_IF_ROOM} directly.
54      */
55     @Deprecated
56     public static final int SHOW_AS_ACTION_IF_ROOM = 1;
57 
58     /**
59      * Always show this item as a button in an Action Bar. Use sparingly!
60      * If too many items are set to always show in the Action Bar it can
61      * crowd the Action Bar and degrade the user experience on devices with
62      * smaller screens. A good rule of thumb is to have no more than 2
63      * items set to always show at a time.
64      *
65      * @deprecated Use {@link MenuItem#SHOW_AS_ACTION_ALWAYS} directly.
66      */
67     @Deprecated
68     public static final int SHOW_AS_ACTION_ALWAYS = 2;
69 
70     /**
71      * When this item is in the action bar, always show it with a
72      * text label even if it also has an icon specified.
73      *
74      * @deprecated Use {@link MenuItem#SHOW_AS_ACTION_WITH_TEXT} directly.
75      */
76     @Deprecated
77     public static final int SHOW_AS_ACTION_WITH_TEXT = 4;
78 
79     /**
80      * This item's action view collapses to a normal menu item.
81      * When expanded, the action view temporarily takes over
82      * a larger segment of its container.
83      *
84      * @deprecated Use {@link MenuItem#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW} directly.
85      */
86     @Deprecated
87     public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8;
88 
89     /**
90      * Interface definition for a callback to be invoked when a menu item marked with {@link
91      * #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW} is expanded or collapsed.
92      *
93      * @see #expandActionView(android.view.MenuItem)
94      * @see #collapseActionView(android.view.MenuItem)
95      * @see #setShowAsAction(android.view.MenuItem, int)
96      *
97      * @deprecated Use {@link MenuItem.OnActionExpandListener} directly.
98      */
99     @Deprecated
100     public interface OnActionExpandListener {
101 
102         /**
103          * Called when a menu item with {@link #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}
104          * is expanded.
105          *
106          * @param item Item that was expanded
107          * @return true if the item should expand, false if expansion should be suppressed.
108          */
onMenuItemActionExpand(MenuItem item)109         boolean onMenuItemActionExpand(MenuItem item);
110 
111         /**
112          * Called when a menu item with {@link #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}
113          * is collapsed.
114          *
115          * @param item Item that was collapsed
116          * @return true if the item should collapse, false if collapsing should be suppressed.
117          */
onMenuItemActionCollapse(MenuItem item)118         boolean onMenuItemActionCollapse(MenuItem item);
119     }
120 
121     // -------------------------------------------------------------------
122 
123     /**
124      * Sets how this item should display in the presence of a compatible Action Bar. If the given
125      * item is compatible, this will call the item's supported implementation of
126      * {@link MenuItem#setShowAsAction(int)}.
127      *
128      * @param item - the item to change
129      * @param actionEnum - How the item should display.
130      *
131      * @deprecated Use {@link MenuItem#setShowAsAction(int)} directly.
132      */
133     @Deprecated
setShowAsAction(MenuItem item, int actionEnum)134     public static void setShowAsAction(MenuItem item, int actionEnum) {
135         item.setShowAsAction(actionEnum);
136     }
137 
138     /**
139      * Set an action view for this menu item. An action view will be displayed in place
140      * of an automatically generated menu item element in the UI when this item is shown
141      * as an action within a parent.
142      *
143      * @param item the item to change
144      * @param view View to use for presenting this item to the user.
145      * @return This Item so additional setters can be called.
146      *
147      * @see #setShowAsAction(MenuItem, int)
148      *
149      * @deprecated Use {@link MenuItem#setActionView(View)} directly.
150      */
151     @Deprecated
setActionView(MenuItem item, View view)152     public static MenuItem setActionView(MenuItem item, View view) {
153         return item.setActionView(view);
154     }
155 
156     /**
157      * Set an action view for this menu item. An action view will be displayed in place
158      * of an automatically generated menu item element in the UI when this item is shown
159      * as an action within a parent.
160      * <p>
161      *   <strong>Note:</strong> Setting an action view overrides the action provider
162      *           set via {@link #setActionProvider(MenuItem, ActionProvider)}.
163      * </p>
164      *
165      * @param item the item to change
166      * @param resId Layout resource to use for presenting this item to the user.
167      * @return This Item so additional setters can be called.
168      *
169      * @see #setShowAsAction(MenuItem, int)
170      *
171      * @deprecated Use {@link MenuItem#setActionView(int)} directly.
172      */
173     @Deprecated
setActionView(MenuItem item, int resId)174     public static MenuItem setActionView(MenuItem item, int resId) {
175         return item.setActionView(resId);
176     }
177 
178     /**
179      * Returns the currently set action view for this menu item.
180      *
181      * @param item the item to query
182      * @return This item's action view
183      *
184      * @deprecated Use {@link MenuItem#getActionView()} directly.
185      */
186     @Deprecated
getActionView(MenuItem item)187     public static View getActionView(MenuItem item) {
188         return item.getActionView();
189     }
190 
191     /**
192      * Sets the {@link ActionProvider} responsible for creating an action view if
193      * the item is placed on the action bar. The provider also provides a default
194      * action invoked if the item is placed in the overflow menu.
195      * <p>
196      *   <strong>Note:</strong> Setting an action provider overrides the action view
197      *           set via {@link #setActionView(MenuItem, View)}.
198      * </p>
199      *
200      * @param item item to change
201      * @param provider The action provider.
202      * @return This Item so additional setters can be called.
203      *
204      * @see ActionProvider
205      */
setActionProvider(MenuItem item, ActionProvider provider)206     public static MenuItem setActionProvider(MenuItem item, ActionProvider provider) {
207         if (item instanceof SupportMenuItem) {
208             return ((SupportMenuItem) item).setSupportActionProvider(provider);
209         }
210         // TODO Wrap the support ActionProvider and assign it
211         Log.w(TAG, "setActionProvider: item does not implement SupportMenuItem; ignoring");
212         return item;
213     }
214 
215     /**
216      * Gets the {@link ActionProvider}.
217      *
218      * @return The action provider.
219      *
220      * @see ActionProvider
221      * @see #setActionProvider(MenuItem, ActionProvider)
222      */
getActionProvider(MenuItem item)223     public static ActionProvider getActionProvider(MenuItem item) {
224         if (item instanceof SupportMenuItem) {
225             return ((SupportMenuItem) item).getSupportActionProvider();
226         }
227 
228         // TODO Wrap the framework ActionProvider and return it
229         Log.w(TAG, "getActionProvider: item does not implement SupportMenuItem; returning null");
230         return null;
231     }
232 
233     /**
234      * Expand the action view associated with this menu item.
235      * The menu item must have an action view set, as well as
236      * the showAsAction flag {@link #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}.
237      * If a listener has been set using
238      * {@link #setOnActionExpandListener(MenuItem, OnActionExpandListener)}
239      * it will have its {@link OnActionExpandListener#onMenuItemActionExpand(MenuItem)}
240      * method invoked. The listener may return false from this method to prevent expanding
241      * the action view.
242      *
243      * @return true if the action view was expanded, false otherwise.
244      *
245      * @deprecated Use {@link MenuItem#expandActionView()} directly.
246      */
247     @Deprecated
expandActionView(MenuItem item)248     public static boolean expandActionView(MenuItem item) {
249         return item.expandActionView();
250     }
251 
252     /**
253      * Collapse the action view associated with this menu item. The menu item must have an action
254      * view set, as well as the showAsAction flag {@link #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}. If a
255      * listener has been set using {@link #setOnActionExpandListener(MenuItem,
256      * androidx.core.view.MenuItemCompat.OnActionExpandListener)}
257      * it will have its {@link
258      * androidx.core.view.MenuItemCompat.OnActionExpandListener#onMenuItemActionCollapse(MenuItem)}
259      * method invoked. The listener may return false from this method to prevent collapsing
260      * the action view.
261      *
262      * @return true if the action view was collapsed, false otherwise.
263      *
264      * @deprecated Use {@link MenuItem#collapseActionView()} directly.
265      */
266     @Deprecated
collapseActionView(MenuItem item)267     public static boolean collapseActionView(MenuItem item) {
268         return item.collapseActionView();
269     }
270 
271     /**
272      * Returns true if this menu item's action view has been expanded.
273      *
274      * @return true if the item's action view is expanded, false otherwise.
275      * @see #expandActionView(MenuItem)
276      * @see #collapseActionView(MenuItem)
277      * @see #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW
278      * @see androidx.core.view.MenuItemCompat.OnActionExpandListener
279      *
280      * @deprecated Use {@link MenuItem#isActionViewExpanded()} directly.
281      */
282     @Deprecated
isActionViewExpanded(MenuItem item)283     public static boolean isActionViewExpanded(MenuItem item) {
284         return item.isActionViewExpanded();
285     }
286 
287     /**
288      * Set an {@link OnActionExpandListener} on this menu
289      * item to be notified when the associated action view is expanded or collapsed.
290      * The menu item must be configured to expand or collapse its action view using the flag
291      * {@link #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}.
292      *
293      * @param listener Listener that will respond to expand/collapse events
294      * @return This menu item instance for call chaining
295      *
296      * @deprecated Use {@link MenuItem#setOnActionExpandListener(MenuItem.OnActionExpandListener)}
297      * directly.
298      */
299     @Deprecated
setOnActionExpandListener(MenuItem item, final OnActionExpandListener listener)300     public static MenuItem setOnActionExpandListener(MenuItem item,
301             final OnActionExpandListener listener) {
302         return item.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
303             @Override
304             public boolean onMenuItemActionExpand(MenuItem item) {
305                 return listener.onMenuItemActionExpand(item);
306             }
307 
308             @Override
309             public boolean onMenuItemActionCollapse(MenuItem item) {
310                 return listener.onMenuItemActionCollapse(item);
311             }
312         });
313     }
314 
315     /**
316      * Change the content description associated with this menu item.
317      *
318      * @param item item to change.
319      * @param contentDescription The new content description.
320      */
321     public static void setContentDescription(MenuItem item, CharSequence contentDescription) {
322         if (item instanceof SupportMenuItem) {
323             ((SupportMenuItem) item).setContentDescription(contentDescription);
324         } else if (Build.VERSION.SDK_INT >= 26) {
325             item.setContentDescription(contentDescription);
326         }
327     }
328 
329     /**
330      * Retrieve the content description associated with this menu item.
331      *
332      * @return The content description.
333      */
334     public static CharSequence getContentDescription(MenuItem item) {
335         if (item instanceof SupportMenuItem) {
336             return ((SupportMenuItem) item).getContentDescription();
337         }
338         if (Build.VERSION.SDK_INT >= 26) {
339             return item.getContentDescription();
340         }
341         return null;
342     }
343 
344     /**
345      * Change the tooltip text associated with this menu item.
346      *
347      * @param item item to change.
348      * @param tooltipText The new tooltip text
349      */
350     public static void setTooltipText(MenuItem item, CharSequence tooltipText) {
351         if (item instanceof SupportMenuItem) {
352             ((SupportMenuItem) item).setTooltipText(tooltipText);
353         } else if (Build.VERSION.SDK_INT >= 26) {
354             item.setTooltipText(tooltipText);
355         }
356     }
357 
358     /**
359      * Retrieve the tooltip text associated with this menu item.
360      *
361      * @return The tooltip text.
362      */
363     public static CharSequence getTooltipText(MenuItem item) {
364         if (item instanceof SupportMenuItem) {
365             return ((SupportMenuItem) item).getTooltipText();
366         }
367         if (Build.VERSION.SDK_INT >= 26) {
368             return item.getTooltipText();
369         }
370         return null;
371     }
372 
373     /**
374      * Change both the numeric and alphabetic shortcut associated with this
375      * item. Note that the shortcut will be triggered when the key that
376      * generates the given character is pressed along with the corresponding
377      * modifier key. Also note that case is not significant and that alphabetic
378      * shortcut characters will be handled in lower case.
379      * <p>
380      * See {@link Menu} for the menu types that support shortcuts.
381      *
382      * @param numericChar The numeric shortcut key. This is the shortcut when
383      *        using a numeric (e.g., 12-key) keyboard.
384      * @param numericModifiers The numeric modifier associated with the shortcut. It should
385      *        be a combination of {@link KeyEvent#META_META_ON}, {@link KeyEvent#META_CTRL_ON},
386      *        {@link KeyEvent#META_ALT_ON}, {@link KeyEvent#META_SHIFT_ON},
387      *        {@link KeyEvent#META_SYM_ON}, {@link KeyEvent#META_FUNCTION_ON}.
388      * @param alphaChar The alphabetic shortcut key. This is the shortcut when
389      *        using a keyboard with alphabetic keys.
390      * @param alphaModifiers The alphabetic modifier associated with the shortcut. It should
391      *        be a combination of {@link KeyEvent#META_META_ON}, {@link KeyEvent#META_CTRL_ON},
392      *        {@link KeyEvent#META_ALT_ON}, {@link KeyEvent#META_SHIFT_ON},
393      *        {@link KeyEvent#META_SYM_ON}, {@link KeyEvent#META_FUNCTION_ON}.
394      */
395     public static void setShortcut(MenuItem item, char numericChar, char alphaChar,
396             int numericModifiers, int alphaModifiers) {
397         if (item instanceof SupportMenuItem) {
398             ((SupportMenuItem) item).setShortcut(numericChar, alphaChar, numericModifiers,
399                     alphaModifiers);
400         } else if (Build.VERSION.SDK_INT >= 26) {
401             item.setShortcut(numericChar, alphaChar, numericModifiers, alphaModifiers);
402         }
403     }
404 
405     /**
406      * Change the numeric shortcut and modifiers associated with this item.
407      * <p>
408      * See {@link Menu} for the menu types that support shortcuts.
409      *
410      * @param numericChar The numeric shortcut key.  This is the shortcut when
411      *                 using a 12-key (numeric) keyboard.
412      * @param numericModifiers The modifier associated with the shortcut. It should
413      *        be a combination of {@link KeyEvent#META_META_ON}, {@link KeyEvent#META_CTRL_ON},
414      *        {@link KeyEvent#META_ALT_ON}, {@link KeyEvent#META_SHIFT_ON},
415      *        {@link KeyEvent#META_SYM_ON}, {@link KeyEvent#META_FUNCTION_ON}.
416      */
417     public static void setNumericShortcut(MenuItem item, char numericChar, int numericModifiers) {
418         if (item instanceof SupportMenuItem) {
419             ((SupportMenuItem) item).setNumericShortcut(numericChar, numericModifiers);
420         } else if (Build.VERSION.SDK_INT >= 26) {
421             item.setNumericShortcut(numericChar, numericModifiers);
422         }
423     }
424 
425     /**
426      * Return the modifiers for this menu item's numeric (12-key) shortcut.
427      * The modifier is a combination of {@link KeyEvent#META_META_ON},
428      * {@link KeyEvent#META_CTRL_ON}, {@link KeyEvent#META_ALT_ON},
429      * {@link KeyEvent#META_SHIFT_ON}, {@link KeyEvent#META_SYM_ON},
430      * {@link KeyEvent#META_FUNCTION_ON}.
431      * For example, {@link KeyEvent#META_FUNCTION_ON}|{@link KeyEvent#META_CTRL_ON}
432      *
433      * @return Modifier associated with the numeric shortcut.
434      */
435     public static int getNumericModifiers(MenuItem item) {
436         if (item instanceof SupportMenuItem) {
437             return ((SupportMenuItem) item).getNumericModifiers();
438         }
439         if (Build.VERSION.SDK_INT >= 26) {
440             return item.getNumericModifiers();
441         }
442         return 0;
443     }
444 
445     /**
446      * Change the alphabetic shortcut associated with this item. The shortcut
447      * will be triggered when the key that generates the given character is
448      * pressed along with the modifier keys. Case is not significant and shortcut
449      * characters will be displayed in lower case. Note that menu items with
450      * the characters '\b' or '\n' as shortcuts will get triggered by the
451      * Delete key or Carriage Return key, respectively.
452      * <p>
453      * See {@link Menu} for the menu types that support shortcuts.
454      *
455      * @param alphaChar The alphabetic shortcut key. This is the shortcut when
456      *        using a keyboard with alphabetic keys.
457      * @param alphaModifiers The modifier associated with the shortcut. It should
458      *        be a combination of {@link KeyEvent#META_META_ON}, {@link KeyEvent#META_CTRL_ON},
459      *        {@link KeyEvent#META_ALT_ON}, {@link KeyEvent#META_SHIFT_ON},
460      *        {@link KeyEvent#META_SYM_ON}, {@link KeyEvent#META_FUNCTION_ON}.
461      */
462     public static void setAlphabeticShortcut(MenuItem item, char alphaChar, int alphaModifiers) {
463         if (item instanceof SupportMenuItem) {
464             ((SupportMenuItem) item).setAlphabeticShortcut(alphaChar, alphaModifiers);
465         } else if (Build.VERSION.SDK_INT >= 26) {
466             item.setAlphabeticShortcut(alphaChar, alphaModifiers);
467         }
468     }
469 
470     /**
471      * Return the modifier for this menu item's alphabetic shortcut.
472      * The modifier is a combination of {@link KeyEvent#META_META_ON},
473      * {@link KeyEvent#META_CTRL_ON}, {@link KeyEvent#META_ALT_ON},
474      * {@link KeyEvent#META_SHIFT_ON}, {@link KeyEvent#META_SYM_ON},
475      * {@link KeyEvent#META_FUNCTION_ON}.
476      * For example, {@link KeyEvent#META_FUNCTION_ON}|{@link KeyEvent#META_CTRL_ON}
477      *
478      * @return Modifier associated with the keyboard shortcut.
479      */
480     public static int getAlphabeticModifiers(MenuItem item) {
481         if (item instanceof SupportMenuItem) {
482             return ((SupportMenuItem) item).getAlphabeticModifiers();
483         }
484         if (Build.VERSION.SDK_INT >= 26) {
485             return item.getAlphabeticModifiers();
486         }
487         return 0;
488     }
489 
490     /**
491      * Applies a tint to the item's icon. Does not modify the
492      * current tint mode of that item, which is {@link PorterDuff.Mode#SRC_IN} by default.
493      * <p>
494      * Subsequent calls to {@link MenuItem#setIcon(Drawable)} or {@link MenuItem#setIcon(int)} will
495      * automatically mutate the icon and apply the specified tint and
496      * tint mode.
497      *
498      * @param tint the tint to apply, may be {@code null} to clear tint
499      *
500      * @see #getIconTintList(MenuItem)
501      */
502     public static void setIconTintList(MenuItem item, ColorStateList tint) {
503         if (item instanceof SupportMenuItem) {
504             ((SupportMenuItem) item).setIconTintList(tint);
505         } else if (Build.VERSION.SDK_INT >= 26) {
506             item.setIconTintList(tint);
507         }
508     }
509 
510     /**
511      * @return the tint applied to the item's icon
512      * @see #setIconTintList(MenuItem, ColorStateList)
513      */
514     public static ColorStateList getIconTintList(MenuItem item) {
515         if (item instanceof SupportMenuItem) {
516             return ((SupportMenuItem) item).getIconTintList();
517         }
518         if (Build.VERSION.SDK_INT >= 26) {
519             return item.getIconTintList();
520         }
521         return null;
522     }
523 
524     /**
525      * Specifies the blending mode used to apply the tint specified by
526      * {@link #setIconTintList(MenuItem, ColorStateList)} to the item's icon. The default mode is
527      * {@link PorterDuff.Mode#SRC_IN}.
528      *
529      * @param tintMode the blending mode used to apply the tint, may be
530      *                 {@code null} to clear tint
531      * @see #setIconTintList(MenuItem, ColorStateList)
532      */
533     public static void setIconTintMode(MenuItem item, PorterDuff.Mode tintMode) {
534         if (item instanceof SupportMenuItem) {
535             ((SupportMenuItem) item).setIconTintMode(tintMode);
536         } else if (Build.VERSION.SDK_INT >= 26) {
537             item.setIconTintMode(tintMode);
538         }
539     }
540 
541     /**
542      * Returns the blending mode used to apply the tint to the item's icon, if specified.
543      *
544      * @return the blending mode used to apply the tint to the item's icon
545      * @see #setIconTintMode(MenuItem, PorterDuff.Mode)
546      */
547     public static PorterDuff.Mode getIconTintMode(MenuItem item) {
548         if (item instanceof SupportMenuItem) {
549             return ((SupportMenuItem) item).getIconTintMode();
550         }
551         if (Build.VERSION.SDK_INT >= 26) {
552             return item.getIconTintMode();
553         }
554         return null;
555     }
556 
557     private MenuItemCompat() {}
558 }
559