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

Commit a2e88ef8 authored by Alan Viverette's avatar Alan Viverette Committed by Android (Google) Code Review
Browse files

Merge "Move forwarding code to ListPopupWindow, add drag-to-open in Spinner" into klp-dev

parents 5feb0ad1 ca6a3611
Loading
Loading
Loading
Loading
+142 −27
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.View.OnTouchListener;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.animation.AccelerateDecelerateInterpolator;
@@ -960,33 +961,6 @@ public class ListPopupWindow {
        return false;
    }

    /**
     * Receives motion events forwarded from a source view. This is used
     * internally to implement support for drag-to-open.
     *
     * @param src view from which the event was forwarded
     * @param srcEvent forwarded motion event in source-local coordinates
     * @param activePointerId id of the pointer that activated forwarding
     * @return whether the event was handled
     * @hide
     */
    public boolean onForwardedEvent(View src, MotionEvent srcEvent, int activePointerId) {
        final DropDownListView dst = mDropDownList;
        if (dst == null || !dst.isShown()) {
            return false;
        }

        // Convert event to local coordinates.
        final MotionEvent dstEvent = MotionEvent.obtainNoHistory(srcEvent);
        src.toGlobalMotionEvent(dstEvent);
        dst.toLocalMotionEvent(dstEvent);

        // Forward converted event, then recycle it.
        final boolean handled = dst.onForwardedEvent(dstEvent, activePointerId);
        dstEvent.recycle();
        return handled;
    }

    /**
     * <p>Builds the popup window's content and returns the height the popup
     * should have. Returns -1 when the content already exists.</p>
@@ -1154,6 +1128,147 @@ public class ListPopupWindow {
        return listContent + otherHeights;
    }

    /**
     * Abstract class that forwards touch events to a {@link ListPopupWindow}.
     *
     * @hide
     */
    public static abstract class ForwardingListener implements View.OnTouchListener {
        /** Scaled touch slop, used for detecting movement outside bounds. */
        private final float mScaledTouchSlop;

        /** Whether this listener is currently forwarding touch events. */
        private boolean mForwarding;

        /** The id of the first pointer down in the current event stream. */
        private int mActivePointerId;

        public ForwardingListener(Context context) {
            mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        }

        /**
         * Returns the popup to which this listener is forwarding events.
         * <p>
         * Override this to return the correct popup. If the popup is displayed
         * asynchronously, you may also need to override
         * {@link #onForwardingStopped} to prevent premature cancelation of
         * forwarding.
         *
         * @return the popup to which this listener is forwarding events
         */
        public abstract ListPopupWindow getPopup();

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            final boolean wasForwarding = mForwarding;
            final boolean forwarding;
            if (wasForwarding) {
                forwarding = onTouchForwarded(v, event) || !onForwardingStopped();
            } else {
                forwarding = onTouchObserved(v, event) && onForwardingStarted();
            }

            mForwarding = forwarding;
            return forwarding || wasForwarding;
        }

        /**
         * Called when forwarding would like to start.
         * <p>
         * By default, this will show the popup returned by {@link #getPopup()}.
         * It may be overridden to perform another action, like clicking the
         * source view or preparing the popup before showing it.
         *
         * @return true to start forwarding, false otherwise
         */
        public boolean onForwardingStarted() {
            final ListPopupWindow popup = getPopup();
            if (popup != null && !popup.isShowing()) {
                popup.show();
            }
            return true;
        }

        /**
         * Called when forwarding would like to stop.
         * <p>
         * By default, this will dismiss the popup returned by
         * {@link #getPopup()}. It may be overridden to perform some other
         * action.
         *
         * @return true to stop forwarding, false otherwise
         */
        public boolean onForwardingStopped() {
            final ListPopupWindow popup = getPopup();
            if (popup != null && popup.isShowing()) {
                popup.dismiss();
            }
            return true;
        }

        /**
         * Observes motion events and determines when to start forwarding.
         *
         * @param src view from which the event originated
         * @param srcEvent motion event in source view coordinates
         * @return true to start forwarding motion events, false otherwise
         */
        private boolean onTouchObserved(View src, MotionEvent srcEvent) {
            if (!src.isEnabled()) {
                return false;
            }

            // The first pointer down is always the active pointer.
            final int actionMasked = srcEvent.getActionMasked();
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                mActivePointerId = srcEvent.getPointerId(0);
            }

            final int activePointerIndex = srcEvent.findPointerIndex(mActivePointerId);
            if (activePointerIndex >= 0) {
                final float x = srcEvent.getX(activePointerIndex);
                final float y = srcEvent.getY(activePointerIndex);
                if (!src.pointInView(x, y, mScaledTouchSlop)) {
                    // The pointer has moved outside of the view.
                    return true;
                }
            }

            return false;
        }

        /**
         * Handled forwarded motion events and determines when to stop
         * forwarding.
         *
         * @param src view from which the event originated
         * @param srcEvent motion event in source view coordinates
         * @return true to continue forwarding motion events, false to cancel
         */
        private boolean onTouchForwarded(View src, MotionEvent srcEvent) {
            final ListPopupWindow popup = getPopup();
            if (popup == null || !popup.isShowing()) {
                return false;
            }

            final DropDownListView dst = popup.mDropDownList;
            if (dst == null || !dst.isShown()) {
                return false;
            }

            // Convert event to destination-local coordinates.
            final MotionEvent dstEvent = MotionEvent.obtainNoHistory(srcEvent);
            src.toGlobalMotionEvent(dstEvent);
            dst.toLocalMotionEvent(dstEvent);

            // Forward converted event to destination view, then recycle it.
            final boolean handled = dst.onForwardedEvent(dstEvent, mActivePointerId);
            dstEvent.recycle();
            return handled;
        }
    }

    /**
     * <p>Wrapper class for a ListView. This wrapper can hijack the focus to
     * make sure the list uses the appropriate drawables and states when
+30 −2
Original line number Diff line number Diff line
@@ -30,12 +30,14 @@ import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ListPopupWindow.ForwardingListener;
import android.widget.PopupWindow.OnDismissListener;


@@ -77,6 +79,9 @@ public class Spinner extends AbsSpinner implements OnClickListener {
     */
    private static final int MODE_THEME = -1;

    /** Forwarding listener used to implement drag-to-open. */
    private ForwardingListener mForwardingListener;

    private SpinnerPopup mPopup;
    private DropDownAdapter mTempAdapter;
    int mDropDownWidth;
@@ -173,7 +178,7 @@ public class Spinner extends AbsSpinner implements OnClickListener {
        }

        case MODE_DROPDOWN: {
            DropdownPopup popup = new DropdownPopup(context, attrs, defStyle);
            final DropdownPopup popup = new DropdownPopup(context, attrs, defStyle);

            mDropDownWidth = a.getLayoutDimension(
                    com.android.internal.R.styleable.Spinner_dropDownWidth,
@@ -193,6 +198,20 @@ public class Spinner extends AbsSpinner implements OnClickListener {
            }

            mPopup = popup;
            mForwardingListener = new ForwardingListener(context) {
                @Override
                public ListPopupWindow getPopup() {
                    return popup;
                }

                @Override
                public boolean onForwardingStarted() {
                    if (!mPopup.isShowing()) {
                        mPopup.show(getTextDirection(), getTextAlignment());
                    }
                    return true;
                }
            };
            break;
        }
        }
@@ -448,6 +467,15 @@ public class Spinner extends AbsSpinner implements OnClickListener {
        super.setOnItemClickListener(l);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mForwardingListener != null && mForwardingListener.onTouch(this, event)) {
            return true;
        }

        return super.onTouchEvent(event);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+32 −53
Original line number Diff line number Diff line
@@ -32,6 +32,8 @@ import android.view.View.MeasureSpec;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ListPopupWindow;
import android.widget.ListPopupWindow.ForwardingListener;

import com.android.internal.view.ActionBarPolicy;
import com.android.internal.view.menu.ActionMenuView.ActionMenuChildView;
@@ -562,7 +564,36 @@ public class ActionMenuPresenter extends BaseMenuPresenter
            setFocusable(true);
            setVisibility(VISIBLE);
            setEnabled(true);
            setOnTouchListener(new OverflowForwardListener(context));

            setOnTouchListener(new ForwardingListener(context) {
                @Override
                public ListPopupWindow getPopup() {
                    if (mOverflowPopup == null) {
                        return null;
                    }

                    return mOverflowPopup.getPopup();
                }

                @Override
                public boolean onForwardingStarted() {
                    showOverflowMenu();
                    return true;
                }

                @Override
                public boolean onForwardingStopped() {
                    // Displaying the popup occurs asynchronously, so wait for
                    // the runnable to finish before deciding whether to stop
                    // forwarding.
                    if (mPostedOpenRunnable != null) {
                        return false;
                    }

                    hideOverflowMenu();
                    return true;
                }
            });
        }

        @Override
@@ -687,56 +718,4 @@ public class ActionMenuPresenter extends BaseMenuPresenter
            mPostedOpenRunnable = null;
        }
    }

    private class OverflowForwardListener extends TouchForwardingListener {
        /** Scaled touch slop, used for detecting movement outside bounds. */
        private final float mScaledTouchSlop;

        private int mActivePointerId = MotionEvent.INVALID_POINTER_ID;

        public OverflowForwardListener(Context context) {
            mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        }

        @Override
        public boolean onTouchObserved(View src, MotionEvent srcEvent) {
            if (!src.isEnabled()) {
                return false;
            }

            // Always start forwarding events when the source view is touched.
            mActivePointerId = srcEvent.getPointerId(0);
            src.performClick();
            return true;
        }

        @Override
        public boolean onTouchForwarded(View src, MotionEvent srcEvent) {
            final OverflowPopup popup = mOverflowPopup;
            if (popup != null && popup.isShowing()) {
                final int activePointerId = mActivePointerId;
                if (activePointerId != MotionEvent.INVALID_POINTER_ID && src.isEnabled()
                        && popup.forwardMotionEvent(src, srcEvent, activePointerId)) {
                    // Handled the motion event, continue forwarding.
                    return true;
                }

                final int activePointerIndex = srcEvent.findPointerIndex(activePointerId);
                if (activePointerIndex >= 0) {
                    final float x = srcEvent.getX(activePointerIndex);
                    final float y = srcEvent.getY(activePointerIndex);
                    if (src.pointInView(x, y, mScaledTouchSlop)) {
                        // The user is touching the source view. Cancel
                        // forwarding, but don't dismiss the popup.
                        return false;
                    }
                }

                popup.dismiss();
            }

            // Cancel forwarding.
            return false;
        }
    }
}
+4 −16
Original line number Diff line number Diff line
@@ -108,6 +108,10 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
        }
    }

    public ListPopupWindow getPopup() {
        return mPopup;
    }

    public boolean tryShow() {
        mPopup = new ListPopupWindow(mContext, null, com.android.internal.R.attr.popupMenuStyle);
        mPopup.setOnDismissListener(this);
@@ -159,22 +163,6 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
        return mPopup != null && mPopup.isShowing();
    }

    /**
     * Forwards motion events from a source view to the popup window.
     *
     * @param src view from which the event was forwarded
     * @param event forwarded motion event in source-local coordinates
     * @param activePointerId id of the pointer that activated forwarding
     * @return whether the event was handled
     */
    public boolean forwardMotionEvent(View src, MotionEvent event, int activePointerId) {
        if (mPopup == null || !mPopup.isShowing()) {
            return false;
        }

        return mPopup.onForwardedEvent(src, event, activePointerId);
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        MenuAdapter adapter = mAdapter;
+0 −72
Original line number Diff line number Diff line
/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.view.menu;

import android.view.MotionEvent;
import android.view.View;

/**
 * Touch listener used to intercept touches and forward them out of a view.
 */
abstract class TouchForwardingListener implements View.OnTouchListener {
    /** Whether this listener is currently forwarding touch events. */
    private boolean mForwarding;

    @Override
    public boolean onTouch(View v, MotionEvent ev) {
        final int actionMasked = ev.getActionMasked();

        if (mForwarding) {
            // Rejecting the event or ending the stream stops forwarding.
            if (!onTouchForwarded(v, ev) || actionMasked == MotionEvent.ACTION_UP
                    || actionMasked == MotionEvent.ACTION_CANCEL) {
                stopForwarding();
            }
        } else {
            if (onTouchObserved(v, ev)) {
                startForwarding();
            }
        }

        return mForwarding;
    }

    public void startForwarding() {
        mForwarding = true;
    }

    public void stopForwarding() {
        mForwarding = false;
    }

    /**
     * Attempts to start forwarding motion events.
     *
     * @param v The view that triggered forwarding.
     * @return True to start forwarding motion events, or false to cancel.
     */
    public abstract boolean onTouchObserved(View v, MotionEvent ev);

    /**
     * Handles forwarded motion events.
     *
     * @param v The view from which the event was forwarded.
     * @param ev The forwarded motion event.
     * @return True to continue forwarding motion events, or false to cancel.
     */
    public abstract boolean onTouchForwarded(View v, MotionEvent ev);
}