diff options
9 files changed, 555 insertions, 132 deletions
diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java index 64c41039bea5..2dd84e3fbcdc 100644 --- a/core/java/android/widget/ActionMenuPresenter.java +++ b/core/java/android/widget/ActionMenuPresenter.java @@ -85,6 +85,8 @@ public class ActionMenuPresenter extends BaseMenuPresenter private OpenOverflowRunnable mPostedOpenRunnable; private ActionMenuPopupCallback mPopupCallback; + private final boolean mShowCascadingMenus; + final PopupPresenterCallback mPopupPresenterCallback = new PopupPresenterCallback(); int mOpenSubMenuId; @@ -126,6 +128,9 @@ public class ActionMenuPresenter extends BaseMenuPresenter public ActionMenuPresenter(Context context) { super(context, com.android.internal.R.layout.action_menu_layout, com.android.internal.R.layout.action_menu_item_layout); + + mShowCascadingMenus = context.getResources().getBoolean( + com.android.internal.R.bool.config_enableCascadingSubmenus); } @Override @@ -500,14 +505,29 @@ public class ActionMenuPresenter extends BaseMenuPresenter } View anchor = findViewForItem(topSubMenu.getItem()); if (anchor == null) { - if (mOverflowButton == null) return false; - anchor = mOverflowButton; + // This means the submenu was opened from an overflow menu item, indicating the + // MenuPopupHelper will handle opening the submenu via its MenuPopup. Return false to + // ensure that the MenuPopup acts as presenter for the submenu, and acts on its + // responsibility to display the new submenu. + return false; } mOpenSubMenuId = subMenu.getItem().getItemId(); - mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu); - mActionButtonPopup.setAnchorView(anchor); + + boolean preserveIconSpacing = false; + final int count = subMenu.size(); + for (int i = 0; i < count; i++) { + MenuItem childItem = subMenu.getItem(i); + if (childItem.isVisible() && childItem.getIcon() != null) { + preserveIconSpacing = true; + break; + } + } + + mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu, anchor); + mActionButtonPopup.setForceShowIcon(preserveIconSpacing); mActionButtonPopup.show(); + super.onSubMenuSelected(subMenu); return true; } @@ -926,12 +946,9 @@ public class ActionMenuPresenter extends BaseMenuPresenter } private class ActionButtonSubmenu extends MenuPopupHelper { - private SubMenuBuilder mSubMenu; - - public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu) { - super(context, subMenu, null, false, + public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu, View anchorView) { + super(context, subMenu, anchorView, false, com.android.internal.R.attr.actionOverflowMenuStyle); - mSubMenu = subMenu; MenuItemImpl item = (MenuItemImpl) subMenu.getItem(); if (!item.isActionButton()) { @@ -940,17 +957,6 @@ public class ActionMenuPresenter extends BaseMenuPresenter } setCallback(mPopupPresenterCallback); - - boolean preserveIconSpacing = false; - final int count = subMenu.size(); - for (int i = 0; i < count; i++) { - MenuItem childItem = subMenu.getItem(i); - if (childItem.isVisible() && childItem.getIcon() != null) { - preserveIconSpacing = true; - break; - } - } - setForceShowIcon(preserveIconSpacing); } @Override diff --git a/core/java/android/widget/DropDownListView.java b/core/java/android/widget/DropDownListView.java index 553651308e9c..bcbafc9af158 100644 --- a/core/java/android/widget/DropDownListView.java +++ b/core/java/android/widget/DropDownListView.java @@ -25,10 +25,8 @@ import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.drawable.Drawable; import android.util.IntProperty; -import android.util.Log; import android.view.MotionEvent; import android.view.View; -import android.view.accessibility.AccessibilityManager; import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.TextView; import android.widget.ListView; @@ -128,6 +126,61 @@ public class DropDownListView extends ListView { setCacheColorHint(0); // Transparent, since the background drawable could be anything. } + @Override + protected boolean shouldShowSelector() { + View selectedView = getSelectedView(); + return selectedView != null && selectedView.isEnabled() || super.shouldShowSelector(); + } + + protected void clearSelection() { + setSelectedPositionInt(-1); + setNextSelectedPositionInt(-1); + } + + @Override + public boolean onHoverEvent(MotionEvent ev) { + final int action = ev.getActionMasked(); + if (action == MotionEvent.ACTION_HOVER_ENTER + || action == MotionEvent.ACTION_HOVER_MOVE) { + final int position = pointToPosition((int) ev.getX(), (int) ev.getY()); + if (position != INVALID_POSITION && position != mSelectedPosition) { + final View hoveredItem = getChildAt(position - getFirstVisiblePosition()); + if (hoveredItem.isEnabled()) { + // Force a focus so that the proper selector state gets used when we update. + requestFocus(); + + positionSelector(position, hoveredItem); + setSelectedPositionInt(position); + setNextSelectedPositionInt(position); + } + updateSelectorState(); + } + } else { + // Do not cancel the selected position if the selection is visible by other reasons. + if (!super.shouldShowSelector()) { + setSelectedPositionInt(INVALID_POSITION); + } + } + return super.onHoverEvent(ev); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + final int x = (int) event.getX(); + final int y = (int) event.getY(); + final int position = pointToPosition(x, y); + if (position == INVALID_POSITION) { + return super.onTouchEvent(event); + } + + if (position != mSelectedPosition) { + setSelectedPositionInt(position); + setNextSelectedPositionInt(position); + } + + return super.onTouchEvent(event); + } + /** * Handles forwarded events. * diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java index 3d07d87dfe70..b95bc283d11a 100644 --- a/core/java/android/widget/ListPopupWindow.java +++ b/core/java/android/widget/ListPopupWindow.java @@ -69,7 +69,6 @@ public class ListPopupWindow implements ShowableListMenu { private static final int EXPAND_LIST_TIMEOUT = 250; private Context mContext; - private PopupWindow mPopup; private ListAdapter mAdapter; private DropDownListView mDropDownList; @@ -112,6 +111,8 @@ public class ListPopupWindow implements ShowableListMenu { private int mLayoutDirection; + PopupWindow mPopup; + /** * The provided prompt view should appear above list content. * diff --git a/core/java/android/widget/MenuPopupWindow.java b/core/java/android/widget/MenuPopupWindow.java index 9e47e8513d49..900aa326d502 100644 --- a/core/java/android/widget/MenuPopupWindow.java +++ b/core/java/android/widget/MenuPopupWindow.java @@ -17,10 +17,17 @@ package android.widget; import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.transition.Transition; import android.util.AttributeSet; -import android.view.MotionEvent; +import android.view.KeyEvent; import android.view.View; -import android.view.accessibility.AccessibilityManager; +import android.view.ViewGroup; +import android.view.ViewParent; + +import com.android.internal.view.menu.ListMenuItemView; +import com.android.internal.view.menu.MenuAdapter; /** * A MenuPopupWindow represents the popup window for menu. @@ -40,52 +47,58 @@ public class MenuPopupWindow extends ListPopupWindow { return new MenuDropDownListView(context, hijackFocus); } - static class MenuDropDownListView extends DropDownListView { - private boolean mHoveredOnDisabledItem = false; - private AccessibilityManager mAccessibilityManager; + public void setEnterTransition(Transition enterTransition) { + mPopup.setEnterTransition(enterTransition); + } - MenuDropDownListView(Context context, boolean hijackFocus) { - super(context, hijackFocus); - mAccessibilityManager = (AccessibilityManager) getContext().getSystemService( - Context.ACCESSIBILITY_SERVICE); - } + /** + * Set whether this window is touch modal or if outside touches will be sent to + * other windows behind it. + */ + public void setTouchModal(boolean touchModal) { + mPopup.setTouchModal(touchModal); + } - @Override - protected boolean shouldShowSelector() { - return (isHovered() && !mHoveredOnDisabledItem) || super.shouldShowSelector(); - } + private static class MenuDropDownListView extends DropDownListView { + final int mAdvanceKey; + final int mRetreatKey; - @Override - public boolean onHoverEvent(MotionEvent ev) { - mHoveredOnDisabledItem = false; + public MenuDropDownListView(Context context, boolean hijackFocus) { + super(context, hijackFocus); - // Accessibility system should already handle hover events and selections, menu does - // not have to handle it by itself. - if (mAccessibilityManager.isTouchExplorationEnabled()) { - return super.onHoverEvent(ev); + final Resources res = context.getResources(); + final Configuration config = res.getConfiguration(); + if (config.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { + mAdvanceKey = KeyEvent.KEYCODE_DPAD_LEFT; + mRetreatKey = KeyEvent.KEYCODE_DPAD_RIGHT; + } else { + mAdvanceKey = KeyEvent.KEYCODE_DPAD_RIGHT; + mRetreatKey = KeyEvent.KEYCODE_DPAD_LEFT; } + } - final int action = ev.getActionMasked(); - if (action == MotionEvent.ACTION_HOVER_ENTER - || action == MotionEvent.ACTION_HOVER_MOVE) { - final int position = pointToPosition((int) ev.getX(), (int) ev.getY()); - if (position != INVALID_POSITION && position != mSelectedPosition) { - final View hoveredItem = getChildAt(position - getFirstVisiblePosition()); - if (hoveredItem.isEnabled()) { - positionSelector(position, hoveredItem); - setSelectedPositionInt(position); - } else { - mHoveredOnDisabledItem = true; - } - updateSelectorState(); - } - } else { - // Do not cancel the selected position if the selection is visible by other reasons. - if (!super.shouldShowSelector()) { - setSelectedPositionInt(INVALID_POSITION); + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + ListMenuItemView selectedItem = (ListMenuItemView) getSelectedView(); + if (selectedItem != null && keyCode == mAdvanceKey) { + if (selectedItem.isEnabled() && + ((ListMenuItemView) selectedItem).getItemData().hasSubMenu()) { + performItemClick( + selectedItem, + getSelectedItemPosition(), + getSelectedItemId()); } + return true; + } else if (selectedItem != null && keyCode == mRetreatKey) { + setSelectedPositionInt(-1); + setNextSelectedPositionInt(-1); + + ((MenuAdapter) getAdapter()).getAdapterMenu().close(); + return true; } - return super.onHoverEvent(ev); + return super.onKeyDown(keyCode, event); } + } -} + +}
\ No newline at end of file diff --git a/core/java/android/widget/PopupMenu.java b/core/java/android/widget/PopupMenu.java index 3b2d60d1e1a4..bd1fbb8f29c5 100644 --- a/core/java/android/widget/PopupMenu.java +++ b/core/java/android/widget/PopupMenu.java @@ -43,6 +43,7 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback { private final MenuBuilder mMenu; private final View mAnchor; private final MenuPopupHelper mPopup; + private final boolean mShowCascadingMenus; private OnMenuItemClickListener mMenuItemClickListener; private OnDismissListener mDismissListener; @@ -107,6 +108,8 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback { public PopupMenu(Context context, View anchor, int gravity, int popupStyleAttr, int popupStyleRes) { mContext = context; + mShowCascadingMenus = context.getResources().getBoolean( + com.android.internal.R.bool.config_enableCascadingSubmenus); mMenu = new MenuBuilder(context); mMenu.setCallback(this); mAnchor = anchor; @@ -273,8 +276,12 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback { return true; } - // Current menu will be dismissed by the normal helper, submenu will be shown in its place. - new MenuPopupHelper(mContext, subMenu, mAnchor).show(); + if (!mShowCascadingMenus) { + // Current menu will be dismissed by the normal helper, submenu will be shown in its + // place. (If cascading menus are enabled, the cascading implementation will show the + // submenu itself). + new MenuPopupHelper(mContext, subMenu, mAnchor).show(); + } return true; } diff --git a/core/java/com/android/internal/view/menu/CascadingMenuPopup.java b/core/java/com/android/internal/view/menu/CascadingMenuPopup.java new file mode 100644 index 000000000000..4c829a240e70 --- /dev/null +++ b/core/java/com/android/internal/view/menu/CascadingMenuPopup.java @@ -0,0 +1,394 @@ +package com.android.internal.view.menu; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; + +import android.annotation.IntDef; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Rect; +import android.os.Parcelable; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnKeyListener; +import android.widget.AdapterView; +import android.widget.DropDownListView; +import android.widget.ListView; +import android.widget.MenuPopupWindow; +import android.widget.PopupWindow; +import android.widget.PopupWindow.OnDismissListener; + +import com.android.internal.util.Preconditions; + +/** + * A popup for a menu which will allow multiple submenus to appear in a cascading fashion, side by + * side. + * @hide + */ +final class CascadingMenuPopup extends MenuPopup implements AdapterView.OnItemClickListener, + MenuPresenter, OnKeyListener, PopupWindow.OnDismissListener { + @Retention(RetentionPolicy.SOURCE) + @IntDef({HORIZ_POSITION_LEFT, HORIZ_POSITION_RIGHT}) + public @interface HorizPosition {} + + private static final int HORIZ_POSITION_LEFT = 0; + private static final int HORIZ_POSITION_RIGHT = 1; + + private final Context mContext; + private final int mMenuMaxWidth; + private final int mPopupStyleAttr; + private final int mPopupStyleRes; + private final boolean mOverflowOnly; + private final int mLayoutDirection; + + private int mDropDownGravity = Gravity.NO_GRAVITY; + private View mAnchor; + private List<DropDownListView> mListViews; + private List<MenuPopupWindow> mPopupWindows; + private List<int[]> mOffsets; + private int mPreferredPosition; + private boolean mForceShowIcon; + private Callback mPresenterCallback; + private PopupWindow.OnDismissListener mOnDismissListener; + + /** + * Initializes a new cascading-capable menu popup. + * + * @param parent A parent view to get the {@link android.view.View#getWindowToken()} token from. + */ + public CascadingMenuPopup(Context context, View anchor, int popupStyleAttr, + int popupStyleRes, boolean overflowOnly) { + mContext = Preconditions.checkNotNull(context); + mAnchor = Preconditions.checkNotNull(anchor); + mPopupStyleAttr = popupStyleAttr; + mPopupStyleRes = popupStyleRes; + mOverflowOnly = overflowOnly; + + mForceShowIcon = false; + + final Resources res = context.getResources(); + final Configuration config = res.getConfiguration(); + mLayoutDirection = config.getLayoutDirection(); + mPreferredPosition = mLayoutDirection == View.LAYOUT_DIRECTION_RTL ? HORIZ_POSITION_LEFT : + HORIZ_POSITION_RIGHT; + mMenuMaxWidth = Math.max(res.getDisplayMetrics().widthPixels / 2, + res.getDimensionPixelSize(com.android.internal.R.dimen.config_prefDialogWidth)); + + mPopupWindows = new ArrayList<MenuPopupWindow>(); + mListViews = new ArrayList<DropDownListView>(); + mOffsets = new ArrayList<int[]>(); + } + + @Override + public void setForceShowIcon(boolean forceShow) { + mForceShowIcon = forceShow; + } + + private MenuPopupWindow createPopupWindow() { + MenuPopupWindow popupWindow = new MenuPopupWindow( + mContext, null, mPopupStyleAttr, mPopupStyleRes); + popupWindow.setOnItemClickListener(this); + popupWindow.setOnDismissListener(this); + popupWindow.setAnchorView(mAnchor); + popupWindow.setDropDownGravity(mDropDownGravity); + popupWindow.setModal(true); + popupWindow.setTouchModal(false); + return popupWindow; + } + + @Override + public void show() { + if (isShowing()) { + return; + } + + // Show any menus that have been added via #addMenu(MenuBuilder) but which have not yet been + // shown. + // In a typical use case, #addMenu(MenuBuilder) would be called once, followed by a call to + // this #show() method -- which would actually show the popup on the screen. + for (int i = 0; i < mPopupWindows.size(); i++) { + MenuPopupWindow popupWindow = mPopupWindows.get(i); + popupWindow.show(); + mListViews.add((DropDownListView) popupWindow.getListView()); + } + } + + @Override + public void dismiss() { + // Need to make another list to avoid a concurrent modification exception, as #onDismiss + // may clear mPopupWindows while we are iterating. + List<MenuPopupWindow> popupWindows = new ArrayList<MenuPopupWindow>(mPopupWindows); + for (MenuPopupWindow popupWindow : popupWindows) { + if (popupWindow != null && popupWindow.isShowing()) { + popupWindow.dismiss(); + } + } + } + + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + MenuAdapter adapter = (MenuAdapter) parent.getAdapter(); + adapter.mAdapterMenu.performItemAction(adapter.getItem(position), 0); + } + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) { + dismiss(); + return true; + } + return false; + } + + /** + * Determines whether the next submenu (of the given width) should display on the right or on + * the left of the most recent menu. + * + * @param nextMenuWidth Width of the next submenu to display. + * @return The position to display it. + */ + @HorizPosition + private int getNextMenuPosition(int nextMenuWidth) { + ListView lastListView = mListViews.get(mListViews.size() - 1); + + final int[] screenLocation = new int[2]; + lastListView.getLocationOnScreen(screenLocation); + + final Rect displayFrame = new Rect(); + mAnchor.getWindowVisibleDisplayFrame(displayFrame); + + if (mPreferredPosition == HORIZ_POSITION_RIGHT) { + final int right = screenLocation[0] + lastListView.getWidth() + nextMenuWidth; + if (right > displayFrame.right) { + return HORIZ_POSITION_LEFT; + } + return HORIZ_POSITION_RIGHT; + } else { // LEFT + final int left = screenLocation[0] - nextMenuWidth; + if (left < 0) { + return HORIZ_POSITION_RIGHT; + } + return HORIZ_POSITION_LEFT; + } + } + + @Override + public void addMenu(MenuBuilder menu) { + boolean addSubMenu = mListViews.size() > 0; + + menu.addMenuPresenter(this, mContext); + + MenuPopupWindow popupWindow = createPopupWindow(); + + MenuAdapter adapter = new MenuAdapter(menu, LayoutInflater.from(mContext), mOverflowOnly); + adapter.setForceShowIcon(mForceShowIcon); + + popupWindow.setAdapter(adapter); + + int menuWidth = measureIndividualMenuWidth(adapter, null, mContext, mMenuMaxWidth); + + int x = 0; + int y = 0; + + if (addSubMenu) { + popupWindow.setEnterTransition(null); + + ListView lastListView = mListViews.get(mListViews.size() - 1); + @HorizPosition int nextMenuPosition = getNextMenuPosition(menuWidth); + boolean showOnRight = nextMenuPosition == HORIZ_POSITION_RIGHT; + mPreferredPosition = nextMenuPosition; + + int[] lastLocation = new int[2]; + lastListView.getLocationOnScreen(lastLocation); + + int[] lastOffset = mOffsets.get(mOffsets.size() - 1); + + // Note: By now, mDropDownGravity is the absolute gravity, so this should work in both + // LTR and RTL. + if ((mDropDownGravity & Gravity.RIGHT) == Gravity.RIGHT) { + if (showOnRight) { + x = lastOffset[0] + menuWidth; + } else { + x = lastOffset[0] - lastListView.getWidth(); + } + } else { + if (showOnRight) { + x = lastOffset[0] + lastListView.getWidth(); + } else { + x = lastOffset[0] - menuWidth; + } + } + + y = lastOffset[1] + lastListView.getSelectedView().getTop() - + lastListView.getChildAt(0).getTop(); + } + + popupWindow.setWidth(menuWidth); + popupWindow.setHorizontalOffset(x); + popupWindow.setVerticalOffset(y); + mPopupWindows.add(popupWindow); + + // NOTE: This case handles showing submenus once the CascadingMenuPopup has already + // been shown via a call to its #show() method. If it hasn't yet been show()n, then + // we deliberately do not yet show the popupWindow, as #show() will do that later. + if (isShowing()) { + popupWindow.show(); + mListViews.add((DropDownListView) popupWindow.getListView()); + } + + int[] offsets = {x, y}; + mOffsets.add(offsets); + } + + /** + * @return {@code true} if the popup is currently showing, {@code false} otherwise. + */ + @Override + public boolean isShowing() { + return mPopupWindows.size() > 0 && mPopupWindows.get(0).isShowing(); + } + + /** + * Called when one or more of the popup windows was dismissed. + */ + @Override + public void onDismiss() { + int dismissedIndex = -1; + for (int i = 0; i < mPopupWindows.size(); i++) { + if (!mPopupWindows.get(i).isShowing()) { + dismissedIndex = i; + break; + } + } + + if (dismissedIndex != -1) { + for (int i = dismissedIndex; i < mListViews.size(); i++) { + ListView view = mListViews.get(i); + MenuAdapter adapter = (MenuAdapter) view.getAdapter(); + adapter.mAdapterMenu.close(); + } + } + } + + @Override + public void updateMenuView(boolean cleared) { + for (ListView view : mListViews) { + ((MenuAdapter) view.getAdapter()).notifyDataSetChanged(); + } + } + + @Override + public void setCallback(Callback cb) { + mPresenterCallback = cb; + } + + @Override + public boolean onSubMenuSelected(SubMenuBuilder subMenu) { + // Don't allow double-opening of the same submenu. + for (ListView view : mListViews) { + if (((MenuAdapter) view.getAdapter()).mAdapterMenu.equals(subMenu)) { + // Just re-focus that one. + view.requestFocus(); + return true; + } + } + + if (subMenu.hasVisibleItems()) { + this.addMenu(subMenu); + if (mPresenterCallback != null) { + mPresenterCallback.onOpenSubMenu(subMenu); + } + return true; + } + return false; + } + + @Override + public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { + int menuIndex = -1; + boolean wasSelected = false; + + for (int i = 0; i < mListViews.size(); i++) { + ListView view = mListViews.get(i); + MenuAdapter adapter = (MenuAdapter) view.getAdapter(); + + if (menuIndex == -1 && menu == adapter.mAdapterMenu) { + menuIndex = i; + wasSelected = view.getSelectedItem() != null; + } + + // Once the menu has been found, remove it and all submenus beneath it from the + // container view. Also remove the presenter. + if (menuIndex != -1) { + adapter.mAdapterMenu.removeMenuPresenter(this); + } + } + + // Then, actually remove the views for these [sub]menu(s) from our list of views. + if (menuIndex != -1) { + for (int i = menuIndex; i < mPopupWindows.size(); i++) { + mPopupWindows.get(i).dismiss(); + } + mPopupWindows.subList(menuIndex, mPopupWindows.size()).clear(); + mListViews.subList(menuIndex, mListViews.size()).clear(); + mOffsets.subList(menuIndex, mOffsets.size()).clear(); + + // If there's still a menu open, refocus the new leaf [sub]menu. + if (mListViews.size() > 0) { + mListViews.get(mListViews.size() - 1).requestFocus(); + } + } + + if (mListViews.size() == 0 || wasSelected) { + dismiss(); + if (mPresenterCallback != null) { + mPresenterCallback.onCloseMenu(menu, allMenusAreClosing); + } + } + + if (mPopupWindows.size() == 0) { + // If every [sub]menu was dismissed, that means the whole thing was dismissed, so notify + // the owner. + mOnDismissListener.onDismiss(); + } + } + + @Override + public boolean flagActionItems() { + return false; + } + + @Override + public Parcelable onSaveInstanceState() { + return null; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + } + + @Override + public void setGravity(int dropDownGravity) { + mDropDownGravity = Gravity.getAbsoluteGravity(dropDownGravity, mLayoutDirection); + } + + @Override + public void setAnchorView(View anchor) { + mAnchor = anchor; + } + + @Override + public void setOnDismissListener(OnDismissListener listener) { + mOnDismissListener = listener; + } + + @Override + public ListView getListView() { + return mListViews.size() > 0 ? mListViews.get(mListViews.size() - 1) : null; + } +}
\ No newline at end of file diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java index e8d1ead7d651..167392874173 100644 --- a/core/java/com/android/internal/view/menu/MenuBuilder.java +++ b/core/java/com/android/internal/view/menu/MenuBuilder.java @@ -64,6 +64,7 @@ public class MenuBuilder implements Menu { private final Context mContext; private final Resources mResources; + private final boolean mShowCascadingMenus; /** * Whether the shortcuts should be qwerty-accessible. Use isQwertyMode() @@ -186,6 +187,8 @@ public class MenuBuilder implements Menu { public MenuBuilder(Context context) { mContext = context; mResources = context.getResources(); + mShowCascadingMenus = context.getResources().getBoolean( + com.android.internal.R.bool.config_enableCascadingSubmenus); mItems = new ArrayList<MenuItemImpl>(); @@ -909,7 +912,9 @@ public class MenuBuilder implements Menu { invoked |= itemImpl.expandActionView(); if (invoked) close(true); } else if (itemImpl.hasSubMenu() || providerHasSubMenu) { - close(false); + if (!mShowCascadingMenus) { + close(false); + } if (!itemImpl.hasSubMenu()) { itemImpl.setSubMenu(new SubMenuBuilder(getContext(), this, itemImpl)); diff --git a/core/java/com/android/internal/view/menu/MenuPopup.java b/core/java/com/android/internal/view/menu/MenuPopup.java index 91788cab49fa..b43e8adbc8ac 100644 --- a/core/java/com/android/internal/view/menu/MenuPopup.java +++ b/core/java/com/android/internal/view/menu/MenuPopup.java @@ -35,9 +35,11 @@ public abstract class MenuPopup implements ShowableListMenu, MenuPresenter { public abstract void setForceShowIcon(boolean forceShow); /** - * Adds the given menu to the popup. If this is the first menu shown it'll be displayed; if it's - * a submenu it will be displayed adjacent to the most recent menu (if supported by the - * implementation). + * Adds the given menu to the popup, if it is capable of displaying submenus within itself. + * If menu is the first menu shown, it won't be displayed until show() is called. + * If the popup was already showing, adding a submenu via this method will cause that new + * submenu to be shown immediately (that is, if this MenuPopup implementation is capable of + * showing its own submenus). * * @param menu */ diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java index 9a47fc1834bb..e0d7feefb1b0 100644 --- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java +++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java @@ -17,10 +17,8 @@ package com.android.internal.view.menu; import android.content.Context; -import android.os.Parcelable; import android.view.Gravity; import android.view.View; -import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.PopupWindow; @@ -30,7 +28,7 @@ import android.widget.PopupWindow; * @hide */ public class MenuPopupHelper implements ViewTreeObserver.OnGlobalLayoutListener, - PopupWindow.OnDismissListener, View.OnAttachStateChangeListener, MenuPresenter { + PopupWindow.OnDismissListener, View.OnAttachStateChangeListener { private final Context mContext; private final MenuBuilder mMenu; private final boolean mOverflowOnly; @@ -70,9 +68,8 @@ public class MenuPopupHelper implements ViewTreeObserver.OnGlobalLayoutListener, private MenuPopup createMenuPopup() { if (mContext.getResources().getBoolean( com.android.internal.R.bool.config_enableCascadingSubmenus)) { - // TODO: Return a Cascading implementation of MenuPopup instead. - return new StandardMenuPopup( - mContext, mMenu, mAnchorView, mPopupStyleAttr, mPopupStyleRes, mOverflowOnly); + return new CascadingMenuPopup(mContext, mAnchorView, mPopupStyleAttr, mPopupStyleRes, + mOverflowOnly); } return new StandardMenuPopup( mContext, mMenu, mAnchorView, mPopupStyleAttr, mPopupStyleRes, mOverflowOnly); @@ -187,62 +184,7 @@ public class MenuPopupHelper implements ViewTreeObserver.OnGlobalLayoutListener, v.removeOnAttachStateChangeListener(this); } - @Override - public void initForMenu(Context context, MenuBuilder menu) { - // Don't need to do anything; we added as a presenter in the constructor. - } - - @Override - public MenuView getMenuView(ViewGroup root) { - throw new UnsupportedOperationException("MenuPopupHelpers manage their own views"); - } - - @Override - public void updateMenuView(boolean cleared) { - mPopup.updateMenuView(cleared); - } - - @Override - public void setCallback(Callback cb) { + public void setCallback(MenuPresenter.Callback cb) { mPopup.setCallback(cb); } - - @Override - public boolean onSubMenuSelected(SubMenuBuilder subMenu) { - return mPopup.onSubMenuSelected(subMenu); - } - - @Override - public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { - mPopup.onCloseMenu(menu, allMenusAreClosing); - } - - @Override - public boolean flagActionItems() { - return false; - } - - @Override - public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) { - return false; - } - - @Override - public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) { - return false; - } - - @Override - public int getId() { - return 0; - } - - @Override - public Parcelable onSaveInstanceState() { - return null; - } - - @Override - public void onRestoreInstanceState(Parcelable state) { - } } |