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

Commit 8fd949e6 authored by Alan Viverette's avatar Alan Viverette
Browse files

Various fixes for popup monkey testing

Ensures PopupMenu works correctly when multiple calls are made to show
and dismiss. Ensure PopupWindow works correctly when multiple calls are
made to showAsDropDown and dismiss (fixes multiple clicks on Spinner).

Bug: 19672907
Bug: 19671831
Change-Id: Ib92accd8fd70a1ff1f8cda27155347b007a4d25b
parent 7d6bc4f0
Loading
Loading
Loading
Loading
+152 −100
Original line number Diff line number Diff line
@@ -29,6 +29,8 @@ import android.os.Build;
import android.os.IBinder;
import android.transition.Transition;
import android.transition.Transition.EpicenterCallback;
import android.transition.Transition.TransitionListener;
import android.transition.Transition.TransitionListenerAdapter;
import android.transition.TransitionInflater;
import android.transition.TransitionManager;
import android.transition.TransitionSet;
@@ -39,12 +41,13 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.ViewTreeObserver.OnScrollChangedListener;
import android.view.WindowManager;

import java.lang.ref.WeakReference;
import java.util.List;

/**
 * <p>A popup window that can be used to display an arbitrary view. The popup
@@ -96,14 +99,12 @@ public class PopupWindow {
    private WindowManager mWindowManager;

    private boolean mIsShowing;
    private boolean mIsTransitioningToDismiss;
    private boolean mIsDropdown;

    /** View that handles event dispatch and content transitions. */
    private PopupDecorView mDecorView;

    /** View that holds the popup background. May be the content view. */
    private View mBackgroundView;

    /** The contents of the popup. */
    private View mContentView;

@@ -1183,23 +1184,30 @@ public class PopupWindow {
                    + "calling setContentView() before attempting to show the popup.");
        }

        // The old decor view may be transitioning out. Make sure it finishes
        // and cleans up before we try to create another one.
        if (mDecorView != null) {
            mDecorView.cancelTransitions();
        }

        // When a background is available, we embed the content view within
        // another view that owns the background drawable.
        final View backgroundView;
        if (mBackground != null) {
            mBackgroundView = createBackgroundView(mContentView);
            mBackgroundView.setBackground(mBackground);
            backgroundView = createBackgroundView(mContentView);
            backgroundView.setBackground(mBackground);
        } else {
            mBackgroundView = mContentView;
            backgroundView = mContentView;
        }

        mDecorView = createDecorView(mBackgroundView);
        mDecorView = createDecorView(backgroundView);

        // The background owner should be elevated so that it casts a shadow.
        mBackgroundView.setElevation(mElevation);
        backgroundView.setElevation(mElevation);

        // We may wrap that in another view, so we'll need to manually specify
        // the surface insets.
        final int surfaceInset = (int) Math.ceil(mBackgroundView.getZ() * 2);
        final int surfaceInset = (int) Math.ceil(backgroundView.getZ() * 2);
        p.surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset);
        p.hasManualSurfaceInsets = true;

@@ -1268,26 +1276,13 @@ public class PopupWindow {
            p.packageName = mContext.getPackageName();
        }

        final View rootView = mContentView.getRootView();
        rootView.setFitsSystemWindows(mLayoutInsetDecor);
        setLayoutDirectionFromAnchor();
        final PopupDecorView decorView = mDecorView;
        decorView.setFitsSystemWindows(mLayoutInsetDecor);
        decorView.requestEnterTransition(mEnterTransition);

        mWindowManager.addView(rootView, p);

        // Postpone enter transition until the scene root has been laid out.
        if (mEnterTransition != null) {
            mEnterTransition.addTarget(mBackgroundView);
            mEnterTransition.addListener(new Transition.TransitionListenerAdapter() {
                @Override
                public void onTransitionEnd(Transition transition) {
                    transition.removeListener(this);
                    transition.removeTarget(mBackgroundView);
                }
            });
        setLayoutDirectionFromAnchor();

            mDecorView.getViewTreeObserver().addOnGlobalLayoutListener(
                    new PostLayoutTransitionListener(mDecorView, mEnterTransition));
        }
        mWindowManager.addView(decorView, p);
    }

    private void setLayoutDirectionFromAnchor() {
@@ -1591,35 +1586,38 @@ public class PopupWindow {
     * @see #showAsDropDown(android.view.View)
     */
    public void dismiss() {
        if (!isShowing()) {
        if (!isShowing() || mIsTransitioningToDismiss) {
            return;
        }

        final PopupDecorView decorView = mDecorView;
        final View contentView = mContentView;

        final ViewGroup contentHolder;
        final ViewParent contentParent = contentView.getParent();
        if (contentParent instanceof ViewGroup) {
            contentHolder = ((ViewGroup) contentParent);
        } else {
            contentHolder = null;
        }

        // Ensure any ongoing or pending transitions are canceled.
        decorView.cancelTransitions();

        unregisterForScrollChanged();

        mIsShowing = false;
        mIsTransitioningToDismiss = true;

        if (mExitTransition != null) {
            // Cache the content view, since it may change without notice.
            final View contentView = mContentView;

            mExitTransition.addTarget(mBackgroundView);
            mExitTransition.addListener(new Transition.TransitionListenerAdapter() {
        if (mExitTransition != null && decorView.isLaidOut()) {
            decorView.startExitTransition(mExitTransition, new TransitionListenerAdapter() {
                @Override
                public void onTransitionEnd(Transition transition) {
                    transition.removeListener(this);
                    transition.removeTarget(mBackgroundView);

                    dismissImmediate(contentView);
                    dismissImmediate(decorView, contentHolder, contentView);
                }
            });

            TransitionManager.beginDelayedTransition(mDecorView, mExitTransition);

            // Transition to invisible.
            mBackgroundView.setVisibility(View.INVISIBLE);
        } else {
            dismissImmediate(mContentView);
            dismissImmediate(decorView, contentHolder, contentView);
        }

        if (mOnDismissListener != null) {
@@ -1631,24 +1629,22 @@ public class PopupWindow {
     * Removes the popup from the window manager and tears down the supporting
     * view hierarchy, if necessary.
     */
    private void dismissImmediate(View contentView) {
        if (mDecorView == null || mBackgroundView == null) {
            throw new RuntimeException("Popup window already dismissed");
    private void dismissImmediate(View decorView, ViewGroup contentHolder, View contentView) {
        // If this method gets called and the decor view doesn't have a parent,
        // then it was either never added or was already removed. That should
        // never happen, but it's worth checking to avoid potential crashes.
        if (decorView.getParent() != null) {
            mWindowManager.removeViewImmediate(decorView);
        }

        try {
            if (mDecorView.isAttachedToWindow()) {
                mWindowManager.removeViewImmediate(mDecorView);
        if (contentHolder != null) {
            contentHolder.removeView(contentView);
        }
        } finally {
            mDecorView.removeView(mBackgroundView);
            mDecorView = null;

            if (mBackgroundView != contentView) {
                ((ViewGroup) mBackgroundView).removeView(contentView);
            }
            mBackgroundView = null;
        }
        // This needs to stay until after all transitions have ended since we
        // need the reference to cancel transitions in preparePopup().
        mDecorView = null;
        mIsTransitioningToDismiss = false;
    }

    /**
@@ -1909,47 +1905,9 @@ public class PopupWindow {
        mAnchoredGravity = gravity;
    }

    /**
     * Layout listener used to run a transition immediately after a view is
     * laid out. Forces the view to transition from invisible to visible.
     */
    private static class PostLayoutTransitionListener implements
            ViewTreeObserver.OnGlobalLayoutListener {
        private final ViewGroup mSceneRoot;
        private final Transition mTransition;

        public PostLayoutTransitionListener(ViewGroup sceneRoot, Transition transition) {
            mSceneRoot = sceneRoot;
            mTransition = transition;
        }

        @Override
        public void onGlobalLayout() {
            final ViewTreeObserver observer = mSceneRoot.getViewTreeObserver();
            if (observer == null) {
                // View has been detached.
                return;
            }

            observer.removeOnGlobalLayoutListener(this);

            // Set all targets to be initially invisible.
            final List<View> targets = mTransition.getTargets();
            final int N = targets.size();
            for (int i = 0; i < N; i++) {
                targets.get(i).setVisibility(View.INVISIBLE);
            }

            TransitionManager.beginDelayedTransition(mSceneRoot, mTransition);

            // Transition targets to visible.
            for (int i = 0; i < N; i++) {
                targets.get(i).setVisibility(View.VISIBLE);
            }
        }
    }

    private class PopupDecorView extends FrameLayout {
        private TransitionListenerAdapter mPendingExitListener;

        public PopupDecorView(Context context) {
            super(context);
        }
@@ -2004,6 +1962,100 @@ public class PopupWindow {
                return super.onTouchEvent(event);
            }
        }

        /**
         * Requests that an enter transition run after the next layout pass.
         */
        public void requestEnterTransition(Transition transition) {
            final ViewTreeObserver observer = getViewTreeObserver();
            if (observer != null && transition != null) {
                final Transition enterTransition = transition.clone();

                // Postpone the enter transition after the first layout pass.
                observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {
                        final ViewTreeObserver observer = getViewTreeObserver();
                        if (observer != null) {
                            observer.removeOnGlobalLayoutListener(this);
                        }

                        startEnterTransition(enterTransition);
                    }
                });
            }
        }

        /**
         * Starts the pending enter transition, if one is set.
         */
        private void startEnterTransition(Transition enterTransition) {
            final int count = getChildCount();
            for (int i = 0; i < count; i++) {
                final View child = getChildAt(i);
                enterTransition.addTarget(child);
                child.setVisibility(View.INVISIBLE);
            }

            TransitionManager.beginDelayedTransition(this, enterTransition);

            for (int i = 0; i < count; i++) {
                final View child = getChildAt(i);
                child.setVisibility(View.VISIBLE);
            }
        }

        /**
         * Starts an exit transition immediately.
         * <p>
         * <strong>Note:</strong> The transition listener is guaranteed to have
         * its {@code onTransitionEnd} method called even if the transition
         * never starts; however, it may be called with a {@code null} argument.
         */
        public void startExitTransition(Transition transition, final TransitionListener listener) {
            if (transition == null) {
                return;
            }

            // The exit listener MUST be called for cleanup, even if the
            // transition never starts or ends. Stash it for later.
            mPendingExitListener = new TransitionListenerAdapter() {
                @Override
                public void onTransitionEnd(Transition transition) {
                    listener.onTransitionEnd(transition);

                    // The listener was called. Our job here is done.
                    mPendingExitListener = null;
                }
            };

            final Transition exitTransition = transition.clone();
            exitTransition.addListener(mPendingExitListener);

            final int count = getChildCount();
            for (int i = 0; i < count; i++) {
                final View child = getChildAt(i);
                exitTransition.addTarget(child);
            }

            TransitionManager.beginDelayedTransition(this, exitTransition);

            for (int i = 0; i < count; i++) {
                final View child = getChildAt(i);
                child.setVisibility(View.INVISIBLE);
            }
        }

        /**
         * Cancels all pending or current transitions.
         */
        public void cancelTransitions() {
            TransitionManager.endTransitions(this);

            if (mPendingExitListener != null) {
                mPendingExitListener.onTransitionEnd(null);
            }
        }
    }

    private class PopupBackgroundView extends FrameLayout {
+13 −2
Original line number Diff line number Diff line
@@ -43,8 +43,6 @@ import java.util.ArrayList;
public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.OnKeyListener,
        ViewTreeObserver.OnGlobalLayoutListener, PopupWindow.OnDismissListener,
        View.OnAttachStateChangeListener, MenuPresenter {
    private static final String TAG = "MenuPopupHelper";

    static final int ITEM_LAYOUT = com.android.internal.R.layout.popup_menu_item_layout;

    private final Context mContext;
@@ -132,7 +130,18 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
        return mPopup;
    }

    /**
     * Attempts to show the popup anchored to the view specified by
     * {@link #setAnchorView(View)}.
     *
     * @return {@code true} if the popup was shown or was already showing prior
     *         to calling this method, {@code false} otherwise
     */
    public boolean tryShow() {
        if (isShowing()) {
            return true;
        }

        mPopup = new ListPopupWindow(mContext, null, mPopupStyleAttr, mPopupStyleRes);
        mPopup.setOnDismissListener(this);
        mPopup.setOnItemClickListener(this);
@@ -169,6 +178,7 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
        }
    }

    @Override
    public void onDismiss() {
        mPopup = null;
        mMenu.close();
@@ -190,6 +200,7 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
        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();