1 package com.android.internal.view.menu; 2 3 import java.lang.annotation.Retention; 4 import java.lang.annotation.RetentionPolicy; 5 import java.util.ArrayList; 6 import java.util.LinkedList; 7 import java.util.List; 8 9 import android.annotation.AttrRes; 10 import android.annotation.IntDef; 11 import android.annotation.NonNull; 12 import android.annotation.Nullable; 13 import android.annotation.StyleRes; 14 import android.content.Context; 15 import android.content.res.Resources; 16 import android.graphics.Rect; 17 import android.os.Handler; 18 import android.os.Parcelable; 19 import android.os.SystemClock; 20 import android.view.Gravity; 21 import android.view.KeyEvent; 22 import android.view.LayoutInflater; 23 import android.view.MenuItem; 24 import android.view.View; 25 import android.view.ViewTreeObserver; 26 import android.view.View.OnAttachStateChangeListener; 27 import android.view.View.OnKeyListener; 28 import android.view.ViewTreeObserver.OnGlobalLayoutListener; 29 import android.widget.AbsListView; 30 import android.widget.FrameLayout; 31 import android.widget.HeaderViewListAdapter; 32 import android.widget.ListAdapter; 33 import android.widget.MenuItemHoverListener; 34 import android.widget.ListView; 35 import android.widget.MenuPopupWindow; 36 import android.widget.PopupWindow; 37 import android.widget.PopupWindow.OnDismissListener; 38 import android.widget.TextView; 39 40 import com.android.internal.R; 41 import 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 */ 48 final 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.setContentWidth(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.setAnchorView(parentView); 385 popupWindow.setTouchModal(false); 386 popupWindow.setEnterTransition(null); 387 388 final @HorizPosition int nextMenuPosition = getNextMenuPosition(menuWidth); 389 final boolean showOnRight = nextMenuPosition == HORIZ_POSITION_RIGHT; 390 mLastPosition = nextMenuPosition; 391 392 // Compute the horizontal offset to display the submenu to the right or to the left 393 // of the parent item. 394 // By now, mDropDownGravity is the resolved absolute gravity, so 395 // this should work in both LTR and RTL. 396 final int x; 397 if ((mDropDownGravity & Gravity.RIGHT) == Gravity.RIGHT) { 398 if (showOnRight) { 399 x = menuWidth; 400 } else { 401 x = -parentView.getWidth(); 402 } 403 } else { 404 if (showOnRight) { 405 x = parentView.getWidth(); 406 } else { 407 x = -menuWidth; 408 } 409 } 410 popupWindow.setHorizontalOffset(x); 411 412 // Align with the top edge of the parent view (or the bottom edge when the submenu is 413 // flipped vertically). 414 popupWindow.setOverlapAnchor(true); 415 popupWindow.setVerticalOffset(0); 416 } else { 417 if (mHasXOffset) { 418 popupWindow.setHorizontalOffset(mXOffset); 419 } 420 if (mHasYOffset) { 421 popupWindow.setVerticalOffset(mYOffset); 422 } 423 final Rect epicenterBounds = getEpicenterBounds(); 424 popupWindow.setEpicenterBounds(epicenterBounds); 425 } 426 427 428 final CascadingMenuInfo menuInfo = new CascadingMenuInfo(popupWindow, menu, mLastPosition); 429 mShowingMenus.add(menuInfo); 430 431 popupWindow.show(); 432 433 final ListView listView = popupWindow.getListView(); 434 listView.setOnKeyListener(this); 435 436 // If this is the root menu, show the title if requested. 437 if (parentInfo == null && mShowTitle && menu.getHeaderTitle() != null) { 438 final FrameLayout titleItemView = (FrameLayout) inflater.inflate( 439 R.layout.popup_menu_header_item_layout, listView, false); 440 final TextView titleView = (TextView) titleItemView.findViewById(R.id.title); 441 titleItemView.setEnabled(false); 442 titleView.setText(menu.getHeaderTitle()); 443 listView.addHeaderView(titleItemView, null, false); 444 445 // Show again to update the title. 446 popupWindow.show(); 447 } 448 } 449 450 /** 451 * Returns the menu item within the specified parent menu that owns 452 * specified submenu. 453 * 454 * @param parent the parent menu 455 * @param submenu the submenu for which the index should be returned 456 * @return the menu item that owns the submenu, or {@code null} if not 457 * present 458 */ 459 private MenuItem findMenuItemForSubmenu( 460 @NonNull MenuBuilder parent, @NonNull MenuBuilder submenu) { 461 for (int i = 0, count = parent.size(); i < count; i++) { 462 final MenuItem item = parent.getItem(i); 463 if (item.hasSubMenu() && submenu == item.getSubMenu()) { 464 return item; 465 } 466 } 467 468 return null; 469 } 470 471 /** 472 * Attempts to find the view for the menu item that owns the specified 473 * submenu. 474 * 475 * @param parentInfo info for the parent menu 476 * @param submenu the submenu whose parent view should be obtained 477 * @return the parent view, or {@code null} if one could not be found 478 */ 479 @Nullable 480 private View findParentViewForSubmenu( 481 @NonNull CascadingMenuInfo parentInfo, @NonNull MenuBuilder submenu) { 482 final MenuItem owner = findMenuItemForSubmenu(parentInfo.menu, submenu); 483 if (owner == null) { 484 // Couldn't find the submenu owner. 485 return null; 486 } 487 488 // The adapter may be wrapped. Adjust the index if necessary. 489 final int headersCount; 490 final MenuAdapter menuAdapter; 491 final ListView listView = parentInfo.getListView(); 492 final ListAdapter listAdapter = listView.getAdapter(); 493 if (listAdapter instanceof HeaderViewListAdapter) { 494 final HeaderViewListAdapter headerAdapter = (HeaderViewListAdapter) listAdapter; 495 headersCount = headerAdapter.getHeadersCount(); 496 menuAdapter = (MenuAdapter) headerAdapter.getWrappedAdapter(); 497 } else { 498 headersCount = 0; 499 menuAdapter = (MenuAdapter) listAdapter; 500 } 501 502 // Find the index within the menu adapter's data set of the menu item. 503 int ownerPosition = AbsListView.INVALID_POSITION; 504 for (int i = 0, count = menuAdapter.getCount(); i < count; i++) { 505 if (owner == menuAdapter.getItem(i)) { 506 ownerPosition = i; 507 break; 508 } 509 } 510 if (ownerPosition == AbsListView.INVALID_POSITION) { 511 // Couldn't find the owner within the menu adapter. 512 return null; 513 } 514 515 // Adjust the index for the adapter used to display views. 516 ownerPosition += headersCount; 517 518 // Adjust the index for the visible views. 519 final int ownerViewPosition = ownerPosition - listView.getFirstVisiblePosition(); 520 if (ownerViewPosition < 0 || ownerViewPosition >= listView.getChildCount()) { 521 // Not visible on screen. 522 return null; 523 } 524 525 return listView.getChildAt(ownerViewPosition); 526 } 527 528 /** 529 * @return {@code true} if the popup is currently showing, {@code false} otherwise. 530 */ 531 @Override 532 public boolean isShowing() { 533 return mShowingMenus.size() > 0 && mShowingMenus.get(0).window.isShowing(); 534 } 535 536 /** 537 * Called when one or more of the popup windows was dismissed. 538 */ 539 @Override 540 public void onDismiss() { 541 // The dismiss listener doesn't pass the calling window, so walk 542 // through the stack to figure out which one was just dismissed. 543 CascadingMenuInfo dismissedInfo = null; 544 for (int i = 0, count = mShowingMenus.size(); i < count; i++) { 545 final CascadingMenuInfo info = mShowingMenus.get(i); 546 if (!info.window.isShowing()) { 547 dismissedInfo = info; 548 break; 549 } 550 } 551 552 // Close all menus starting from the dismissed menu, passing false 553 // since we are manually closing only a subset of windows. 554 if (dismissedInfo != null) { 555 dismissedInfo.menu.close(false); 556 } 557 } 558 559 @Override 560 public void updateMenuView(boolean cleared) { 561 for (CascadingMenuInfo info : mShowingMenus) { 562 toMenuAdapter(info.getListView().getAdapter()).notifyDataSetChanged(); 563 } 564 } 565 566 @Override 567 public void setCallback(Callback cb) { 568 mPresenterCallback = cb; 569 } 570 571 @Override 572 public boolean onSubMenuSelected(SubMenuBuilder subMenu) { 573 // Don't allow double-opening of the same submenu. 574 for (CascadingMenuInfo info : mShowingMenus) { 575 if (subMenu == info.menu) { 576 // Just re-focus that one. 577 info.getListView().requestFocus(); 578 return true; 579 } 580 } 581 582 if (subMenu.hasVisibleItems()) { 583 addMenu(subMenu); 584 585 if (mPresenterCallback != null) { 586 mPresenterCallback.onOpenSubMenu(subMenu); 587 } 588 return true; 589 } 590 return false; 591 } 592 593 /** 594 * Finds the index of the specified menu within the list of added menus. 595 * 596 * @param menu the menu to find 597 * @return the index of the menu, or {@code -1} if not present 598 */ 599 private int findIndexOfAddedMenu(@NonNull MenuBuilder menu) { 600 for (int i = 0, count = mShowingMenus.size(); i < count; i++) { 601 final CascadingMenuInfo info = mShowingMenus.get(i); 602 if (menu == info.menu) { 603 return i; 604 } 605 } 606 607 return -1; 608 } 609 610 @Override 611 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 612 final int menuIndex = findIndexOfAddedMenu(menu); 613 if (menuIndex < 0) { 614 return; 615 } 616 617 // Recursively close descendant menus. 618 final int nextMenuIndex = menuIndex + 1; 619 if (nextMenuIndex < mShowingMenus.size()) { 620 final CascadingMenuInfo childInfo = mShowingMenus.get(nextMenuIndex); 621 childInfo.menu.close(false /* closeAllMenus */); 622 } 623 624 // Close the target menu. 625 final CascadingMenuInfo info = mShowingMenus.remove(menuIndex); 626 info.menu.removeMenuPresenter(this); 627 if (mShouldCloseImmediately) { 628 // Disable all exit animations. 629 info.window.setExitTransition(null); 630 info.window.setAnimationStyle(0); 631 } 632 info.window.dismiss(); 633 634 final int count = mShowingMenus.size(); 635 if (count > 0) { 636 mLastPosition = mShowingMenus.get(count - 1).position; 637 } else { 638 mLastPosition = getInitialMenuPosition(); 639 } 640 641 if (count == 0) { 642 // This was the last window. Clean up. 643 dismiss(); 644 645 if (mPresenterCallback != null) { 646 mPresenterCallback.onCloseMenu(menu, true); 647 } 648 649 if (mTreeObserver != null) { 650 if (mTreeObserver.isAlive()) { 651 mTreeObserver.removeGlobalOnLayoutListener(mGlobalLayoutListener); 652 } 653 mTreeObserver = null; 654 } 655 mShownAnchorView.removeOnAttachStateChangeListener(mAttachStateChangeListener); 656 657 // If every [sub]menu was dismissed, that means the whole thing was 658 // dismissed, so notify the owner. 659 mOnDismissListener.onDismiss(); 660 } else if (allMenusAreClosing) { 661 // Close all menus starting from the root. This will recursively 662 // close any remaining menus, so we don't need to propagate the 663 // "closeAllMenus" flag. The last window will clean up. 664 final CascadingMenuInfo rootInfo = mShowingMenus.get(0); 665 rootInfo.menu.close(false /* closeAllMenus */); 666 } 667 } 668 669 @Override 670 public boolean flagActionItems() { 671 return false; 672 } 673 674 @Override 675 public Parcelable onSaveInstanceState() { 676 return null; 677 } 678 679 @Override 680 public void onRestoreInstanceState(Parcelable state) { 681 } 682 683 @Override 684 public void setGravity(int dropDownGravity) { 685 if (mRawDropDownGravity != dropDownGravity) { 686 mRawDropDownGravity = dropDownGravity; 687 mDropDownGravity = Gravity.getAbsoluteGravity( 688 dropDownGravity, mAnchorView.getLayoutDirection()); 689 } 690 } 691 692 @Override 693 public void setAnchorView(@NonNull View anchor) { 694 if (mAnchorView != anchor) { 695 mAnchorView = anchor; 696 697 // Gravity resolution may have changed, update from raw gravity. 698 mDropDownGravity = Gravity.getAbsoluteGravity( 699 mRawDropDownGravity, mAnchorView.getLayoutDirection()); 700 } 701 } 702 703 @Override 704 public void setOnDismissListener(OnDismissListener listener) { 705 mOnDismissListener = listener; 706 } 707 708 @Override 709 public ListView getListView() { 710 return mShowingMenus.isEmpty() ? null : mShowingMenus.get(mShowingMenus.size() - 1).getListView(); 711 } 712 713 @Override 714 public void setHorizontalOffset(int x) { 715 mHasXOffset = true; 716 mXOffset = x; 717 } 718 719 @Override 720 public void setVerticalOffset(int y) { 721 mHasYOffset = true; 722 mYOffset = y; 723 } 724 725 @Override 726 public void setShowTitle(boolean showTitle) { 727 mShowTitle = showTitle; 728 } 729 730 private static class CascadingMenuInfo { 731 public final MenuPopupWindow window; 732 public final MenuBuilder menu; 733 public final int position; 734 735 public CascadingMenuInfo(@NonNull MenuPopupWindow window, @NonNull MenuBuilder menu, 736 int position) { 737 this.window = window; 738 this.menu = menu; 739 this.position = position; 740 } 741 742 public ListView getListView() { 743 return window.getListView(); 744 } 745 } 746 } 747