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

Commit 80e72700 authored by Alan Viverette's avatar Alan Viverette
Browse files

Implement drag to open overflow menu, lift to select

BUG: 9437139
Change-Id: Iaa962453ba1b2c739a04b6b1be4f6de1fb2fa752
parent d8501485
Loading
Loading
Loading
Loading
+51 −3
Original line number Diff line number Diff line
@@ -16,9 +16,6 @@

package com.android.internal.view.menu;

import com.android.internal.view.ActionBarPolicy;
import com.android.internal.view.menu.ActionMenuView.ActionMenuChildView;

import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -27,11 +24,18 @@ import android.os.Parcelable;
import android.util.SparseBooleanArray;
import android.view.ActionProvider;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ImageButton;
import android.widget.ListPopupWindow;

import com.android.internal.view.ActionBarPolicy;
import com.android.internal.view.menu.ActionMenuView.ActionMenuChildView;

import java.util.ArrayList;

@@ -559,6 +563,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter
            setFocusable(true);
            setVisibility(VISIBLE);
            setEnabled(true);
            setOnTouchListener(new OverflowForwardListener(context));
        }

        @Override
@@ -572,10 +577,12 @@ public class ActionMenuPresenter extends BaseMenuPresenter
            return true;
        }

        @Override
        public boolean needsDividerBefore() {
            return false;
        }

        @Override
        public boolean needsDividerAfter() {
            return false;
        }
@@ -675,4 +682,45 @@ 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 v, MotionEvent ev) {
            if (ev.getActionMasked() == MotionEvent.ACTION_MOVE && v.isEnabled()
                    && !v.pointInView(ev.getX(), ev.getY(), mScaledTouchSlop)) {
                mActivePointerId = ev.getPointerId(0);
                v.performClick();
                return true;
            }

            return false;
        }

        @Override
        public boolean onTouchForwarded(View v, MotionEvent ev) {
            if (!v.isEnabled() || mOverflowPopup == null || !mOverflowPopup.isShowing()) {
                return false;
            }

            if (mActivePointerId != MotionEvent.INVALID_POINTER_ID) {
                if (mOverflowPopup.forwardMotionEvent(v, ev, mActivePointerId)) {
                    return true;
                }

                mActivePointerId = MotionEvent.INVALID_POINTER_ID;
            }

            mOverflowPopup.dismiss();
            return false;
        }
    }
}
+67 −0
Original line number Diff line number Diff line
@@ -22,10 +22,12 @@ import android.os.Parcelable;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.FrameLayout;
@@ -46,6 +48,8 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On

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

    private final int[] mTempLocation = new int[2];

    private final Context mContext;
    private final LayoutInflater mInflater;
    private final MenuBuilder mMenu;
@@ -158,6 +162,69 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
        return mPopup != null && mPopup.isShowing();
    }

    public boolean forwardMotionEvent(View v, MotionEvent ev, int activePointerId) {
        if (mPopup == null || !mPopup.isShowing()) {
            return false;
        }

        final AbsListView dstView = mPopup.getListView();
        if (dstView == null || !dstView.isShown()) {
            return false;
        }

        boolean cancelForwarding = false;
        final int actionMasked = ev.getActionMasked();
        switch (actionMasked) {
            case MotionEvent.ACTION_CANCEL:
                cancelForwarding = true;
                break;
            case MotionEvent.ACTION_UP:
                cancelForwarding = true;
                // $FALL-THROUGH$
            case MotionEvent.ACTION_MOVE:
                final int activeIndex = ev.findPointerIndex(activePointerId);
                if (activeIndex < 0) {
                    return false;
                }

                final int[] location = mTempLocation;
                int x = (int) ev.getX(activeIndex);
                int y = (int) ev.getY(activeIndex);

                // Convert to global coordinates.
                v.getLocationOnScreen(location);
                x += location[0];
                y += location[1];

                // Convert to local coordinates.
                dstView.getLocationOnScreen(location);
                x -= location[0];
                y -= location[1];

                final int position = dstView.pointToPosition(x, y);
                if (position >= 0) {
                    final int childCount = dstView.getChildCount();
                    final int firstVisiblePosition = dstView.getFirstVisiblePosition();
                    final int index = position - firstVisiblePosition;
                    if (index < childCount) {
                        final View child = dstView.getChildAt(index);
                        if (actionMasked == MotionEvent.ACTION_UP) {
                            // Touch ended, click highlighted item.
                            final long id = dstView.getItemIdAtPosition(position);
                            dstView.performItemClick(child, position, id);
                        } else if (actionMasked == MotionEvent.ACTION_MOVE) {
                            // TODO: Highlight touched item, activate after
                            // long-hover. Consider forwarding events as HOVER and
                            // letting ListView handle this.
                        }
                    }
                }
                break;
        }

        return true;
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        MenuAdapter adapter = mAdapter;
+72 −0
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);
}