/* * Copyright (C) 2022 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.wm.shell.bubbles; import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import android.content.Context; import android.graphics.PointF; import android.util.Log; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.ViewConfiguration; import androidx.annotation.Nullable; /** * Handles {@link MotionEvent}s for bubbles that begin in the nav bar area */ class BubblesNavBarMotionEventHandler { private static final String TAG = TAG_WITH_CLASS_NAME ? "BubblesNavBarMotionEventHandler" : TAG_BUBBLES; private static final int VELOCITY_UNITS = 1000; private final Runnable mOnInterceptTouch; private final MotionEventListener mMotionEventListener; private final int mTouchSlop; private final BubblePositioner mPositioner; private final PointF mTouchDown = new PointF(); private boolean mTrackingTouches; private boolean mInterceptingTouches; @Nullable private VelocityTracker mVelocityTracker; BubblesNavBarMotionEventHandler(Context context, BubblePositioner positioner, Runnable onInterceptTouch, MotionEventListener motionEventListener) { mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); mPositioner = positioner; mOnInterceptTouch = onInterceptTouch; mMotionEventListener = motionEventListener; } /** * Handle {@link MotionEvent} and forward it to {@code motionEventListener} defined in * constructor * * @return {@code true} if this {@link MotionEvent} is handled (it started in the gesture area) */ public boolean onMotionEvent(MotionEvent motionEvent) { float dx = motionEvent.getX() - mTouchDown.x; float dy = motionEvent.getY() - mTouchDown.y; switch (motionEvent.getAction()) { case MotionEvent.ACTION_DOWN: if (isInGestureRegion(motionEvent)) { mTouchDown.set(motionEvent.getX(), motionEvent.getY()); mMotionEventListener.onDown(motionEvent.getX(), motionEvent.getY()); mTrackingTouches = true; return true; } break; case MotionEvent.ACTION_MOVE: if (mTrackingTouches) { if (!mInterceptingTouches && Math.hypot(dx, dy) > mTouchSlop) { mInterceptingTouches = true; mOnInterceptTouch.run(); } if (mInterceptingTouches) { getVelocityTracker().addMovement(motionEvent); mMotionEventListener.onMove(dx, dy); } return true; } break; case MotionEvent.ACTION_CANCEL: if (mTrackingTouches) { mMotionEventListener.onCancel(); finishTracking(); return true; } break; case MotionEvent.ACTION_UP: if (mTrackingTouches) { if (mInterceptingTouches) { getVelocityTracker().computeCurrentVelocity(VELOCITY_UNITS); mMotionEventListener.onUp(getVelocityTracker().getXVelocity(), getVelocityTracker().getYVelocity()); } finishTracking(); return true; } break; } return false; } private boolean isInGestureRegion(MotionEvent ev) { // Only handles touch events beginning in navigation bar system gesture zone if (mPositioner.getNavBarGestureZone().contains((int) ev.getX(), (int) ev.getY())) { if (DEBUG_BUBBLE_GESTURE) { Log.d(TAG, "handling touch y=" + ev.getY() + " navBarGestureZone=" + mPositioner.getNavBarGestureZone()); } return true; } return false; } private VelocityTracker getVelocityTracker() { if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } return mVelocityTracker; } private void finishTracking() { mTouchDown.set(0, 0); mTrackingTouches = false; mInterceptingTouches = false; if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } } /** * Callback for receiving {@link MotionEvent} updates */ interface MotionEventListener { /** * Touch down action. * * @param x x coordinate * @param y y coordinate */ void onDown(float x, float y); /** * Move action. * Reports distance from point reported in {@link #onDown(float, float)} * * @param dx distance moved on x-axis from starting point, in pixels * @param dy distance moved on y-axis from starting point, in pixels */ void onMove(float dx, float dy); /** * Touch up action. * * @param velX velocity of the move action on x axis * @param velY velocity of the move actin on y axis */ void onUp(float velX, float velY); /** * Motion action was cancelled. */ void onCancel(); } }