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

Commit 4afd62b1 authored by Adam Powell's avatar Adam Powell
Browse files

Add an API to listen for window attach/detach events on a View.

Fix bug 3312949 - inconsistent state in MenuPopupHelper

Change-Id: Ie802ada3f8de4cf71c92fcc7c6abce9ba85e7b75
parent 10d63fad
Loading
Loading
Loading
Loading
+60 −0
Original line number Original line Diff line number Diff line
@@ -220070,6 +220070,19 @@
<parameter name="focusableMode" type="int">
<parameter name="focusableMode" type="int">
</parameter>
</parameter>
</method>
</method>
<method name="addOnAttachStateChangeListener"
 return="void"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="listener" type="android.view.View.OnAttachStateChangeListener">
</parameter>
</method>
<method name="addOnLayoutChangeListener"
<method name="addOnLayoutChangeListener"
 return="void"
 return="void"
 abstract="false"
 abstract="false"
@@ -223073,6 +223086,19 @@
<parameter name="action" type="java.lang.Runnable">
<parameter name="action" type="java.lang.Runnable">
</parameter>
</parameter>
</method>
</method>
<method name="removeOnAttachStateChangeListener"
 return="void"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="listener" type="android.view.View.OnAttachStateChangeListener">
</parameter>
</method>
<method name="removeOnLayoutChangeListener"
<method name="removeOnLayoutChangeListener"
 return="void"
 return="void"
 abstract="false"
 abstract="false"
@@ -225394,6 +225420,40 @@
>
>
</field>
</field>
</class>
</class>
<interface name="View.OnAttachStateChangeListener"
 abstract="true"
 static="true"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<method name="onViewAttachedToWindow"
 return="void"
 abstract="true"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="v" type="android.view.View">
</parameter>
</method>
<method name="onViewDetachedFromWindow"
 return="void"
 abstract="true"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="v" type="android.view.View">
</parameter>
</method>
</interface>
<interface name="View.OnClickListener"
<interface name="View.OnClickListener"
 abstract="true"
 abstract="true"
 static="true"
 static="true"
+79 −0
Original line number Original line Diff line number Diff line
@@ -74,6 +74,7 @@ import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Arrays;
import java.util.WeakHashMap;
import java.util.WeakHashMap;
import java.util.concurrent.CopyOnWriteArrayList;


/**
/**
 * <p>
 * <p>
@@ -2098,6 +2099,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
     */
     */
    private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;
    private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;


    /**
     * Listeners for attach events.
     */
    private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;

    /**
    /**
     * Listener used to dispatch click events.
     * Listener used to dispatch click events.
     * This field should be made private, so it is hidden from the SDK.
     * This field should be made private, so it is hidden from the SDK.
@@ -2995,6 +3001,37 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
        mOnLayoutChangeListeners.remove(listener);
        mOnLayoutChangeListeners.remove(listener);
    }
    }


    /**
     * Add a listener for attach state changes.
     *
     * This listener will be called whenever this view is attached or detached
     * from a window. Remove the listener using
     * {@link #removeOnAttachStateChangeListener(OnAttachStateChangeListener)}.
     *
     * @param listener Listener to attach
     * @see #removeOnAttachStateChangeListener(OnAttachStateChangeListener)
     */
    public void addOnAttachStateChangeListener(OnAttachStateChangeListener listener) {
        if (mOnAttachStateChangeListeners == null) {
            mOnAttachStateChangeListeners = new CopyOnWriteArrayList<OnAttachStateChangeListener>();
        }
        mOnAttachStateChangeListeners.add(listener);
    }

    /**
     * Remove a listener for attach state changes. The listener will receive no further
     * notification of window attach/detach events.
     *
     * @param listener Listener to remove
     * @see #addOnAttachStateChangeListener(OnAttachStateChangeListener)
     */
    public void removeOnAttachStateChangeListener(OnAttachStateChangeListener listener) {
        if (mOnAttachStateChangeListeners == null) {
            return;
        }
        mOnAttachStateChangeListeners.remove(listener);
    }

    /**
    /**
     * Returns the focus-change callback registered for this view.
     * Returns the focus-change callback registered for this view.
     *
     *
@@ -7953,6 +7990,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
        }
        }
        performCollectViewAttributes(visibility);
        performCollectViewAttributes(visibility);
        onAttachedToWindow();
        onAttachedToWindow();

        final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
                mOnAttachStateChangeListeners;
        if (listeners != null && listeners.size() > 0) {
            // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
            // perform the dispatching. The iterator is a safe guard against listeners that
            // could mutate the list by calling the various add/remove methods. This prevents
            // the array from being modified while we iterate it.
            for (OnAttachStateChangeListener listener : listeners) {
                listener.onViewAttachedToWindow(this);
            }
        }

        int vis = info.mWindowVisibility;
        int vis = info.mWindowVisibility;
        if (vis != GONE) {
        if (vis != GONE) {
            onWindowVisibilityChanged(vis);
            onWindowVisibilityChanged(vis);
@@ -7974,6 +8024,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility


        onDetachedFromWindow();
        onDetachedFromWindow();


        final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
                mOnAttachStateChangeListeners;
        if (listeners != null && listeners.size() > 0) {
            // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
            // perform the dispatching. The iterator is a safe guard against listeners that
            // could mutate the list by calling the various add/remove methods. This prevents
            // the array from being modified while we iterate it.
            for (OnAttachStateChangeListener listener : listeners) {
                listener.onViewDetachedFromWindow(this);
            }
        }

        if ((mPrivateFlags & SCROLL_CONTAINER_ADDED) != 0) {
        if ((mPrivateFlags & SCROLL_CONTAINER_ADDED) != 0) {
            mAttachInfo.mScrollContainers.remove(this);
            mAttachInfo.mScrollContainers.remove(this);
            mPrivateFlags &= ~SCROLL_CONTAINER_ADDED;
            mPrivateFlags &= ~SCROLL_CONTAINER_ADDED;
@@ -11767,6 +11829,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
        public void onSystemUiVisibilityChange(int visibility);
        public void onSystemUiVisibilityChange(int visibility);
    }
    }


    /**
     * Interface definition for a callback to be invoked when this view is attached
     * or detached from its window.
     */
    public interface OnAttachStateChangeListener {
        /**
         * Called when the view is attached to a window.
         * @param v The view that was attached
         */
        public void onViewAttachedToWindow(View v);
        /**
         * Called when the view is detached from a window.
         * @param v The view that was detached
         */
        public void onViewDetachedFromWindow(View v);
    }

    private final class UnsetPressedState implements Runnable {
    private final class UnsetPressedState implements Runnable {
        public void run() {
        public void run() {
            setPressed(false);
            setPressed(false);
+30 −21
Original line number Original line Diff line number Diff line
@@ -36,14 +36,15 @@ import java.lang.ref.WeakReference;
 * @hide
 * @hide
 */
 */
public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.OnKeyListener,
public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.OnKeyListener,
        ViewTreeObserver.OnGlobalLayoutListener, PopupWindow.OnDismissListener {
        ViewTreeObserver.OnGlobalLayoutListener, PopupWindow.OnDismissListener,
        View.OnAttachStateChangeListener {
    private static final String TAG = "MenuPopupHelper";
    private static final String TAG = "MenuPopupHelper";


    private Context mContext;
    private Context mContext;
    private ListPopupWindow mPopup;
    private ListPopupWindow mPopup;
    private MenuBuilder mMenu;
    private MenuBuilder mMenu;
    private int mPopupMaxWidth;
    private int mPopupMaxWidth;
    private WeakReference<View> mAnchorView;
    private View mAnchorView;
    private boolean mOverflowOnly;
    private boolean mOverflowOnly;
    private ViewTreeObserver mTreeObserver;
    private ViewTreeObserver mTreeObserver;


@@ -66,13 +67,11 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
        final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        mPopupMaxWidth = metrics.widthPixels / 2;
        mPopupMaxWidth = metrics.widthPixels / 2;


        if (anchorView != null) {
        mAnchorView = anchorView;
            mAnchorView = new WeakReference<View>(anchorView);
        }
    }
    }


    public void setAnchorView(View anchor) {
    public void setAnchorView(View anchor) {
        mAnchorView = new WeakReference<View>(anchor);
        mAnchorView = anchor;
    }
    }


    public void show() {
    public void show() {
@@ -92,19 +91,19 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
        mPopup.setAdapter(adapter);
        mPopup.setAdapter(adapter);
        mPopup.setModal(true);
        mPopup.setModal(true);


        View anchor = mAnchorView != null ? mAnchorView.get() : null;
        View anchor = mAnchorView;
        if (anchor == null && mMenu instanceof SubMenuBuilder) {
        if (anchor == null && mMenu instanceof SubMenuBuilder) {
            SubMenuBuilder subMenu = (SubMenuBuilder) mMenu;
            SubMenuBuilder subMenu = (SubMenuBuilder) mMenu;
            final MenuItemImpl itemImpl = (MenuItemImpl) subMenu.getItem();
            final MenuItemImpl itemImpl = (MenuItemImpl) subMenu.getItem();
            anchor = itemImpl.getItemView(MenuBuilder.TYPE_ACTION_BUTTON, null);
            anchor = itemImpl.getItemView(MenuBuilder.TYPE_ACTION_BUTTON, null);
            mAnchorView = new WeakReference<View>(anchor);
            mAnchorView = anchor;
        }
        }


        if (anchor != null) {
        if (anchor != null) {
            if (mTreeObserver == null) {
            final boolean addGlobalListener = mTreeObserver == null;
                mTreeObserver = anchor.getViewTreeObserver();
            mTreeObserver = anchor.getViewTreeObserver(); // Refresh to latest
                mTreeObserver.addOnGlobalLayoutListener(this);
            if (addGlobalListener) mTreeObserver.addOnGlobalLayoutListener(this);
            }
            anchor.addOnAttachStateChangeListener(this);
            mPopup.setAnchorView(anchor);
            mPopup.setAnchorView(anchor);
        } else {
        } else {
            return false;
            return false;
@@ -125,11 +124,13 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On


    public void onDismiss() {
    public void onDismiss() {
        mPopup = null;
        mPopup = null;
        if (mTreeObserver != null && mTreeObserver.isAlive()) {
        if (mTreeObserver != null) {
            if (!mTreeObserver.isAlive()) mTreeObserver = mAnchorView.getViewTreeObserver();
            mTreeObserver.removeGlobalOnLayoutListener(this);
            mTreeObserver.removeGlobalOnLayoutListener(this);
        }
            mTreeObserver = null;
            mTreeObserver = null;
        }
        }
        mAnchorView.removeOnAttachStateChangeListener(this);
    }


    public boolean isShowing() {
    public boolean isShowing() {
        return mPopup != null && mPopup.isShowing();
        return mPopup != null && mPopup.isShowing();
@@ -187,13 +188,8 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On


    @Override
    @Override
    public void onGlobalLayout() {
    public void onGlobalLayout() {
        if (!isShowing()) {
        if (isShowing()) {
            if (mTreeObserver.isAlive()) {
            final View anchor = mAnchorView;
                mTreeObserver.removeGlobalOnLayoutListener(this);
            }
            mTreeObserver = null;
        } else {
            final View anchor = mAnchorView != null ? mAnchorView.get() : null;
            if (anchor == null || !anchor.isShown()) {
            if (anchor == null || !anchor.isShown()) {
                dismiss();
                dismiss();
            } else if (isShowing()) {
            } else if (isShowing()) {
@@ -202,4 +198,17 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
            }
            }
        }
        }
    }
    }

    @Override
    public void onViewAttachedToWindow(View v) {
    }

    @Override
    public void onViewDetachedFromWindow(View v) {
        if (mTreeObserver != null) {
            if (!mTreeObserver.isAlive()) mTreeObserver = v.getViewTreeObserver();
            mTreeObserver.removeGlobalOnLayoutListener(this);
        }
        v.removeOnAttachStateChangeListener(this);
    }
}
}