Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b40e61b7 authored by Vladislav Kaznacheev's avatar Vladislav Kaznacheev
Browse files

Anchor a cascading submenu to its parent menu item

The current implementation of cascading menus is anchoring
a submenu to the top-level anchor view using a carefully
computed offset. This offset is only correct for the case when
the submenu is being shown normally, but not when it is
flipped due to insufficient space below.

More over, when the window containing the anchor is scrolled,
the pre-computed might become completely irrelevant, as
parent menus might change their above/below state.

This CL allows a PopupWindow to be anchored to a view in
another popup window (previously it was only possible to
anchor to a view in the main app window).

Cascading submenu is now tracking its parent item position
correctly.

Bug: 35768002
Test: android.cts.widget.PopupWindowTest.testAnchorInPopup
Change-Id: Id163d739de05729a9fa7e5fedebc9ec0037ed80e
parent 07084924
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -75,6 +75,7 @@ public class ListPopupWindow implements ShowableListMenu {
    private int mDropDownWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
    private boolean mDropDownVerticalOffsetSet;
    private boolean mIsAnimatedFromAnchor = true;
    private boolean mOverlapAnchor;

    private int mDropDownGravity = Gravity.NO_GRAVITY;

@@ -668,6 +669,7 @@ public class ListPopupWindow implements ShowableListMenu {
            mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
            mPopup.setTouchInterceptor(mTouchInterceptor);
            mPopup.setEpicenterBounds(mEpicenterBounds);
            mPopup.setOverlapAnchor(mOverlapAnchor);
            mPopup.showAsDropDown(getAnchorView(), mDropDownHorizontalOffset,
                    mDropDownVerticalOffset, mDropDownGravity);
            mDropDownList.setSelection(ListView.INVALID_POSITION);
@@ -1241,6 +1243,13 @@ public class ListPopupWindow implements ShowableListMenu {
        return listContent + otherHeights;
    }

    /**
     * @hide
     */
    public void setOverlapAnchor(boolean overlap) {
        mOverlapAnchor = overlap;
    }

    private class PopupDataSetObserver extends DataSetObserver {
        @Override
        public void onChanged() {
+33 −8
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ import android.view.ViewTreeObserver.OnScrollChangedListener;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import android.view.WindowManagerGlobal;

import com.android.internal.R;

@@ -137,6 +138,7 @@ public class PopupWindow {

    private final int[] mTmpDrawingLocation = new int[2];
    private final int[] mTmpScreenLocation = new int[2];
    private final int[] mTmpAppLocation = new int[2];
    private final Rect mTempRect = new Rect();

    private Context mContext;
@@ -242,6 +244,9 @@ public class PopupWindow {

    private final OnScrollChangedListener mOnScrollChangedListener = this::alignToAnchor;

    private final View.OnLayoutChangeListener mOnLayoutChangeListener =
            (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> alignToAnchor();

    private int mAnchorXoff;
    private int mAnchorYoff;
    private int mAnchoredGravity;
@@ -1238,7 +1243,8 @@ public class PopupWindow {
        mIsShowing = true;
        mIsDropdown = true;

        final WindowManager.LayoutParams p = createPopupLayoutParams(anchor.getWindowToken());
        final WindowManager.LayoutParams p =
                createPopupLayoutParams(anchor.getApplicationWindowToken());
        preparePopup(p);

        final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff,
@@ -1547,13 +1553,21 @@ public class PopupWindow {
        }

        // Initially, align to the bottom-left corner of the anchor plus offsets.
        final int[] appScreenLocation = mTmpAppLocation;
        final View appRootView = getAppRootView(anchor);
        appRootView.getLocationOnScreen(appScreenLocation);

        final int[] screenLocation = mTmpScreenLocation;
        anchor.getLocationOnScreen(screenLocation);

        final int[] drawingLocation = mTmpDrawingLocation;
        anchor.getLocationInWindow(drawingLocation);
        drawingLocation[0] = screenLocation[0] - appScreenLocation[0];
        drawingLocation[1] = screenLocation[1] - appScreenLocation[1];
        outParams.x = drawingLocation[0] + xOffset;
        outParams.y = drawingLocation[1] + anchorHeight + yOffset;

        final Rect displayFrame = new Rect();
        anchor.getWindowVisibleDisplayFrame(displayFrame);
        appRootView.getWindowVisibleDisplayFrame(displayFrame);
        if (width == MATCH_PARENT) {
            width = displayFrame.right - displayFrame.left;
        }
@@ -1574,9 +1588,6 @@ public class PopupWindow {
            outParams.x -= width - anchorWidth;
        }

        final int[] screenLocation = mTmpScreenLocation;
        anchor.getLocationOnScreen(screenLocation);

        // First, attempt to fit the popup vertically without resizing.
        final boolean fitsVertical = tryFitVertical(outParams, yOffset, height,
                anchorHeight, drawingLocation[1], screenLocation[1], displayFrame.top,
@@ -1595,7 +1606,9 @@ public class PopupWindow {
                    scrollY + height + anchorHeight + yOffset);
            if (allowScroll && anchor.requestRectangleOnScreen(r, true)) {
                // Reset for the new anchor position.
                anchor.getLocationInWindow(drawingLocation);
                anchor.getLocationOnScreen(screenLocation);
                drawingLocation[0] = screenLocation[0] - appScreenLocation[0];
                drawingLocation[1] = screenLocation[1] - appScreenLocation[1];
                outParams.x = drawingLocation[0] + xOffset;
                outParams.y = drawingLocation[1] + anchorHeight + yOffset;

@@ -1793,7 +1806,8 @@ public class PopupWindow {
        Rect displayFrame = null;
        final Rect visibleDisplayFrame = new Rect();

        anchor.getWindowVisibleDisplayFrame(visibleDisplayFrame);
        final View appView = getAppRootView(anchor);
        appView.getWindowVisibleDisplayFrame(visibleDisplayFrame);
        if (ignoreBottomDecorations) {
            // In the ignore bottom decorations case we want to
            // still respect all other decorations so we use the inset visible
@@ -2240,6 +2254,7 @@ public class PopupWindow {
        final View anchorRoot = mAnchorRoot != null ? mAnchorRoot.get() : null;
        if (anchorRoot != null) {
            anchorRoot.removeOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
            anchorRoot.removeOnLayoutChangeListener(mOnLayoutChangeListener);
        }

        mAnchor = null;
@@ -2258,6 +2273,7 @@ public class PopupWindow {

        final View anchorRoot = anchor.getRootView();
        anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
        anchorRoot.addOnLayoutChangeListener(mOnLayoutChangeListener);

        mAnchor = new WeakReference<>(anchor);
        mAnchorRoot = new WeakReference<>(anchorRoot);
@@ -2281,6 +2297,15 @@ public class PopupWindow {
        }
    }

    private View getAppRootView(View anchor) {
        final View appWindowView = WindowManagerGlobal.getInstance().getWindowView(
                anchor.getApplicationWindowToken());
        if (appWindowView != null) {
            return appWindowView;
        }
        return anchor.getRootView();
    }

    private class PopupDecorView extends FrameLayout {
        private TransitionListenerAdapter mPendingExitListener;

+10 −21
Original line number Diff line number Diff line
@@ -381,6 +381,7 @@ final class CascadingMenuPopup extends MenuPopup implements MenuPresenter, OnKey

        if (parentView != null) {
            // This menu is a cascading submenu anchored to a parent view.
            popupWindow.setAnchorView(parentView);
            popupWindow.setTouchModal(false);
            popupWindow.setEnterTransition(null);

@@ -388,42 +389,30 @@ final class CascadingMenuPopup extends MenuPopup implements MenuPresenter, OnKey
            final boolean showOnRight = nextMenuPosition == HORIZ_POSITION_RIGHT;
            mLastPosition = nextMenuPosition;

            // A popup anchored to mAnchorView with (0,0) offset would be shown at this position.
            final int[] offsetOrigin = new int[2];
            mAnchorView.getLocationOnScreen(offsetOrigin);
            offsetOrigin[1] += mAnchorView.getHeight();

            final int[] parentViewScreenLocation = new int[2];
            parentView.getLocationOnScreen(parentViewScreenLocation);

            // Translate the parent view location into the offset coordinate space.
            // If used as horizontal/vertical offsets, these values would position the submenu
            // at the exact same position as the parent item.
            final int parentOffsetLeft = parentViewScreenLocation[0] - offsetOrigin[0];
            final int parentOffsetTop = parentViewScreenLocation[1] - offsetOrigin[1];

            // Adjust the horizontal offset to display the submenu to the right or to the left
            // Compute the horizontal offset to display the submenu to the right or to the left
            // of the parent item.
            // By now, mDropDownGravity is the resolved absolute gravity, so
            // this should work in both LTR and RTL.
            final int x;
            if ((mDropDownGravity & Gravity.RIGHT) == Gravity.RIGHT) {
                if (showOnRight) {
                    x = parentOffsetLeft + menuWidth;
                    x = menuWidth;
                } else {
                    x = parentOffsetLeft - parentView.getWidth();
                    x = -parentView.getWidth();
                }
            } else {
                if (showOnRight) {
                    x = parentOffsetLeft + parentView.getWidth();
                    x = parentView.getWidth();
                } else {
                    x = parentOffsetLeft - menuWidth;
                    x = -menuWidth;
                }
            }
            popupWindow.setHorizontalOffset(x);

            // Use the same vertical offset as the parent item.
            popupWindow.setVerticalOffset(parentOffsetTop);
            // Align with the top edge of the parent view (or the bottom edge when the submenu is
            // flipped vertically).
            popupWindow.setOverlapAnchor(true);
            popupWindow.setVerticalOffset(0);
        } else {
            if (mHasXOffset) {
                popupWindow.setHorizontalOffset(mXOffset);