1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.appcompat.view.menu;
18 
19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
20 
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.res.ColorStateList;
24 import android.graphics.PorterDuff;
25 import android.graphics.drawable.Drawable;
26 import android.view.ContextMenu.ContextMenuInfo;
27 import android.view.KeyEvent;
28 import android.view.MenuItem;
29 import android.view.SubMenu;
30 import android.view.View;
31 
32 import androidx.annotation.RestrictTo;
33 import androidx.core.content.ContextCompat;
34 import androidx.core.graphics.drawable.DrawableCompat;
35 import androidx.core.internal.view.SupportMenuItem;
36 import androidx.core.view.ActionProvider;
37 
38 import org.jspecify.annotations.NonNull;
39 import org.jspecify.annotations.Nullable;
40 
41 /**
42  */
43 @RestrictTo(LIBRARY_GROUP_PREFIX)
44 public class ActionMenuItem implements SupportMenuItem {
45 
46     private final int mId;
47     private final int mGroup;
48     private final int mOrdering;
49 
50     private CharSequence mTitle;
51     private CharSequence mTitleCondensed;
52     private Intent mIntent;
53     private char mShortcutNumericChar;
54     private int mShortcutNumericModifiers = KeyEvent.META_CTRL_ON;
55     private char mShortcutAlphabeticChar;
56     private int mShortcutAlphabeticModifiers = KeyEvent.META_CTRL_ON;
57 
58     private Drawable mIconDrawable;
59 
60     private Context mContext;
61 
62     private SupportMenuItem.OnMenuItemClickListener mClickListener;
63 
64     private CharSequence mContentDescription;
65     private CharSequence mTooltipText;
66 
67     private ColorStateList mIconTintList = null;
68     private PorterDuff.Mode mIconTintMode = null;
69     private boolean mHasIconTint = false;
70     private boolean mHasIconTintMode = false;
71 
72     private int mFlags = ENABLED;
73     private static final int CHECKABLE = 0x00000001;
74     private static final int CHECKED = 0x00000002;
75     private static final int EXCLUSIVE = 0x00000004;
76     private static final int HIDDEN = 0x00000008;
77     private static final int ENABLED = 0x00000010;
78 
ActionMenuItem(Context context, int group, int id, int categoryOrder, int ordering, CharSequence title)79     public ActionMenuItem(Context context, int group, int id, int categoryOrder, int ordering,
80             CharSequence title) {
81         mContext = context;
82         mId = id;
83         mGroup = group;
84         mOrdering = ordering;
85         mTitle = title;
86     }
87 
88     @Override
getAlphabeticShortcut()89     public char getAlphabeticShortcut() {
90         return mShortcutAlphabeticChar;
91     }
92 
93     @Override
getAlphabeticModifiers()94     public int getAlphabeticModifiers() {
95         return mShortcutAlphabeticModifiers;
96     }
97 
98     @Override
getGroupId()99     public int getGroupId() {
100         return mGroup;
101     }
102 
103     @Override
getIcon()104     public Drawable getIcon() {
105         return mIconDrawable;
106     }
107 
108     @Override
getIntent()109     public Intent getIntent() {
110         return mIntent;
111     }
112 
113     @Override
getItemId()114     public int getItemId() {
115         return mId;
116     }
117 
118     @Override
getMenuInfo()119     public ContextMenuInfo getMenuInfo() {
120         return null;
121     }
122 
123     @Override
getNumericShortcut()124     public char getNumericShortcut() {
125         return mShortcutNumericChar;
126     }
127 
128     @Override
getNumericModifiers()129     public int getNumericModifiers() {
130         return mShortcutNumericModifiers;
131     }
132 
133     @Override
getOrder()134     public int getOrder() {
135         return mOrdering;
136     }
137 
138     @Override
getSubMenu()139     public SubMenu getSubMenu() {
140         return null;
141     }
142 
143     @Override
getTitle()144     public CharSequence getTitle() {
145         return mTitle;
146     }
147 
148     @Override
getTitleCondensed()149     public CharSequence getTitleCondensed() {
150         return mTitleCondensed != null ? mTitleCondensed : mTitle;
151     }
152 
153     @Override
hasSubMenu()154     public boolean hasSubMenu() {
155         return false;
156     }
157 
158     @Override
isCheckable()159     public boolean isCheckable() {
160         return (mFlags & CHECKABLE) != 0;
161     }
162 
163     @Override
isChecked()164     public boolean isChecked() {
165         return (mFlags & CHECKED) != 0;
166     }
167 
168     @Override
isEnabled()169     public boolean isEnabled() {
170         return (mFlags & ENABLED) != 0;
171     }
172 
173     @Override
isVisible()174     public boolean isVisible() {
175         return (mFlags & HIDDEN) == 0;
176     }
177 
178     @Override
setAlphabeticShortcut(char alphaChar)179     public MenuItem setAlphabeticShortcut(char alphaChar) {
180         mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
181         return this;
182     }
183 
184     @Override
setAlphabeticShortcut(char alphaChar, int alphaModifiers)185     public @NonNull MenuItem setAlphabeticShortcut(char alphaChar, int alphaModifiers) {
186         mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
187         mShortcutAlphabeticModifiers = KeyEvent.normalizeMetaState(alphaModifiers);
188         return this;
189     }
190 
191     @Override
setCheckable(boolean checkable)192     public MenuItem setCheckable(boolean checkable) {
193         mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0);
194         return this;
195     }
196 
setExclusiveCheckable(boolean exclusive)197     public ActionMenuItem setExclusiveCheckable(boolean exclusive) {
198         mFlags = (mFlags & ~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
199         return this;
200     }
201 
202     @Override
setChecked(boolean checked)203     public MenuItem setChecked(boolean checked) {
204         mFlags = (mFlags & ~CHECKED) | (checked ? CHECKED : 0);
205         return this;
206     }
207 
208     @Override
setEnabled(boolean enabled)209     public MenuItem setEnabled(boolean enabled) {
210         mFlags = (mFlags & ~ENABLED) | (enabled ? ENABLED : 0);
211         return this;
212     }
213 
214     @Override
setIcon(Drawable icon)215     public MenuItem setIcon(Drawable icon) {
216         mIconDrawable = icon;
217 
218         applyIconTint();
219         return this;
220     }
221 
222     @Override
setIcon(int iconRes)223     public MenuItem setIcon(int iconRes) {
224         mIconDrawable = ContextCompat.getDrawable(mContext, iconRes);
225 
226         applyIconTint();
227         return this;
228     }
229 
230     @Override
setIntent(Intent intent)231     public MenuItem setIntent(Intent intent) {
232         mIntent = intent;
233         return this;
234     }
235 
236     @Override
setNumericShortcut(char numericChar)237     public MenuItem setNumericShortcut(char numericChar) {
238         mShortcutNumericChar = numericChar;
239         return this;
240     }
241 
242     @Override
setNumericShortcut(char numericChar, int numericModifiers)243     public @NonNull MenuItem setNumericShortcut(char numericChar, int numericModifiers) {
244         mShortcutNumericChar = numericChar;
245         mShortcutNumericModifiers = KeyEvent.normalizeMetaState(numericModifiers);
246         return this;
247     }
248 
249     @Override
setOnMenuItemClickListener(OnMenuItemClickListener menuItemClickListener)250     public MenuItem setOnMenuItemClickListener(OnMenuItemClickListener menuItemClickListener) {
251         mClickListener = menuItemClickListener;
252         return this;
253     }
254 
255     @Override
setShortcut(char numericChar, char alphaChar)256     public MenuItem setShortcut(char numericChar, char alphaChar) {
257         mShortcutNumericChar = numericChar;
258         mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
259         return this;
260     }
261 
262     @Override
setShortcut(char numericChar, char alphaChar, int numericModifiers, int alphaModifiers)263     public @NonNull MenuItem setShortcut(char numericChar, char alphaChar, int numericModifiers,
264             int alphaModifiers) {
265         mShortcutNumericChar = numericChar;
266         mShortcutNumericModifiers = KeyEvent.normalizeMetaState(numericModifiers);
267         mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
268         mShortcutAlphabeticModifiers = KeyEvent.normalizeMetaState(alphaModifiers);
269         return this;
270     }
271 
272     @Override
setTitle(CharSequence title)273     public MenuItem setTitle(CharSequence title) {
274         mTitle = title;
275         return this;
276     }
277 
278     @Override
setTitle(int title)279     public MenuItem setTitle(int title) {
280         mTitle = mContext.getResources().getString(title);
281         return this;
282     }
283 
284     @Override
setTitleCondensed(CharSequence title)285     public MenuItem setTitleCondensed(CharSequence title) {
286         mTitleCondensed = title;
287         return this;
288     }
289 
290     @Override
setVisible(boolean visible)291     public MenuItem setVisible(boolean visible) {
292         mFlags = (mFlags & HIDDEN) | (visible ? 0 : HIDDEN);
293         return this;
294     }
295 
invoke()296     public boolean invoke() {
297         if (mClickListener != null && mClickListener.onMenuItemClick(this)) {
298             return true;
299         }
300 
301         if (mIntent != null) {
302             mContext.startActivity(mIntent);
303             return true;
304         }
305 
306         return false;
307     }
308 
309     @Override
setShowAsAction(int show)310     public void setShowAsAction(int show) {
311         // Do nothing. ActionMenuItems always show as action buttons.
312     }
313 
314     @Override
requiresActionButton()315     public boolean requiresActionButton() {
316         return true;
317     }
318 
319     @Override
requiresOverflow()320     public boolean requiresOverflow() {
321         return false;
322     }
323 
324     @Override
setActionView(View actionView)325     public @NonNull SupportMenuItem setActionView(View actionView) {
326         throw new UnsupportedOperationException();
327     }
328 
329     @Override
getActionView()330     public View getActionView() {
331         return null;
332     }
333 
334     @Override
setActionProvider(android.view.ActionProvider actionProvider)335     public MenuItem setActionProvider(android.view.ActionProvider actionProvider) {
336         throw new UnsupportedOperationException();
337     }
338 
339     @Override
getActionProvider()340     public android.view.ActionProvider getActionProvider() {
341         throw new UnsupportedOperationException();
342     }
343 
344     @Override
setActionView(int resId)345     public @NonNull SupportMenuItem setActionView(int resId) {
346         throw new UnsupportedOperationException();
347     }
348 
349     @Override
getSupportActionProvider()350     public ActionProvider getSupportActionProvider() {
351         return null;
352     }
353 
354     @Override
setSupportActionProvider(ActionProvider actionProvider)355     public @NonNull SupportMenuItem setSupportActionProvider(ActionProvider actionProvider) {
356         throw new UnsupportedOperationException();
357     }
358 
359     @Override
setShowAsActionFlags(int actionEnum)360     public @NonNull SupportMenuItem setShowAsActionFlags(int actionEnum) {
361         setShowAsAction(actionEnum);
362         return this;
363     }
364 
365     @Override
expandActionView()366     public boolean expandActionView() {
367         return false;
368     }
369 
370     @Override
collapseActionView()371     public boolean collapseActionView() {
372         return false;
373     }
374 
375     @Override
isActionViewExpanded()376     public boolean isActionViewExpanded() {
377         return false;
378     }
379 
380     @Override
setOnActionExpandListener(MenuItem.OnActionExpandListener listener)381     public MenuItem setOnActionExpandListener(MenuItem.OnActionExpandListener listener) {
382         throw new UnsupportedOperationException();
383     }
384 
385     @Override
setContentDescription(CharSequence contentDescription)386     public @NonNull SupportMenuItem setContentDescription(CharSequence contentDescription) {
387         mContentDescription = contentDescription;
388         return this;
389     }
390 
391     @Override
getContentDescription()392     public CharSequence getContentDescription() {
393         return mContentDescription;
394     }
395 
396     @Override
setTooltipText(CharSequence tooltipText)397     public @NonNull SupportMenuItem setTooltipText(CharSequence tooltipText) {
398         mTooltipText = tooltipText;
399         return this;
400     }
401 
402     @Override
getTooltipText()403     public CharSequence getTooltipText() {
404         return mTooltipText;
405     }
406 
407     @Override
setIconTintList(@ullable ColorStateList iconTintList)408     public @NonNull MenuItem setIconTintList(@Nullable ColorStateList iconTintList) {
409         mIconTintList = iconTintList;
410         mHasIconTint = true;
411 
412         applyIconTint();
413 
414         return this;
415     }
416 
417     @Override
getIconTintList()418     public ColorStateList getIconTintList() {
419         return mIconTintList;
420     }
421 
422     @Override
setIconTintMode(PorterDuff.Mode iconTintMode)423     public @NonNull MenuItem setIconTintMode(PorterDuff.Mode iconTintMode) {
424         mIconTintMode = iconTintMode;
425         mHasIconTintMode = true;
426 
427         applyIconTint();
428 
429         return this;
430     }
431 
432     @Override
getIconTintMode()433     public PorterDuff.Mode getIconTintMode() {
434         return mIconTintMode;
435     }
436 
applyIconTint()437     private void applyIconTint() {
438         if (mIconDrawable != null && (mHasIconTint || mHasIconTintMode)) {
439             mIconDrawable = DrawableCompat.wrap(mIconDrawable);
440             mIconDrawable = mIconDrawable.mutate();
441 
442             if (mHasIconTint) {
443                 DrawableCompat.setTintList(mIconDrawable, mIconTintList);
444             }
445 
446             if (mHasIconTintMode) {
447                 DrawableCompat.setTintMode(mIconDrawable, mIconTintMode);
448             }
449         }
450     }
451 }
452