From 7039cbc6f3a596aee6851014019849490f358f13 Mon Sep 17 00:00:00 2001 From: Vladislav Kaznacheev Date: Wed, 4 Jan 2017 10:15:31 -0800 Subject: [PATCH] Add content description and tooltip to menu item Bug: 34076597 Test: manual Change-Id: Ide32463252457721286c929ab2f8f7bae241835d --- api/current.txt | 4 ++ api/system-current.txt | 4 ++ api/test-current.txt | 4 ++ core/java/android/view/MenuInflater.java | 10 +++++ core/java/android/view/MenuItem.java | 36 +++++++++++++++++ .../android/view/WindowManagerGlobal.java | 13 +++++++ .../android/internal/view/TooltipPopup.java | 22 ++++++++++- .../internal/view/menu/ActionMenuItem.java | 39 +++++++++++++++---- .../view/menu/ActionMenuItemView.java | 21 ++++++++-- .../internal/view/menu/IconMenuItemView.java | 17 ++++++-- .../internal/view/menu/ListMenuItemView.java | 4 +- .../internal/view/menu/MenuItemImpl.java | 39 +++++++++++++++++-- .../internal/widget/FloatingToolbar.java | 36 ++++++++++++----- core/res/res/values/attrs.xml | 6 +++ core/res/res/values/styles.xml | 1 + core/res/res/values/styles_holo.xml | 2 + core/res/res/values/styles_material.xml | 1 + 17 files changed, 229 insertions(+), 30 deletions(-) diff --git a/api/current.txt b/api/current.txt index b017f9d2f5b94..b58947d36c0b3 100644 --- a/api/current.txt +++ b/api/current.txt @@ -42593,6 +42593,7 @@ package android.view { method public abstract android.view.ActionProvider getActionProvider(); method public abstract android.view.View getActionView(); method public abstract char getAlphabeticShortcut(); + method public default java.lang.CharSequence getContentDescription(); method public abstract int getGroupId(); method public abstract android.graphics.drawable.Drawable getIcon(); method public abstract android.content.Intent getIntent(); @@ -42603,6 +42604,7 @@ package android.view { method public abstract android.view.SubMenu getSubMenu(); method public abstract java.lang.CharSequence getTitle(); method public abstract java.lang.CharSequence getTitleCondensed(); + method public default java.lang.CharSequence getTooltip(); method public abstract boolean hasSubMenu(); method public abstract boolean isActionViewExpanded(); method public abstract boolean isCheckable(); @@ -42615,6 +42617,7 @@ package android.view { method public abstract android.view.MenuItem setAlphabeticShortcut(char); method public abstract android.view.MenuItem setCheckable(boolean); method public abstract android.view.MenuItem setChecked(boolean); + method public default android.view.MenuItem setContentDescription(java.lang.CharSequence); method public abstract android.view.MenuItem setEnabled(boolean); method public abstract android.view.MenuItem setIcon(android.graphics.drawable.Drawable); method public abstract android.view.MenuItem setIcon(int); @@ -42628,6 +42631,7 @@ package android.view { method public abstract android.view.MenuItem setTitle(java.lang.CharSequence); method public abstract android.view.MenuItem setTitle(int); method public abstract android.view.MenuItem setTitleCondensed(java.lang.CharSequence); + method public default android.view.MenuItem setTooltip(java.lang.CharSequence); method public abstract android.view.MenuItem setVisible(boolean); field public static final int SHOW_AS_ACTION_ALWAYS = 2; // 0x2 field public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8; // 0x8 diff --git a/api/system-current.txt b/api/system-current.txt index a5534deebd93e..7a0f24c3c9f92 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -45803,6 +45803,7 @@ package android.view { method public abstract android.view.ActionProvider getActionProvider(); method public abstract android.view.View getActionView(); method public abstract char getAlphabeticShortcut(); + method public default java.lang.CharSequence getContentDescription(); method public abstract int getGroupId(); method public abstract android.graphics.drawable.Drawable getIcon(); method public abstract android.content.Intent getIntent(); @@ -45813,6 +45814,7 @@ package android.view { method public abstract android.view.SubMenu getSubMenu(); method public abstract java.lang.CharSequence getTitle(); method public abstract java.lang.CharSequence getTitleCondensed(); + method public default java.lang.CharSequence getTooltip(); method public abstract boolean hasSubMenu(); method public abstract boolean isActionViewExpanded(); method public abstract boolean isCheckable(); @@ -45825,6 +45827,7 @@ package android.view { method public abstract android.view.MenuItem setAlphabeticShortcut(char); method public abstract android.view.MenuItem setCheckable(boolean); method public abstract android.view.MenuItem setChecked(boolean); + method public default android.view.MenuItem setContentDescription(java.lang.CharSequence); method public abstract android.view.MenuItem setEnabled(boolean); method public abstract android.view.MenuItem setIcon(android.graphics.drawable.Drawable); method public abstract android.view.MenuItem setIcon(int); @@ -45838,6 +45841,7 @@ package android.view { method public abstract android.view.MenuItem setTitle(java.lang.CharSequence); method public abstract android.view.MenuItem setTitle(int); method public abstract android.view.MenuItem setTitleCondensed(java.lang.CharSequence); + method public default android.view.MenuItem setTooltip(java.lang.CharSequence); method public abstract android.view.MenuItem setVisible(boolean); field public static final int SHOW_AS_ACTION_ALWAYS = 2; // 0x2 field public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8; // 0x8 diff --git a/api/test-current.txt b/api/test-current.txt index 5db134a289646..4118053af2f7b 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -42880,6 +42880,7 @@ package android.view { method public abstract android.view.ActionProvider getActionProvider(); method public abstract android.view.View getActionView(); method public abstract char getAlphabeticShortcut(); + method public default java.lang.CharSequence getContentDescription(); method public abstract int getGroupId(); method public abstract android.graphics.drawable.Drawable getIcon(); method public abstract android.content.Intent getIntent(); @@ -42890,6 +42891,7 @@ package android.view { method public abstract android.view.SubMenu getSubMenu(); method public abstract java.lang.CharSequence getTitle(); method public abstract java.lang.CharSequence getTitleCondensed(); + method public default java.lang.CharSequence getTooltip(); method public abstract boolean hasSubMenu(); method public abstract boolean isActionViewExpanded(); method public abstract boolean isCheckable(); @@ -42902,6 +42904,7 @@ package android.view { method public abstract android.view.MenuItem setAlphabeticShortcut(char); method public abstract android.view.MenuItem setCheckable(boolean); method public abstract android.view.MenuItem setChecked(boolean); + method public default android.view.MenuItem setContentDescription(java.lang.CharSequence); method public abstract android.view.MenuItem setEnabled(boolean); method public abstract android.view.MenuItem setIcon(android.graphics.drawable.Drawable); method public abstract android.view.MenuItem setIcon(int); @@ -42915,6 +42918,7 @@ package android.view { method public abstract android.view.MenuItem setTitle(java.lang.CharSequence); method public abstract android.view.MenuItem setTitle(int); method public abstract android.view.MenuItem setTitleCondensed(java.lang.CharSequence); + method public default android.view.MenuItem setTooltip(java.lang.CharSequence); method public abstract android.view.MenuItem setVisible(boolean); field public static final int SHOW_AS_ACTION_ALWAYS = 2; // 0x2 field public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8; // 0x8 diff --git a/core/java/android/view/MenuInflater.java b/core/java/android/view/MenuInflater.java index 73ea9ee31bce2..f3f3d40213ec3 100644 --- a/core/java/android/view/MenuInflater.java +++ b/core/java/android/view/MenuInflater.java @@ -334,6 +334,9 @@ public class MenuInflater { private ActionProvider itemActionProvider; + private CharSequence itemContentDescription; + private CharSequence itemTooltip; + private static final int defaultGroupId = NO_ID; private static final int defaultItemId = NO_ID; private static final int defaultItemCategory = 0; @@ -424,6 +427,10 @@ public class MenuInflater { itemActionProvider = null; } + itemContentDescription = + a.getText(com.android.internal.R.styleable.MenuItem_contentDescription); + itemTooltip = a.getText(com.android.internal.R.styleable.MenuItem_tooltip); + a.recycle(); itemAdded = false; @@ -486,6 +493,9 @@ public class MenuInflater { if (itemActionProvider != null) { item.setActionProvider(itemActionProvider); } + + item.setContentDescription(itemContentDescription); + item.setTooltip(itemTooltip); } public MenuItem addItem() { diff --git a/core/java/android/view/MenuItem.java b/core/java/android/view/MenuItem.java index 9e8b97eade6b1..5ced76592756b 100644 --- a/core/java/android/view/MenuItem.java +++ b/core/java/android/view/MenuItem.java @@ -599,4 +599,40 @@ public interface MenuItem { * @return This menu item instance for call chaining */ public MenuItem setOnActionExpandListener(OnActionExpandListener listener); + + /** + * Change the content description associated with this menu item. + * + * @param contentDescription The new content description. + */ + default MenuItem setContentDescription(CharSequence contentDescription) { + return this; + } + + /** + * Retrieve the content description associated with this menu item. + * + * @return The content description. + */ + default CharSequence getContentDescription() { + return null; + } + + /** + * Change the tooltip text associated with this menu item. + * + * @param tooltip The new tooltip text. + */ + default MenuItem setTooltip(CharSequence tooltip) { + return this; + } + + /** + * Retrieve the tooltip text associated with this menu item. + * + * @return The tooltip text. + */ + default CharSequence getTooltip() { + return null; + } } diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index 83fc3623cf206..fc6448ad7eac7 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -249,6 +249,19 @@ public final class WindowManagerGlobal { return views; } + public View getWindowView(IBinder windowToken) { + synchronized (mLock) { + final int numViews = mViews.size(); + for (int i = 0; i < numViews; ++i) { + final View view = mViews.get(i); + if (view.getWindowToken() == windowToken) { + return view; + } + } + } + return null; + } + public View getRootView(String name) { synchronized (mLock) { for (int i = mRoots.size() - 1; i >= 0; --i) { diff --git a/core/java/com/android/internal/view/TooltipPopup.java b/core/java/com/android/internal/view/TooltipPopup.java index c5f6356e92b17..ebbbdbb0d6a6d 100644 --- a/core/java/com/android/internal/view/TooltipPopup.java +++ b/core/java/com/android/internal/view/TooltipPopup.java @@ -19,13 +19,17 @@ package com.android.internal.view; import android.content.Context; import android.graphics.PixelFormat; import android.graphics.Rect; +import android.util.Slog; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; +import android.view.WindowManagerGlobal; import android.widget.TextView; public class TooltipPopup { + private static final String TAG = "TooltipPopup"; + private final Context mContext; private final View mContentView; @@ -34,6 +38,7 @@ public class TooltipPopup { private final WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams(); private final Rect mTmpDisplayFrame = new Rect(); private final int[] mTmpAnchorPos = new int[2]; + private final int[] mTmpAppPos = new int[2]; public TooltipPopup(Context context) { mContext = context; @@ -124,8 +129,21 @@ public class TooltipPopup { fromTouch ? com.android.internal.R.dimen.tooltip_y_offset_touch : com.android.internal.R.dimen.tooltip_y_offset_non_touch); - anchorView.getWindowVisibleDisplayFrame(mTmpDisplayFrame); - anchorView.getLocationInWindow(mTmpAnchorPos); + // Find the main app window. The popup window will be positioned relative to it. + final View appView = WindowManagerGlobal.getInstance().getWindowView( + anchorView.getApplicationWindowToken()); + if (appView == null) { + Slog.e(TAG, "Cannot find app view"); + return; + } + appView.getWindowVisibleDisplayFrame(mTmpDisplayFrame); + appView.getLocationOnScreen(mTmpAppPos); + + anchorView.getLocationOnScreen(mTmpAnchorPos); + mTmpAnchorPos[0] -= mTmpAppPos[0]; + mTmpAnchorPos[1] -= mTmpAppPos[1]; + // mTmpAnchorPos is now relative to the main app window. + outParams.x = mTmpAnchorPos[0] + offsetX - mTmpDisplayFrame.width() / 2; final int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); diff --git a/core/java/com/android/internal/view/menu/ActionMenuItem.java b/core/java/com/android/internal/view/menu/ActionMenuItem.java index ed676bb703e9d..c8697ddba0296 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuItem.java +++ b/core/java/com/android/internal/view/menu/ActionMenuItem.java @@ -39,23 +39,26 @@ public class ActionMenuItem implements MenuItem { private Intent mIntent; private char mShortcutNumericChar; private char mShortcutAlphabeticChar; - + private Drawable mIconDrawable; private int mIconResId = NO_ICON; - + private Context mContext; - + private MenuItem.OnMenuItemClickListener mClickListener; - + + private CharSequence mContentDescription; + private CharSequence mTooltip; + private static final int NO_ICON = 0; - + private int mFlags = ENABLED; private static final int CHECKABLE = 0x00000001; private static final int CHECKED = 0x00000002; private static final int EXCLUSIVE = 0x00000004; private static final int HIDDEN = 0x00000008; private static final int ENABLED = 0x00000010; - + public ActionMenuItem(Context context, int group, int id, int categoryOrder, int ordering, CharSequence title) { mContext = context; @@ -65,7 +68,7 @@ public class ActionMenuItem implements MenuItem { mOrdering = ordering; mTitle = title; } - + public char getAlphabeticShortcut() { return mShortcutAlphabeticChar; } @@ -274,4 +277,26 @@ public class ActionMenuItem implements MenuItem { // No need to save the listener; ActionMenuItem does not support collapsing items. return this; } + + @Override + public MenuItem setContentDescription(CharSequence contentDescription) { + mContentDescription = contentDescription; + return this; + } + + @Override + public CharSequence getContentDescription() { + return mContentDescription; + } + + @Override + public MenuItem setTooltip(CharSequence tooltip) { + mTooltip = tooltip; + return this; + } + + @Override + public CharSequence getTooltip() { + return mTooltip; + } } diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java index af4f77786a5db..4ee59931d8315 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java +++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java @@ -119,7 +119,7 @@ public class ActionMenuItemView extends TextView mItemData = itemData; setIcon(itemData.getIcon()); - setTitle(itemData.getTitleForItemView(this)); // Title only takes effect if there is no icon + setTitle(itemData.getTitleForItemView(this)); // Title is only displayed if there is no icon setId(itemData.getItemId()); setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE); @@ -184,8 +184,22 @@ public class ActionMenuItemView extends TextView setText(visible ? mTitle : null); - // Show the tooltip for items that do not already show text. - setTooltip(visible ? null : mTitle); + final CharSequence contentDescription = mItemData.getContentDescription(); + if (TextUtils.isEmpty(contentDescription)) { + // Use the uncondensed title for content description, but only if the title is not + // shown already. + setContentDescription(visible ? null : mItemData.getTitle()); + } else { + setContentDescription(contentDescription); + } + + final CharSequence tooltip = mItemData.getTooltip(); + if (TextUtils.isEmpty(tooltip)) { + // Use the uncondensed title for tooltip, but only if the title is not shown already. + setTooltip(visible ? null : mItemData.getTitle()); + } else { + setTooltip(tooltip); + } } public void setIcon(Drawable icon) { @@ -221,7 +235,6 @@ public class ActionMenuItemView extends TextView public void setTitle(CharSequence title) { mTitle = title; - setContentDescription(mTitle); updateTextButtonVisibility(); } diff --git a/core/java/com/android/internal/view/menu/IconMenuItemView.java b/core/java/com/android/internal/view/menu/IconMenuItemView.java index 0e0c9b0cf244b..f9ebdbc4f0bb5 100644 --- a/core/java/com/android/internal/view/menu/IconMenuItemView.java +++ b/core/java/com/android/internal/view/menu/IconMenuItemView.java @@ -22,6 +22,7 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.text.TextUtils; import android.util.AttributeSet; import android.view.Gravity; import android.view.SoundEffectConstants; @@ -31,7 +32,7 @@ import android.widget.TextView; import android.text.Layout; /** - * The item view for each item in the {@link IconMenuView}. + * The item view for each item in the {@link IconMenuView}. */ public final class IconMenuItemView extends TextView implements MenuView.ItemView { @@ -104,13 +105,23 @@ public final class IconMenuItemView extends TextView implements MenuView.ItemVie setTitle(title); setIcon(icon); + + if (mItemData != null) { + final CharSequence contentDescription = mItemData.getContentDescription(); + if (TextUtils.isEmpty(contentDescription)) { + setContentDescription(title); + } else { + setContentDescription(contentDescription); + } + setTooltip(mItemData.getTooltip()); + } } - + public void initialize(MenuItemImpl itemData, int menuType) { mItemData = itemData; initialize(itemData.getTitleForItemView(this), itemData.getIcon()); - + setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE); setEnabled(itemData.isEnabled()); } diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java index 25263932bb756..7c9f70902d05c 100644 --- a/core/java/com/android/internal/view/menu/ListMenuItemView.java +++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java @@ -107,13 +107,15 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView mMenuType = menuType; setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE); - + setTitle(itemData.getTitleForItemView(this)); setCheckable(itemData.isCheckable()); setShortcut(itemData.shouldShowShortcut(), itemData.getShortcut()); setIcon(itemData.getIcon()); setEnabled(itemData.isEnabled()); setSubMenuArrowVisible(itemData.hasSubMenu()); + setContentDescription(itemData.getContentDescription()); + setTooltip(itemData.getTooltip()); } public void setForceShowIcon(boolean forceShow) { diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java index 624c9e24e9451..cad0276165825 100644 --- a/core/java/com/android/internal/view/menu/MenuItemImpl.java +++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java @@ -90,16 +90,19 @@ public final class MenuItemImpl implements MenuItem { /** * Current use case is for context menu: Extra information linked to the * View that added this item to the context menu. - */ + */ private ContextMenuInfo mMenuInfo; - + + private CharSequence mContentDescription; + private CharSequence mTooltip; + private static String sLanguage; private static String sPrependShortcutLabel; private static String sEnterShortcutLabel; private static String sDeleteShortcutLabel; private static String sSpaceShortcutLabel; - - + + /** * Instantiates this menu item. * @@ -670,4 +673,32 @@ public final class MenuItemImpl implements MenuItem { public boolean isActionViewExpanded() { return mIsActionViewExpanded; } + + @Override + public MenuItem setContentDescription(CharSequence contentDescription) { + mContentDescription = contentDescription; + + mMenu.onItemsChanged(false); + + return this; + } + + @Override + public CharSequence getContentDescription() { + return mContentDescription; + } + + @Override + public MenuItem setTooltip(CharSequence tooltip) { + mTooltip = tooltip; + + mMenu.onItemsChanged(false); + + return this; + } + + @Override + public CharSequence getTooltip() { + return mTooltip; + } } diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java index 66042086e9257..04e09a85d1bfb 100644 --- a/core/java/com/android/internal/widget/FloatingToolbar.java +++ b/core/java/com/android/internal/widget/FloatingToolbar.java @@ -1585,7 +1585,13 @@ public final class FloatingToolbar { } if (menuItem != null) { menuButton.setText(menuItem.getTitle()); - menuButton.setContentDescription(menuItem.getTitle()); + final CharSequence contentDescription = menuItem.getContentDescription(); + if (TextUtils.isEmpty(contentDescription)) { + menuButton.setContentDescription(menuItem.getTitle()); + } else { + menuButton.setContentDescription(contentDescription); + } + menuButton.setTooltip(menuItem.getTooltip()); menuButton.setMinimumWidth(minimumWidth); } return menuButton; @@ -1629,19 +1635,31 @@ public final class FloatingToolbar { * Creates and returns a menu button for the specified menu item. */ private static View createMenuItemButton(Context context, MenuItem menuItem) { + final View menuItemButton; if (isIconOnlyMenuItem(menuItem)) { - View imageMenuItemButton = LayoutInflater.from(context) + menuItemButton = LayoutInflater.from(context) .inflate(R.layout.floating_popup_menu_image_button, null); - ((ImageButton) imageMenuItemButton + ((ImageButton) menuItemButton .findViewById(R.id.floating_toolbar_menu_item_image_button)) .setImageDrawable(menuItem.getIcon()); - return imageMenuItemButton; + final CharSequence tooltip = menuItem.getTooltip(); + if (TextUtils.isEmpty(tooltip)) { + menuItemButton.setTooltip(menuItem.getTitle()); + } else { + menuItemButton.setTooltip(tooltip); + } + } else { + menuItemButton = LayoutInflater.from(context) + .inflate(R.layout.floating_popup_menu_button, null); + ((Button) menuItemButton).setText(menuItem.getTitle()); + menuItemButton.setTooltip(menuItem.getTooltip()); + } + final CharSequence contentDescription = menuItem.getContentDescription(); + if (TextUtils.isEmpty(contentDescription)) { + menuItemButton.setContentDescription(menuItem.getTitle()); + } else { + menuItemButton.setContentDescription(contentDescription); } - - Button menuItemButton = (Button) LayoutInflater.from(context) - .inflate(R.layout.floating_popup_menu_button, null); - menuItemButton.setText(menuItem.getTitle()); - menuItemButton.setContentDescription(menuItem.getTitle()); return menuItemButton; } diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 05658b018861a..37b3ffc480168 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -7009,6 +7009,12 @@ for more info. --> + + + + + + diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 0f756b94a3212..ce7ab1626aa0c 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -1290,6 +1290,7 @@ please see styles_device_defaults.xml.