summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/widget/ActionMenuPresenter.java46
-rw-r--r--core/java/android/widget/DropDownListView.java57
-rw-r--r--core/java/android/widget/ListPopupWindow.java3
-rw-r--r--core/java/android/widget/MenuPopupWindow.java95
-rw-r--r--core/java/android/widget/PopupMenu.java11
-rw-r--r--core/java/com/android/internal/view/menu/CascadingMenuPopup.java394
-rw-r--r--core/java/com/android/internal/view/menu/MenuBuilder.java7
-rw-r--r--core/java/com/android/internal/view/menu/MenuPopup.java8
-rw-r--r--core/java/com/android/internal/view/menu/MenuPopupHelper.java66
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) {
- }
}