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