diff options
6 files changed, 205 insertions, 24 deletions
diff --git a/core/java/com/android/internal/view/menu/ActionMenuView.java b/core/java/com/android/internal/view/menu/ActionMenuView.java index c3fe5dcba0c7..7024a2748cd8 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuView.java +++ b/core/java/com/android/internal/view/menu/ActionMenuView.java @@ -16,10 +16,12 @@ package com.android.internal.view.menu; import android.content.Context; +import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.ViewGroup; +import android.widget.ImageButton; import android.widget.LinearLayout; import java.util.ArrayList; @@ -35,6 +37,8 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo private int mItemPadding; private int mItemMargin; private int mMaxItems; + private boolean mReserveOverflow; + private OverflowMenuButton mOverflowButton; public ActionMenuView(Context context) { this(context, null); @@ -56,6 +60,15 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo final int itemSpace = size + mItemPadding; mMaxItems = spaceAvailable / (itemSpace > 0 ? itemSpace : 1); + + // TODO There has to be a better way to indicate that we don't have a hard menu key. + final int screen = res.getConfiguration().screenLayout; + mReserveOverflow = (screen & Configuration.SCREENLAYOUT_SIZE_MASK) == + Configuration.SCREENLAYOUT_SIZE_XLARGE; + } + + public boolean isOverflowReserved() { + return mReserveOverflow; } @Override @@ -101,9 +114,10 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo } public void updateChildren(boolean cleared) { + final boolean reserveOverflow = mReserveOverflow; removeAllViews(); - final ArrayList<MenuItemImpl> itemsToShow = mMenu.getActionItems(); + final ArrayList<MenuItemImpl> itemsToShow = mMenu.getActionItems(reserveOverflow); final int itemCount = itemsToShow.size(); for (int i = 0; i < itemCount; i++) { @@ -111,10 +125,53 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo addItemView((ActionMenuItemView) itemData.getItemView(MenuBuilder.TYPE_ACTION_BUTTON, this)); } + + if (reserveOverflow) { + if (mMenu.getNonActionItems(true).size() > 0) { + OverflowMenuButton button = new OverflowMenuButton(mContext); + addView(button); + mOverflowButton = button; + } else { + mOverflowButton = null; + } + } + } + + public boolean showOverflowMenu() { + if (mOverflowButton != null) { + MenuPopupHelper popup = new MenuPopupHelper(getContext(), mMenu, mOverflowButton, true); + popup.show(); + return true; + } + return false; } private void addItemView(ActionMenuItemView view) { view.setItemInvoker(this); addView(view); } + + private class OverflowMenuButton extends ImageButton { + public OverflowMenuButton(Context context) { + super(context, null, com.android.internal.R.attr.actionButtonStyle); + + final Resources res = context.getResources(); + setClickable(true); + setFocusable(true); + // TODO setTitle() to a localized string for accessibility + setImageDrawable(res.getDrawable(com.android.internal.R.drawable.ic_menu_more)); + setVisibility(VISIBLE); + setEnabled(true); + } + + @Override + public boolean performClick() { + if (super.performClick()) { + return true; + } + + showOverflowMenu(); + return true; + } + } } diff --git a/core/java/com/android/internal/view/menu/IconMenuView.java b/core/java/com/android/internal/view/menu/IconMenuView.java index bbf7c689e298..178dcde9a030 100644 --- a/core/java/com/android/internal/view/menu/IconMenuView.java +++ b/core/java/com/android/internal/view/menu/IconMenuView.java @@ -337,7 +337,9 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi // This method does a clear refresh of children removeAllViews(); - final ArrayList<MenuItemImpl> itemsToShow = mMenu.getNonActionItems(); + // IconMenuView never wants content sorted for an overflow action button, since + // it is never used in the presence of an overflow button. + final ArrayList<MenuItemImpl> itemsToShow = mMenu.getNonActionItems(false); final int numItems = itemsToShow.size(); final int numItemsThatCanFit = mMaxItems; // Minimum of the num that can fit and the num that we have diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java index 94a9f65a0dbe..215d809524c6 100644 --- a/core/java/com/android/internal/view/menu/MenuBuilder.java +++ b/core/java/com/android/internal/view/menu/MenuBuilder.java @@ -165,6 +165,12 @@ public class MenuBuilder implements Menu { private boolean mIsActionItemsStale; /** + * Whether the process of granting space as action items should reserve a space for + * an overflow option in the action list. + */ + private boolean mReserveActionOverflow; + + /** * Current use case is Context Menus: As Views populate the context menu, each one has * extra information that should be passed along. This is the current menu info that * should be set on all items added to this menu. @@ -670,6 +676,11 @@ public class MenuBuilder implements Menu { return mItems.get(index); } + public MenuItem getOverflowItem(int index) { + flagActionItems(true); + return mNonActionItems.get(index); + } + public boolean isShortcutKey(int keyCode, KeyEvent event) { return findItemWithShortcutForKey(keyCode, event) != null; } @@ -986,22 +997,41 @@ public class MenuBuilder implements Menu { return mVisibleItems; } - private void flagActionItems() { + private void flagActionItems(boolean reserveActionOverflow) { + if (reserveActionOverflow != mReserveActionOverflow) { + mReserveActionOverflow = reserveActionOverflow; + mIsActionItemsStale = true; + } + if (!mIsActionItemsStale) { return; } - + final ArrayList<MenuItemImpl> visibleItems = getVisibleItems(); final int itemsSize = visibleItems.size(); int maxActions = mMaxActionItems; - + + int requiredItems = 0; + int requestedItems = 0; + boolean hasOverflow = false; for (int i = 0; i < itemsSize; i++) { MenuItemImpl item = visibleItems.get(i); if (item.requiresActionButton()) { - maxActions--; + requiredItems++; + } else if (item.requestsActionButton()) { + requestedItems++; + } else { + hasOverflow = true; } } - + + // Reserve a spot for the overflow item if needed. + if (reserveActionOverflow && + (hasOverflow || requiredItems + requestedItems > maxActions)) { + maxActions--; + } + maxActions -= requiredItems; + // Flag as many more requested items as will fit. for (int i = 0; i < itemsSize; i++) { MenuItemImpl item = visibleItems.get(i); @@ -1010,7 +1040,7 @@ public class MenuBuilder implements Menu { maxActions--; } } - + mActionItems.clear(); mNonActionItems.clear(); for (int i = 0; i < itemsSize; i++) { @@ -1021,17 +1051,17 @@ public class MenuBuilder implements Menu { mNonActionItems.add(item); } } - + mIsActionItemsStale = false; } - ArrayList<MenuItemImpl> getActionItems() { - flagActionItems(); + ArrayList<MenuItemImpl> getActionItems(boolean reserveActionOverflow) { + flagActionItems(reserveActionOverflow); return mActionItems; } - ArrayList<MenuItemImpl> getNonActionItems() { - flagActionItems(); + ArrayList<MenuItemImpl> getNonActionItems(boolean reserveActionOverflow) { + flagActionItems(reserveActionOverflow); return mNonActionItems; } @@ -1180,6 +1210,16 @@ public class MenuBuilder implements Menu { return new MenuAdapter(menuType); } + /** + * Gets an adapter for providing overflow (non-action) items and their views. + * + * @param menuType The type of menu to get an adapter for. + * @return A {@link MenuAdapter} for this menu with the given menu type. + */ + public MenuAdapter getOverflowMenuAdapter(int menuType) { + return new OverflowMenuAdapter(menuType); + } + void setOptionalIconsVisible(boolean visible) { mOptionalIconsVisible = visible; } @@ -1271,6 +1311,28 @@ public class MenuBuilder implements Menu { return item.getItemView(mMenuType, parent); } } + } + + /** + * An adapter that allows an {@link AdapterView} to use this {@link MenuBuilder} as a data + * source for overflow menu items that do not fit in the list of action items. + */ + private class OverflowMenuAdapter extends MenuAdapter { + private ArrayList<MenuItemImpl> mOverflowItems; + + public OverflowMenuAdapter(int menuType) { + super(menuType); + mOverflowItems = getNonActionItems(true); + } + @Override + public MenuItemImpl getItem(int position) { + return mOverflowItems.get(position); + } + + @Override + public int getCount() { + return mOverflowItems.size(); + } } } diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java index 751ecdae48bc..9913c0099406 100644 --- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java +++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java @@ -20,28 +20,48 @@ import com.android.internal.view.menu.MenuBuilder.MenuAdapter; import android.content.Context; import android.util.DisplayMetrics; +import android.view.KeyEvent; +import android.view.MenuItem; import android.view.View; import android.view.View.MeasureSpec; import android.widget.AdapterView; import android.widget.ListPopupWindow; +import java.lang.ref.WeakReference; + /** * @hide */ -public class MenuPopupHelper implements AdapterView.OnItemClickListener { +public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.OnKeyListener { private static final String TAG = "MenuPopupHelper"; private Context mContext; private ListPopupWindow mPopup; - private SubMenuBuilder mSubMenu; + private MenuBuilder mMenu; private int mPopupMaxWidth; + private WeakReference<View> mAnchorView; + private boolean mOverflowOnly; + + public MenuPopupHelper(Context context, MenuBuilder menu) { + this(context, menu, null, false); + } - public MenuPopupHelper(Context context, SubMenuBuilder subMenu) { + public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView) { + this(context, menu, anchorView, false); + } + + public MenuPopupHelper(Context context, MenuBuilder menu, + View anchorView, boolean overflowOnly) { mContext = context; - mSubMenu = subMenu; + mMenu = menu; + mOverflowOnly = overflowOnly; final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); mPopupMaxWidth = metrics.widthPixels / 2; + + if (anchorView != null) { + mAnchorView = new WeakReference<View>(anchorView); + } } public void show() { @@ -50,16 +70,23 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener { com.android.internal.R.style.Widget_Spinner); mPopup.setOnItemClickListener(this); - final MenuAdapter adapter = mSubMenu.getMenuAdapter(MenuBuilder.TYPE_POPUP); + final MenuAdapter adapter = mOverflowOnly ? + mMenu.getOverflowMenuAdapter(MenuBuilder.TYPE_POPUP) : + mMenu.getMenuAdapter(MenuBuilder.TYPE_POPUP); mPopup.setAdapter(adapter); mPopup.setModal(true); - final MenuItemImpl itemImpl = (MenuItemImpl) mSubMenu.getItem(); - final View anchorView = itemImpl.getItemView(MenuBuilder.TYPE_ACTION_BUTTON, null); - mPopup.setAnchorView(anchorView); + if (mMenu instanceof SubMenuBuilder) { + SubMenuBuilder subMenu = (SubMenuBuilder) mMenu; + final MenuItemImpl itemImpl = (MenuItemImpl) subMenu.getItem(); + mPopup.setAnchorView(itemImpl.getItemView(MenuBuilder.TYPE_ACTION_BUTTON, null)); + } else if (mAnchorView != null) { + mPopup.setAnchorView(mAnchorView.get()); + } mPopup.setContentWidth(Math.min(measureContentWidth(adapter), mPopupMaxWidth)); mPopup.show(); + mPopup.getListView().setOnKeyListener(this); } public void dismiss() { @@ -67,11 +94,29 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener { mPopup = null; } + public boolean isShowing() { + return mPopup != null && mPopup.isShowing(); + } + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - mSubMenu.performItemAction(mSubMenu.getItem(position), 0); + MenuItem item = null; + if (mOverflowOnly) { + item = mMenu.getOverflowItem(position); + } else { + item = mMenu.getItem(position); + } + mMenu.performItemAction(item, 0); mPopup.dismiss(); } + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) { + dismiss(); + return true; + } + return false; + } + private int measureContentWidth(MenuAdapter adapter) { // Menus don't tend to be long, so this is more sane than it looks. int width = 0; diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index fbff8ae4ae5e..8f8b3afe62f6 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -202,6 +202,17 @@ public class ActionBarView extends ViewGroup { mMenuView = menuView; } + public boolean showOverflowMenu() { + if (mMenuView != null) { + return mMenuView.showOverflowMenu(); + } + return false; + } + + public boolean isOverflowReserved() { + return mMenuView != null && mMenuView.isOverflowReserved(); + } + public void setCustomNavigationView(View view) { mCustomNavView = view; if (view != null) { diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java index 070d1e8fc3e1..cc91f319efb8 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java @@ -1381,8 +1381,12 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } case KeyEvent.KEYCODE_MENU: { - onKeyUpPanel(featureId < 0 ? FEATURE_OPTIONS_PANEL : featureId, - event); + if (mActionBar != null && mActionBar.isOverflowReserved()) { + mActionBar.showOverflowMenu(); + } else { + onKeyUpPanel(featureId < 0 ? FEATURE_OPTIONS_PANEL : featureId, + event); + } return true; } |