Home | History | Annotate | Download | only in menu
      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 com.android.internal.view.menu.MenuView.ItemView;
     20 
     21 import android.content.ActivityNotFoundException;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.graphics.drawable.Drawable;
     25 import android.util.Log;
     26 import android.view.ActionProvider;
     27 import android.view.ContextMenu.ContextMenuInfo;
     28 import android.view.LayoutInflater;
     29 import android.view.MenuItem;
     30 import android.view.SubMenu;
     31 import android.view.View;
     32 import android.view.ViewDebug;
     33 import android.widget.LinearLayout;
     34 
     35 /**
     36  * @hide
     37  */
     38 public final class MenuItemImpl implements MenuItem {
     39     private static final String TAG = "MenuItemImpl";
     40 
     41     private static final int SHOW_AS_ACTION_MASK = SHOW_AS_ACTION_NEVER |
     42             SHOW_AS_ACTION_IF_ROOM |
     43             SHOW_AS_ACTION_ALWAYS;
     44 
     45     private final int mId;
     46     private final int mGroup;
     47     private final int mCategoryOrder;
     48     private final int mOrdering;
     49     private CharSequence mTitle;
     50     private CharSequence mTitleCondensed;
     51     private Intent mIntent;
     52     private char mShortcutNumericChar;
     53     private char mShortcutAlphabeticChar;
     54 
     55     /** The icon's drawable which is only created as needed */
     56     private Drawable mIconDrawable;
     57     /**
     58      * The icon's resource ID which is used to get the Drawable when it is
     59      * needed (if the Drawable isn't already obtained--only one of the two is
     60      * needed).
     61      */
     62     private int mIconResId = NO_ICON;
     63 
     64     /** The menu to which this item belongs */
     65     private MenuBuilder mMenu;
     66     /** If this item should launch a sub menu, this is the sub menu to launch */
     67     private SubMenuBuilder mSubMenu;
     68 
     69     private Runnable mItemCallback;
     70     private MenuItem.OnMenuItemClickListener mClickListener;
     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     private static final int IS_ACTION      = 0x00000020;
     79 
     80     private int mShowAsAction = SHOW_AS_ACTION_NEVER;
     81 
     82     private View mActionView;
     83     private ActionProvider mActionProvider;
     84     private OnActionExpandListener mOnActionExpandListener;
     85     private boolean mIsActionViewExpanded = false;
     86 
     87     /** Used for the icon resource ID if this item does not have an icon */
     88     static final int NO_ICON = 0;
     89 
     90     /**
     91      * Current use case is for context menu: Extra information linked to the
     92      * View that added this item to the context menu.
     93      */
     94     private ContextMenuInfo mMenuInfo;
     95 
     96     private static String sPrependShortcutLabel;
     97     private static String sEnterShortcutLabel;
     98     private static String sDeleteShortcutLabel;
     99     private static String sSpaceShortcutLabel;
    100 
    101 
    102     /**
    103      * Instantiates this menu item.
    104      *
    105      * @param menu
    106      * @param group Item ordering grouping control. The item will be added after
    107      *            all other items whose order is <= this number, and before any
    108      *            that are larger than it. This can also be used to define
    109      *            groups of items for batch state changes. Normally use 0.
    110      * @param id Unique item ID. Use 0 if you do not need a unique ID.
    111      * @param categoryOrder The ordering for this item.
    112      * @param title The text to display for the item.
    113      */
    114     MenuItemImpl(MenuBuilder menu, int group, int id, int categoryOrder, int ordering,
    115             CharSequence title, int showAsAction) {
    116 
    117         if (sPrependShortcutLabel == null) {
    118             // This is instantiated from the UI thread, so no chance of sync issues
    119             sPrependShortcutLabel = menu.getContext().getResources().getString(
    120                     com.android.internal.R.string.prepend_shortcut_label);
    121             sEnterShortcutLabel = menu.getContext().getResources().getString(
    122                     com.android.internal.R.string.menu_enter_shortcut_label);
    123             sDeleteShortcutLabel = menu.getContext().getResources().getString(
    124                     com.android.internal.R.string.menu_delete_shortcut_label);
    125             sSpaceShortcutLabel = menu.getContext().getResources().getString(
    126                     com.android.internal.R.string.menu_space_shortcut_label);
    127         }
    128 
    129         mMenu = menu;
    130         mId = id;
    131         mGroup = group;
    132         mCategoryOrder = categoryOrder;
    133         mOrdering = ordering;
    134         mTitle = title;
    135         mShowAsAction = showAsAction;
    136     }
    137 
    138     /**
    139      * Invokes the item by calling various listeners or callbacks.
    140      *
    141      * @return true if the invocation was handled, false otherwise
    142      */
    143     public boolean invoke() {
    144         if (mClickListener != null &&
    145             mClickListener.onMenuItemClick(this)) {
    146             return true;
    147         }
    148 
    149         if (mMenu.dispatchMenuItemSelected(mMenu.getRootMenu(), this)) {
    150             return true;
    151         }
    152 
    153         if (mItemCallback != null) {
    154             mItemCallback.run();
    155             return true;
    156         }
    157 
    158         if (mIntent != null) {
    159             try {
    160                 mMenu.getContext().startActivity(mIntent);
    161                 return true;
    162             } catch (ActivityNotFoundException e) {
    163                 Log.e(TAG, "Can't find activity to handle intent; ignoring", e);
    164             }
    165         }
    166 
    167         if (mActionProvider != null && mActionProvider.onPerformDefaultAction()) {
    168             return true;
    169         }
    170 
    171         return false;
    172     }
    173 
    174     public boolean isEnabled() {
    175         return (mFlags & ENABLED) != 0;
    176     }
    177 
    178     public MenuItem setEnabled(boolean enabled) {
    179         if (enabled) {
    180             mFlags |= ENABLED;
    181         } else {
    182             mFlags &= ~ENABLED;
    183         }
    184 
    185         mMenu.onItemsChanged(false);
    186 
    187         return this;
    188     }
    189 
    190     public int getGroupId() {
    191         return mGroup;
    192     }
    193 
    194     @ViewDebug.CapturedViewProperty
    195     public int getItemId() {
    196         return mId;
    197     }
    198 
    199     public int getOrder() {
    200         return mCategoryOrder;
    201     }
    202 
    203     public int getOrdering() {
    204         return mOrdering;
    205     }
    206 
    207     public Intent getIntent() {
    208         return mIntent;
    209     }
    210 
    211     public MenuItem setIntent(Intent intent) {
    212         mIntent = intent;
    213         return this;
    214     }
    215 
    216     Runnable getCallback() {
    217         return mItemCallback;
    218     }
    219 
    220     public MenuItem setCallback(Runnable callback) {
    221         mItemCallback = callback;
    222         return this;
    223     }
    224 
    225     public char getAlphabeticShortcut() {
    226         return mShortcutAlphabeticChar;
    227     }
    228 
    229     public MenuItem setAlphabeticShortcut(char alphaChar) {
    230         if (mShortcutAlphabeticChar == alphaChar) return this;
    231 
    232         mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
    233 
    234         mMenu.onItemsChanged(false);
    235 
    236         return this;
    237     }
    238 
    239     public char getNumericShortcut() {
    240         return mShortcutNumericChar;
    241     }
    242 
    243     public MenuItem setNumericShortcut(char numericChar) {
    244         if (mShortcutNumericChar == numericChar) return this;
    245 
    246         mShortcutNumericChar = numericChar;
    247 
    248         mMenu.onItemsChanged(false);
    249 
    250         return this;
    251     }
    252 
    253     public MenuItem setShortcut(char numericChar, char alphaChar) {
    254         mShortcutNumericChar = numericChar;
    255         mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
    256 
    257         mMenu.onItemsChanged(false);
    258 
    259         return this;
    260     }
    261 
    262     /**
    263      * @return The active shortcut (based on QWERTY-mode of the menu).
    264      */
    265     char getShortcut() {
    266         return (mMenu.isQwertyMode() ? mShortcutAlphabeticChar : mShortcutNumericChar);
    267     }
    268 
    269     /**
    270      * @return The label to show for the shortcut. This includes the chording
    271      *         key (for example 'Menu+a'). Also, any non-human readable
    272      *         characters should be human readable (for example 'Menu+enter').
    273      */
    274     String getShortcutLabel() {
    275 
    276         char shortcut = getShortcut();
    277         if (shortcut == 0) {
    278             return "";
    279         }
    280 
    281         StringBuilder sb = new StringBuilder(sPrependShortcutLabel);
    282         switch (shortcut) {
    283 
    284             case '\n':
    285                 sb.append(sEnterShortcutLabel);
    286                 break;
    287 
    288             case '\b':
    289                 sb.append(sDeleteShortcutLabel);
    290                 break;
    291 
    292             case ' ':
    293                 sb.append(sSpaceShortcutLabel);
    294                 break;
    295 
    296             default:
    297                 sb.append(shortcut);
    298                 break;
    299         }
    300 
    301         return sb.toString();
    302     }
    303 
    304     /**
    305      * @return Whether this menu item should be showing shortcuts (depends on
    306      *         whether the menu should show shortcuts and whether this item has
    307      *         a shortcut defined)
    308      */
    309     boolean shouldShowShortcut() {
    310         // Show shortcuts if the menu is supposed to show shortcuts AND this item has a shortcut
    311         return mMenu.isShortcutsVisible() && (getShortcut() != 0);
    312     }
    313 
    314     public SubMenu getSubMenu() {
    315         return mSubMenu;
    316     }
    317 
    318     public boolean hasSubMenu() {
    319         return mSubMenu != null;
    320     }
    321 
    322     void setSubMenu(SubMenuBuilder subMenu) {
    323         mSubMenu = subMenu;
    324 
    325         subMenu.setHeaderTitle(getTitle());
    326     }
    327 
    328     @ViewDebug.CapturedViewProperty
    329     public CharSequence getTitle() {
    330         return mTitle;
    331     }
    332 
    333     /**
    334      * Gets the title for a particular {@link ItemView}
    335      *
    336      * @param itemView The ItemView that is receiving the title
    337      * @return Either the title or condensed title based on what the ItemView
    338      *         prefers
    339      */
    340     CharSequence getTitleForItemView(MenuView.ItemView itemView) {
    341         return ((itemView != null) && itemView.prefersCondensedTitle())
    342                 ? getTitleCondensed()
    343                 : getTitle();
    344     }
    345 
    346     public MenuItem setTitle(CharSequence title) {
    347         mTitle = title;
    348 
    349         mMenu.onItemsChanged(false);
    350 
    351         if (mSubMenu != null) {
    352             mSubMenu.setHeaderTitle(title);
    353         }
    354 
    355         return this;
    356     }
    357 
    358     public MenuItem setTitle(int title) {
    359         return setTitle(mMenu.getContext().getString(title));
    360     }
    361 
    362     public CharSequence getTitleCondensed() {
    363         return mTitleCondensed != null ? mTitleCondensed : mTitle;
    364     }
    365 
    366     public MenuItem setTitleCondensed(CharSequence title) {
    367         mTitleCondensed = title;
    368 
    369         // Could use getTitle() in the loop below, but just cache what it would do here
    370         if (title == null) {
    371             title = mTitle;
    372         }
    373 
    374         mMenu.onItemsChanged(false);
    375 
    376         return this;
    377     }
    378 
    379     public Drawable getIcon() {
    380         if (mIconDrawable != null) {
    381             return mIconDrawable;
    382         }
    383 
    384         if (mIconResId != NO_ICON) {
    385             Drawable icon =  mMenu.getResources().getDrawable(mIconResId);
    386             mIconResId = NO_ICON;
    387             mIconDrawable = icon;
    388             return icon;
    389         }
    390 
    391         return null;
    392     }
    393 
    394     public MenuItem setIcon(Drawable icon) {
    395         mIconResId = NO_ICON;
    396         mIconDrawable = icon;
    397         mMenu.onItemsChanged(false);
    398 
    399         return this;
    400     }
    401 
    402     public MenuItem setIcon(int iconResId) {
    403         mIconDrawable = null;
    404         mIconResId = iconResId;
    405 
    406         // If we have a view, we need to push the Drawable to them
    407         mMenu.onItemsChanged(false);
    408 
    409         return this;
    410     }
    411 
    412     public boolean isCheckable() {
    413         return (mFlags & CHECKABLE) == CHECKABLE;
    414     }
    415 
    416     public MenuItem setCheckable(boolean checkable) {
    417         final int oldFlags = mFlags;
    418         mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0);
    419         if (oldFlags != mFlags) {
    420             mMenu.onItemsChanged(false);
    421         }
    422 
    423         return this;
    424     }
    425 
    426     public void setExclusiveCheckable(boolean exclusive) {
    427         mFlags = (mFlags & ~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
    428     }
    429 
    430     public boolean isExclusiveCheckable() {
    431         return (mFlags & EXCLUSIVE) != 0;
    432     }
    433 
    434     public boolean isChecked() {
    435         return (mFlags & CHECKED) == CHECKED;
    436     }
    437 
    438     public MenuItem setChecked(boolean checked) {
    439         if ((mFlags & EXCLUSIVE) != 0) {
    440             // Call the method on the Menu since it knows about the others in this
    441             // exclusive checkable group
    442             mMenu.setExclusiveItemChecked(this);
    443         } else {
    444             setCheckedInt(checked);
    445         }
    446 
    447         return this;
    448     }
    449 
    450     void setCheckedInt(boolean checked) {
    451         final int oldFlags = mFlags;
    452         mFlags = (mFlags & ~CHECKED) | (checked ? CHECKED : 0);
    453         if (oldFlags != mFlags) {
    454             mMenu.onItemsChanged(false);
    455         }
    456     }
    457 
    458     public boolean isVisible() {
    459         if (mActionProvider != null && mActionProvider.overridesItemVisibility()) {
    460             return (mFlags & HIDDEN) == 0 && mActionProvider.isVisible();
    461         }
    462         return (mFlags & HIDDEN) == 0;
    463     }
    464 
    465     /**
    466      * Changes the visibility of the item. This method DOES NOT notify the
    467      * parent menu of a change in this item, so this should only be called from
    468      * methods that will eventually trigger this change.  If unsure, use {@link #setVisible(boolean)}
    469      * instead.
    470      *
    471      * @param shown Whether to show (true) or hide (false).
    472      * @return Whether the item's shown state was changed
    473      */
    474     boolean setVisibleInt(boolean shown) {
    475         final int oldFlags = mFlags;
    476         mFlags = (mFlags & ~HIDDEN) | (shown ? 0 : HIDDEN);
    477         return oldFlags != mFlags;
    478     }
    479 
    480     public MenuItem setVisible(boolean shown) {
    481         // Try to set the shown state to the given state. If the shown state was changed
    482         // (i.e. the previous state isn't the same as given state), notify the parent menu that
    483         // the shown state has changed for this item
    484         if (setVisibleInt(shown)) mMenu.onItemVisibleChanged(this);
    485 
    486         return this;
    487     }
    488 
    489    public MenuItem setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener clickListener) {
    490         mClickListener = clickListener;
    491         return this;
    492     }
    493 
    494     @Override
    495     public String toString() {
    496         return mTitle.toString();
    497     }
    498 
    499     void setMenuInfo(ContextMenuInfo menuInfo) {
    500         mMenuInfo = menuInfo;
    501     }
    502 
    503     public ContextMenuInfo getMenuInfo() {
    504         return mMenuInfo;
    505     }
    506 
    507     public void actionFormatChanged() {
    508         mMenu.onItemActionRequestChanged(this);
    509     }
    510 
    511     /**
    512      * @return Whether the menu should show icons for menu items.
    513      */
    514     public boolean shouldShowIcon() {
    515         return mMenu.getOptionalIconsVisible();
    516     }
    517 
    518     public boolean isActionButton() {
    519         return (mFlags & IS_ACTION) == IS_ACTION;
    520     }
    521 
    522     public boolean requestsActionButton() {
    523         return (mShowAsAction & SHOW_AS_ACTION_IF_ROOM) == SHOW_AS_ACTION_IF_ROOM;
    524     }
    525 
    526     public boolean requiresActionButton() {
    527         return (mShowAsAction & SHOW_AS_ACTION_ALWAYS) == SHOW_AS_ACTION_ALWAYS;
    528     }
    529 
    530     public void setIsActionButton(boolean isActionButton) {
    531         if (isActionButton) {
    532             mFlags |= IS_ACTION;
    533         } else {
    534             mFlags &= ~IS_ACTION;
    535         }
    536     }
    537 
    538     public boolean showsTextAsAction() {
    539         return (mShowAsAction & SHOW_AS_ACTION_WITH_TEXT) == SHOW_AS_ACTION_WITH_TEXT;
    540     }
    541 
    542     public void setShowAsAction(int actionEnum) {
    543         switch (actionEnum & SHOW_AS_ACTION_MASK) {
    544             case SHOW_AS_ACTION_ALWAYS:
    545             case SHOW_AS_ACTION_IF_ROOM:
    546             case SHOW_AS_ACTION_NEVER:
    547                 // Looks good!
    548                 break;
    549 
    550             default:
    551                 // Mutually exclusive options selected!
    552                 throw new IllegalArgumentException("SHOW_AS_ACTION_ALWAYS, SHOW_AS_ACTION_IF_ROOM,"
    553                         + " and SHOW_AS_ACTION_NEVER are mutually exclusive.");
    554         }
    555         mShowAsAction = actionEnum;
    556         mMenu.onItemActionRequestChanged(this);
    557     }
    558 
    559     public MenuItem setActionView(View view) {
    560         mActionView = view;
    561         mActionProvider = null;
    562         if (view != null && view.getId() == View.NO_ID && mId > 0) {
    563             view.setId(mId);
    564         }
    565         mMenu.onItemActionRequestChanged(this);
    566         return this;
    567     }
    568 
    569     public MenuItem setActionView(int resId) {
    570         final Context context = mMenu.getContext();
    571         final LayoutInflater inflater = LayoutInflater.from(context);
    572         setActionView(inflater.inflate(resId, new LinearLayout(context), false));
    573         return this;
    574     }
    575 
    576     public View getActionView() {
    577         if (mActionView != null) {
    578             return mActionView;
    579         } else if (mActionProvider != null) {
    580             mActionView = mActionProvider.onCreateActionView(this);
    581             return mActionView;
    582         } else {
    583             return null;
    584         }
    585     }
    586 
    587     public ActionProvider getActionProvider() {
    588         return mActionProvider;
    589     }
    590 
    591     public MenuItem setActionProvider(ActionProvider actionProvider) {
    592         if (mActionProvider != null) {
    593             mActionProvider.setVisibilityListener(null);
    594         }
    595         mActionView = null;
    596         mActionProvider = actionProvider;
    597         mMenu.onItemsChanged(true); // Measurement can be changed
    598         mActionProvider.setVisibilityListener(new ActionProvider.VisibilityListener() {
    599             @Override public void onActionProviderVisibilityChanged(boolean isVisible) {
    600                 mMenu.onItemVisibleChanged(MenuItemImpl.this);
    601             }
    602         });
    603         return this;
    604     }
    605 
    606     @Override
    607     public MenuItem setShowAsActionFlags(int actionEnum) {
    608         setShowAsAction(actionEnum);
    609         return this;
    610     }
    611 
    612     @Override
    613     public boolean expandActionView() {
    614         if ((mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) == 0 || mActionView == null) {
    615             return false;
    616         }
    617 
    618         if (mOnActionExpandListener == null ||
    619                 mOnActionExpandListener.onMenuItemActionExpand(this)) {
    620             return mMenu.expandItemActionView(this);
    621         }
    622 
    623         return false;
    624     }
    625 
    626     @Override
    627     public boolean collapseActionView() {
    628         if ((mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) == 0) {
    629             return false;
    630         }
    631         if (mActionView == null) {
    632             // We're already collapsed if we have no action view.
    633             return true;
    634         }
    635 
    636         if (mOnActionExpandListener == null ||
    637                 mOnActionExpandListener.onMenuItemActionCollapse(this)) {
    638             return mMenu.collapseItemActionView(this);
    639         }
    640 
    641         return false;
    642     }
    643 
    644     @Override
    645     public MenuItem setOnActionExpandListener(OnActionExpandListener listener) {
    646         mOnActionExpandListener = listener;
    647         return this;
    648     }
    649 
    650     public boolean hasCollapsibleActionView() {
    651         return (mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) != 0 && mActionView != null;
    652     }
    653 
    654     public void setActionViewExpanded(boolean isExpanded) {
    655         mIsActionViewExpanded = isExpanded;
    656         mMenu.onItemsChanged(false);
    657     }
    658 
    659     public boolean isActionViewExpanded() {
    660         return mIsActionViewExpanded;
    661     }
    662 }
    663