1package com.android.internal.view.menu; 2 3import java.lang.annotation.Retention; 4import java.lang.annotation.RetentionPolicy; 5import java.util.ArrayList; 6import java.util.LinkedList; 7import java.util.List; 8 9import android.annotation.AttrRes; 10import android.annotation.IntDef; 11import android.annotation.NonNull; 12import android.annotation.Nullable; 13import android.annotation.StyleRes; 14import android.content.Context; 15import android.content.res.Resources; 16import android.graphics.Rect; 17import android.os.Handler; 18import android.os.Parcelable; 19import android.os.SystemClock; 20import android.view.Gravity; 21import android.view.KeyEvent; 22import android.view.LayoutInflater; 23import android.view.MenuItem; 24import android.view.View; 25import android.view.ViewTreeObserver; 26import android.view.View.OnAttachStateChangeListener; 27import android.view.View.OnKeyListener; 28import android.view.ViewTreeObserver.OnGlobalLayoutListener; 29import android.widget.AbsListView; 30import android.widget.FrameLayout; 31import android.widget.HeaderViewListAdapter; 32import android.widget.ListAdapter; 33import android.widget.MenuItemHoverListener; 34import android.widget.ListView; 35import android.widget.MenuPopupWindow; 36import android.widget.PopupWindow; 37import android.widget.PopupWindow.OnDismissListener; 38import android.widget.TextView; 39 40import com.android.internal.R; 41import com.android.internal.util.Preconditions; 42 43/** 44 * A popup for a menu which will allow multiple submenus to appear in a cascading fashion, side by 45 * side. 46 * @hide 47 */ 48final class CascadingMenuPopup extends MenuPopup implements MenuPresenter, OnKeyListener, 49 PopupWindow.OnDismissListener { 50 51 @Retention(RetentionPolicy.SOURCE) 52 @IntDef({HORIZ_POSITION_LEFT, HORIZ_POSITION_RIGHT}) 53 public @interface HorizPosition {} 54 55 private static final int HORIZ_POSITION_LEFT = 0; 56 private static final int HORIZ_POSITION_RIGHT = 1; 57 58 /** 59 * Delay between hovering over a menu item with a mouse and receiving 60 * side-effects (ex. opening a sub-menu or closing unrelated menus). 61 */ 62 private static final int SUBMENU_TIMEOUT_MS = 200; 63 64 private final Context mContext; 65 private final int mMenuMaxWidth; 66 private final int mPopupStyleAttr; 67 private final int mPopupStyleRes; 68 private final boolean mOverflowOnly; 69 private final Handler mSubMenuHoverHandler; 70 71 /** List of menus that were added before this popup was shown. */ 72 private final List<MenuBuilder> mPendingMenus = new LinkedList<>(); 73 74 /** 75 * List of open menus. The first item is the root menu and each 76 * subsequent item is a direct submenu of the previous item. 77 */ 78 private final List<CascadingMenuInfo> mShowingMenus = new ArrayList<>(); 79 80 private final OnGlobalLayoutListener mGlobalLayoutListener = new OnGlobalLayoutListener() { 81 @Override 82 public void onGlobalLayout() { 83 // Only move the popup if it's showing and non-modal. We don't want 84 // to be moving around the only interactive window, since there's a 85 // good chance the user is interacting with it. 86 if (isShowing() && mShowingMenus.size() > 0 87 && !mShowingMenus.get(0).window.isModal()) { 88 final View anchor = mShownAnchorView; 89 if (anchor == null || !anchor.isShown()) { 90 dismiss(); 91 } else { 92 // Recompute window sizes and positions. 93 for (CascadingMenuInfo info : mShowingMenus) { 94 info.window.show(); 95 } 96 } 97 } 98 } 99 }; 100 101 private final OnAttachStateChangeListener mAttachStateChangeListener = 102 new OnAttachStateChangeListener() { 103 @Override 104 public void onViewAttachedToWindow(View v) { 105 } 106 107 @Override 108 public void onViewDetachedFromWindow(View v) { 109 if (mTreeObserver != null) { 110 if (!mTreeObserver.isAlive()) { 111 mTreeObserver = v.getViewTreeObserver(); 112 } 113 mTreeObserver.removeGlobalOnLayoutListener(mGlobalLayoutListener); 114 } 115 v.removeOnAttachStateChangeListener(this); 116 } 117 }; 118 119 private final MenuItemHoverListener mMenuItemHoverListener = new MenuItemHoverListener() { 120 @Override 121 public void onItemHoverExit(@NonNull MenuBuilder menu, @NonNull MenuItem item) { 122 // If the mouse moves between two windows, hover enter/exit pairs 123 // may be received out of order. So, instead of canceling all 124 // pending runnables, only cancel runnables for the host menu. 125 mSubMenuHoverHandler.removeCallbacksAndMessages(menu); 126 } 127 128 @Override 129 public void onItemHoverEnter( 130 @NonNull final MenuBuilder menu, @NonNull final MenuItem item) { 131 // Something new was hovered, cancel all scheduled runnables. 132 mSubMenuHoverHandler.removeCallbacksAndMessages(null); 133 134 // Find the position of the hovered menu within the added menus. 135 int menuIndex = -1; 136 for (int i = 0, count = mShowingMenus.size(); i < count; i++) { 137 if (menu == mShowingMenus.get(i).menu) { 138 menuIndex = i; 139 break; 140 } 141 } 142 143 if (menuIndex == -1) { 144 return; 145 } 146 147 final CascadingMenuInfo nextInfo; 148 final int nextIndex = menuIndex + 1; 149 if (nextIndex < mShowingMenus.size()) { 150 nextInfo = mShowingMenus.get(nextIndex); 151 } else { 152 nextInfo = null; 153 } 154 155 final Runnable runnable = new Runnable() { 156 @Override 157 public void run() { 158 // Close any other submenus that might be open at the 159 // current or a deeper level. 160 if (nextInfo != null) { 161 // Disable exit animations to prevent overlapping 162 // fading out submenus. 163 mShouldCloseImmediately = true; 164 nextInfo.menu.close(false /* closeAllMenus */); 165 mShouldCloseImmediately = false; 166 } 167 168 // Then open the selected submenu, if there is one. 169 if (item.isEnabled() && item.hasSubMenu()) { 170 menu.performItemAction(item, 0); 171 } 172 } 173 }; 174 final long uptimeMillis = SystemClock.uptimeMillis() + SUBMENU_TIMEOUT_MS; 175 mSubMenuHoverHandler.postAtTime(runnable, menu, uptimeMillis); 176 } 177 }; 178 179 private int mRawDropDownGravity = Gravity.NO_GRAVITY; 180 private int mDropDownGravity = Gravity.NO_GRAVITY; 181 private View mAnchorView; 182 private View mShownAnchorView; 183 private int mLastPosition; 184 private boolean mHasXOffset; 185 private boolean mHasYOffset; 186 private int mXOffset; 187 private int mYOffset; 188 private boolean mForceShowIcon; 189 private boolean mShowTitle; 190 private Callback mPresenterCallback; 191 private ViewTreeObserver mTreeObserver; 192 private PopupWindow.OnDismissListener mOnDismissListener; 193 194 /** Whether popup menus should disable exit animations when closing. */ 195 private boolean mShouldCloseImmediately; 196 197 /** 198 * Initializes a new cascading-capable menu popup. 199 * 200 * @param anchor A parent view to get the {@link android.view.View#getWindowToken()} token from. 201 */ 202 public CascadingMenuPopup(@NonNull Context context, @NonNull View anchor, 203 @AttrRes int popupStyleAttr, @StyleRes int popupStyleRes, boolean overflowOnly) { 204 mContext = Preconditions.checkNotNull(context); 205 mAnchorView = Preconditions.checkNotNull(anchor); 206 mPopupStyleAttr = popupStyleAttr; 207 mPopupStyleRes = popupStyleRes; 208 mOverflowOnly = overflowOnly; 209 210 mForceShowIcon = false; 211 mLastPosition = getInitialMenuPosition(); 212 213 final Resources res = context.getResources(); 214 mMenuMaxWidth = Math.max(res.getDisplayMetrics().widthPixels / 2, 215 res.getDimensionPixelSize(com.android.internal.R.dimen.config_prefDialogWidth)); 216 217 mSubMenuHoverHandler = new Handler(); 218 } 219 220 @Override 221 public void setForceShowIcon(boolean forceShow) { 222 mForceShowIcon = forceShow; 223 } 224 225 private MenuPopupWindow createPopupWindow() { 226 MenuPopupWindow popupWindow = new MenuPopupWindow( 227 mContext, null, mPopupStyleAttr, mPopupStyleRes); 228 popupWindow.setHoverListener(mMenuItemHoverListener); 229 popupWindow.setOnItemClickListener(this); 230 popupWindow.setOnDismissListener(this); 231 popupWindow.setAnchorView(mAnchorView); 232 popupWindow.setDropDownGravity(mDropDownGravity); 233 popupWindow.setModal(true); 234 popupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); 235 return popupWindow; 236 } 237 238 @Override 239 public void show() { 240 if (isShowing()) { 241 return; 242 } 243 244 // Display all pending menus. 245 for (MenuBuilder menu : mPendingMenus) { 246 showMenu(menu); 247 } 248 mPendingMenus.clear(); 249 250 mShownAnchorView = mAnchorView; 251 252 if (mShownAnchorView != null) { 253 final boolean addGlobalListener = mTreeObserver == null; 254 mTreeObserver = mShownAnchorView.getViewTreeObserver(); // Refresh to latest 255 if (addGlobalListener) { 256 mTreeObserver.addOnGlobalLayoutListener(mGlobalLayoutListener); 257 } 258 mShownAnchorView.addOnAttachStateChangeListener(mAttachStateChangeListener); 259 } 260 } 261 262 @Override 263 public void dismiss() { 264 // Need to make another list to avoid a concurrent modification 265 // exception, as #onDismiss may clear mPopupWindows while we are 266 // iterating. Remove from the last added menu so that the callbacks 267 // are received in order from foreground to background. 268 final int length = mShowingMenus.size(); 269 if (length > 0) { 270 final CascadingMenuInfo[] addedMenus = 271 mShowingMenus.toArray(new CascadingMenuInfo[length]); 272 for (int i = length - 1; i >= 0; i--) { 273 final CascadingMenuInfo info = addedMenus[i]; 274 if (info.window.isShowing()) { 275 info.window.dismiss(); 276 } 277 } 278 } 279 } 280 281 @Override 282 public boolean onKey(View v, int keyCode, KeyEvent event) { 283 if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) { 284 dismiss(); 285 return true; 286 } 287 return false; 288 } 289 290 /** 291 * Determines the proper initial menu position for the current LTR/RTL configuration. 292 * @return The initial position. 293 */ 294 @HorizPosition 295 private int getInitialMenuPosition() { 296 final int layoutDirection = mAnchorView.getLayoutDirection(); 297 return layoutDirection == View.LAYOUT_DIRECTION_RTL ? HORIZ_POSITION_LEFT : 298 HORIZ_POSITION_RIGHT; 299 } 300 301 /** 302 * Determines whether the next submenu (of the given width) should display on the right or on 303 * the left of the most recent menu. 304 * 305 * @param nextMenuWidth Width of the next submenu to display. 306 * @return The position to display it. 307 */ 308 @HorizPosition 309 private int getNextMenuPosition(int nextMenuWidth) { 310 ListView lastListView = mShowingMenus.get(mShowingMenus.size() - 1).getListView(); 311 312 final int[] screenLocation = new int[2]; 313 lastListView.getLocationOnScreen(screenLocation); 314 315 final Rect displayFrame = new Rect(); 316 mShownAnchorView.getWindowVisibleDisplayFrame(displayFrame); 317 318 if (mLastPosition == HORIZ_POSITION_RIGHT) { 319 final int right = screenLocation[0] + lastListView.getWidth() + nextMenuWidth; 320 if (right > displayFrame.right) { 321 return HORIZ_POSITION_LEFT; 322 } 323 return HORIZ_POSITION_RIGHT; 324 } else { // LEFT 325 final int left = screenLocation[0] - nextMenuWidth; 326 if (left < 0) { 327 return HORIZ_POSITION_RIGHT; 328 } 329 return HORIZ_POSITION_LEFT; 330 } 331 } 332 333 @Override 334 public void addMenu(MenuBuilder menu) { 335 menu.addMenuPresenter(this, mContext); 336 337 if (isShowing()) { 338 showMenu(menu); 339 } else { 340 mPendingMenus.add(menu); 341 } 342 } 343 344 /** 345 * Prepares and shows the specified menu immediately. 346 * 347 * @param menu the menu to show 348 */ 349 private void showMenu(@NonNull MenuBuilder menu) { 350 final LayoutInflater inflater = LayoutInflater.from(mContext); 351 final MenuAdapter adapter = new MenuAdapter(menu, inflater, mOverflowOnly); 352 353 // Apply "force show icon" setting. There are 3 cases: 354 // (1) This is the top level menu and icon spacing is forced. Add spacing. 355 // (2) This is a submenu. Add spacing if any of the visible menu items has an icon. 356 // (3) This is the top level menu and icon spacing isn't forced. Do not add spacing. 357 if (!isShowing() && mForceShowIcon) { 358 // Case 1 359 adapter.setForceShowIcon(true); 360 } else if (isShowing()) { 361 // Case 2 362 adapter.setForceShowIcon(MenuPopup.shouldPreserveIconSpacing(menu)); 363 } 364 // Case 3: Else, don't allow spacing for icons (default behavior; do nothing). 365 366 final int menuWidth = measureIndividualMenuWidth(adapter, null, mContext, mMenuMaxWidth); 367 final MenuPopupWindow popupWindow = createPopupWindow(); 368 popupWindow.setAdapter(adapter); 369 popupWindow.setWidth(menuWidth); 370 popupWindow.setDropDownGravity(mDropDownGravity); 371 372 final CascadingMenuInfo parentInfo; 373 final View parentView; 374 if (mShowingMenus.size() > 0) { 375 parentInfo = mShowingMenus.get(mShowingMenus.size() - 1); 376 parentView = findParentViewForSubmenu(parentInfo, menu); 377 } else { 378 parentInfo = null; 379 parentView = null; 380 } 381 382 if (parentView != null) { 383 // This menu is a cascading submenu anchored to a parent view. 384 popupWindow.setTouchModal(false); 385 popupWindow.setEnterTransition(null); 386 387 final @HorizPosition int nextMenuPosition = getNextMenuPosition(menuWidth); 388 final boolean showOnRight = nextMenuPosition == HORIZ_POSITION_RIGHT; 389 mLastPosition = nextMenuPosition; 390 391 final int[] tempLocation = new int[2]; 392 393 // This popup menu will be positioned relative to the top-left edge 394 // of the view representing its parent menu. 395 parentView.getLocationInWindow(tempLocation); 396 final int parentOffsetLeft = parentInfo.window.getHorizontalOffset() + tempLocation[0]; 397 final int parentOffsetTop = parentInfo.window.getVerticalOffset() + tempLocation[1]; 398 399 // By now, mDropDownGravity is the resolved absolute gravity, so 400 // this should work in both LTR and RTL. 401 final int x; 402 if ((mDropDownGravity & Gravity.RIGHT) == Gravity.RIGHT) { 403 if (showOnRight) { 404 x = parentOffsetLeft + menuWidth; 405 } else { 406 x = parentOffsetLeft - parentView.getWidth(); 407 } 408 } else { 409 if (showOnRight) { 410 x = parentOffsetLeft + parentView.getWidth(); 411 } else { 412 x = parentOffsetLeft - menuWidth; 413 } 414 } 415 416 popupWindow.setHorizontalOffset(x); 417 418 final int y = parentOffsetTop; 419 popupWindow.setVerticalOffset(y); 420 } else { 421 if (mHasXOffset) { 422 popupWindow.setHorizontalOffset(mXOffset); 423 } 424 if (mHasYOffset) { 425 popupWindow.setVerticalOffset(mYOffset); 426 } 427 final Rect epicenterBounds = getEpicenterBounds(); 428 popupWindow.setEpicenterBounds(epicenterBounds); 429 } 430 431 432 final CascadingMenuInfo menuInfo = new CascadingMenuInfo(popupWindow, menu, mLastPosition); 433 mShowingMenus.add(menuInfo); 434 435 popupWindow.show(); 436 437 // If this is the root menu, show the title if requested. 438 if (parentInfo == null && mShowTitle && menu.getHeaderTitle() != null) { 439 final ListView listView = popupWindow.getListView(); 440 final FrameLayout titleItemView = (FrameLayout) inflater.inflate( 441 R.layout.popup_menu_header_item_layout, listView, false); 442 final TextView titleView = (TextView) titleItemView.findViewById(R.id.title); 443 titleItemView.setEnabled(false); 444 titleView.setText(menu.getHeaderTitle()); 445 listView.addHeaderView(titleItemView, null, false); 446 447 // Show again to update the title. 448 popupWindow.show(); 449 } 450 } 451 452 /** 453 * Returns the menu item within the specified parent menu that owns 454 * specified submenu. 455 * 456 * @param parent the parent menu 457 * @param submenu the submenu for which the index should be returned 458 * @return the menu item that owns the submenu, or {@code null} if not 459 * present 460 */ 461 private MenuItem findMenuItemForSubmenu( 462 @NonNull MenuBuilder parent, @NonNull MenuBuilder submenu) { 463 for (int i = 0, count = parent.size(); i < count; i++) { 464 final MenuItem item = parent.getItem(i); 465 if (item.hasSubMenu() && submenu == item.getSubMenu()) { 466 return item; 467 } 468 } 469 470 return null; 471 } 472 473 /** 474 * Attempts to find the view for the menu item that owns the specified 475 * submenu. 476 * 477 * @param parentInfo info for the parent menu 478 * @param submenu the submenu whose parent view should be obtained 479 * @return the parent view, or {@code null} if one could not be found 480 */ 481 @Nullable 482 private View findParentViewForSubmenu( 483 @NonNull CascadingMenuInfo parentInfo, @NonNull MenuBuilder submenu) { 484 final MenuItem owner = findMenuItemForSubmenu(parentInfo.menu, submenu); 485 if (owner == null) { 486 // Couldn't find the submenu owner. 487 return null; 488 } 489 490 // The adapter may be wrapped. Adjust the index if necessary. 491 final int headersCount; 492 final MenuAdapter menuAdapter; 493 final ListView listView = parentInfo.getListView(); 494 final ListAdapter listAdapter = listView.getAdapter(); 495 if (listAdapter instanceof HeaderViewListAdapter) { 496 final HeaderViewListAdapter headerAdapter = (HeaderViewListAdapter) listAdapter; 497 headersCount = headerAdapter.getHeadersCount(); 498 menuAdapter = (MenuAdapter) headerAdapter.getWrappedAdapter(); 499 } else { 500 headersCount = 0; 501 menuAdapter = (MenuAdapter) listAdapter; 502 } 503 504 // Find the index within the menu adapter's data set of the menu item. 505 int ownerPosition = AbsListView.INVALID_POSITION; 506 for (int i = 0, count = menuAdapter.getCount(); i < count; i++) { 507 if (owner == menuAdapter.getItem(i)) { 508 ownerPosition = i; 509 break; 510 } 511 } 512 if (ownerPosition == AbsListView.INVALID_POSITION) { 513 // Couldn't find the owner within the menu adapter. 514 return null; 515 } 516 517 // Adjust the index for the adapter used to display views. 518 ownerPosition += headersCount; 519 520 // Adjust the index for the visible views. 521 final int ownerViewPosition = ownerPosition - listView.getFirstVisiblePosition(); 522 if (ownerViewPosition < 0 || ownerViewPosition >= listView.getChildCount()) { 523 // Not visible on screen. 524 return null; 525 } 526 527 return listView.getChildAt(ownerViewPosition); 528 } 529 530 /** 531 * @return {@code true} if the popup is currently showing, {@code false} otherwise. 532 */ 533 @Override 534 public boolean isShowing() { 535 return mShowingMenus.size() > 0 && mShowingMenus.get(0).window.isShowing(); 536 } 537 538 /** 539 * Called when one or more of the popup windows was dismissed. 540 */ 541 @Override 542 public void onDismiss() { 543 // The dismiss listener doesn't pass the calling window, so walk 544 // through the stack to figure out which one was just dismissed. 545 CascadingMenuInfo dismissedInfo = null; 546 for (int i = 0, count = mShowingMenus.size(); i < count; i++) { 547 final CascadingMenuInfo info = mShowingMenus.get(i); 548 if (!info.window.isShowing()) { 549 dismissedInfo = info; 550 break; 551 } 552 } 553 554 // Close all menus starting from the dismissed menu, passing false 555 // since we are manually closing only a subset of windows. 556 if (dismissedInfo != null) { 557 dismissedInfo.menu.close(false); 558 } 559 } 560 561 @Override 562 public void updateMenuView(boolean cleared) { 563 for (CascadingMenuInfo info : mShowingMenus) { 564 toMenuAdapter(info.getListView().getAdapter()).notifyDataSetChanged(); 565 } 566 } 567 568 @Override 569 public void setCallback(Callback cb) { 570 mPresenterCallback = cb; 571 } 572 573 @Override 574 public boolean onSubMenuSelected(SubMenuBuilder subMenu) { 575 // Don't allow double-opening of the same submenu. 576 for (CascadingMenuInfo info : mShowingMenus) { 577 if (subMenu == info.menu) { 578 // Just re-focus that one. 579 info.getListView().requestFocus(); 580 return true; 581 } 582 } 583 584 if (subMenu.hasVisibleItems()) { 585 addMenu(subMenu); 586 587 if (mPresenterCallback != null) { 588 mPresenterCallback.onOpenSubMenu(subMenu); 589 } 590 return true; 591 } 592 return false; 593 } 594 595 /** 596 * Finds the index of the specified menu within the list of added menus. 597 * 598 * @param menu the menu to find 599 * @return the index of the menu, or {@code -1} if not present 600 */ 601 private int findIndexOfAddedMenu(@NonNull MenuBuilder menu) { 602 for (int i = 0, count = mShowingMenus.size(); i < count; i++) { 603 final CascadingMenuInfo info = mShowingMenus.get(i); 604 if (menu == info.menu) { 605 return i; 606 } 607 } 608 609 return -1; 610 } 611 612 @Override 613 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 614 final int menuIndex = findIndexOfAddedMenu(menu); 615 if (menuIndex < 0) { 616 return; 617 } 618 619 // Recursively close descendant menus. 620 final int nextMenuIndex = menuIndex + 1; 621 if (nextMenuIndex < mShowingMenus.size()) { 622 final CascadingMenuInfo childInfo = mShowingMenus.get(nextMenuIndex); 623 childInfo.menu.close(false /* closeAllMenus */); 624 } 625 626 // Close the target menu. 627 final CascadingMenuInfo info = mShowingMenus.remove(menuIndex); 628 info.menu.removeMenuPresenter(this); 629 if (mShouldCloseImmediately) { 630 // Disable all exit animations. 631 info.window.setExitTransition(null); 632 info.window.setAnimationStyle(0); 633 } 634 info.window.dismiss(); 635 636 final int count = mShowingMenus.size(); 637 if (count > 0) { 638 mLastPosition = mShowingMenus.get(count - 1).position; 639 } else { 640 mLastPosition = getInitialMenuPosition(); 641 } 642 643 if (count == 0) { 644 // This was the last window. Clean up. 645 dismiss(); 646 647 if (mPresenterCallback != null) { 648 mPresenterCallback.onCloseMenu(menu, true); 649 } 650 651 if (mTreeObserver != null) { 652 if (mTreeObserver.isAlive()) { 653 mTreeObserver.removeGlobalOnLayoutListener(mGlobalLayoutListener); 654 } 655 mTreeObserver = null; 656 } 657 mShownAnchorView.removeOnAttachStateChangeListener(mAttachStateChangeListener); 658 659 // If every [sub]menu was dismissed, that means the whole thing was 660 // dismissed, so notify the owner. 661 mOnDismissListener.onDismiss(); 662 } else if (allMenusAreClosing) { 663 // Close all menus starting from the root. This will recursively 664 // close any remaining menus, so we don't need to propagate the 665 // "closeAllMenus" flag. The last window will clean up. 666 final CascadingMenuInfo rootInfo = mShowingMenus.get(0); 667 rootInfo.menu.close(false /* closeAllMenus */); 668 } 669 } 670 671 @Override 672 public boolean flagActionItems() { 673 return false; 674 } 675 676 @Override 677 public Parcelable onSaveInstanceState() { 678 return null; 679 } 680 681 @Override 682 public void onRestoreInstanceState(Parcelable state) { 683 } 684 685 @Override 686 public void setGravity(int dropDownGravity) { 687 if (mRawDropDownGravity != dropDownGravity) { 688 mRawDropDownGravity = dropDownGravity; 689 mDropDownGravity = Gravity.getAbsoluteGravity( 690 dropDownGravity, mAnchorView.getLayoutDirection()); 691 } 692 } 693 694 @Override 695 public void setAnchorView(@NonNull View anchor) { 696 if (mAnchorView != anchor) { 697 mAnchorView = anchor; 698 699 // Gravity resolution may have changed, update from raw gravity. 700 mDropDownGravity = Gravity.getAbsoluteGravity( 701 mRawDropDownGravity, mAnchorView.getLayoutDirection()); 702 } 703 } 704 705 @Override 706 public void setOnDismissListener(OnDismissListener listener) { 707 mOnDismissListener = listener; 708 } 709 710 @Override 711 public ListView getListView() { 712 return mShowingMenus.isEmpty() ? null : mShowingMenus.get(mShowingMenus.size() - 1).getListView(); 713 } 714 715 @Override 716 public void setHorizontalOffset(int x) { 717 mHasXOffset = true; 718 mXOffset = x; 719 } 720 721 @Override 722 public void setVerticalOffset(int y) { 723 mHasYOffset = true; 724 mYOffset = y; 725 } 726 727 @Override 728 public void setShowTitle(boolean showTitle) { 729 mShowTitle = showTitle; 730 } 731 732 private static class CascadingMenuInfo { 733 public final MenuPopupWindow window; 734 public final MenuBuilder menu; 735 public final int position; 736 737 public CascadingMenuInfo(@NonNull MenuPopupWindow window, @NonNull MenuBuilder menu, 738 int position) { 739 this.window = window; 740 this.menu = menu; 741 this.position = position; 742 } 743 744 public ListView getListView() { 745 return window.getListView(); 746 } 747 } 748} 749