1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.wm.shell.bubbles; 18 19 import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE; 20 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; 21 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; 22 23 import android.content.Context; 24 import android.graphics.PointF; 25 import android.util.Log; 26 import android.view.MotionEvent; 27 import android.view.VelocityTracker; 28 import android.view.ViewConfiguration; 29 30 import androidx.annotation.Nullable; 31 32 /** 33 * Handles {@link MotionEvent}s for bubbles that begin in the nav bar area 34 */ 35 class BubblesNavBarMotionEventHandler { 36 private static final String TAG = 37 TAG_WITH_CLASS_NAME ? "BubblesNavBarMotionEventHandler" : TAG_BUBBLES; 38 private static final int VELOCITY_UNITS = 1000; 39 40 private final Runnable mOnInterceptTouch; 41 private final MotionEventListener mMotionEventListener; 42 private final int mTouchSlop; 43 private final BubblePositioner mPositioner; 44 private final PointF mTouchDown = new PointF(); 45 private boolean mTrackingTouches; 46 private boolean mInterceptingTouches; 47 @Nullable 48 private VelocityTracker mVelocityTracker; 49 BubblesNavBarMotionEventHandler(Context context, BubblePositioner positioner, Runnable onInterceptTouch, MotionEventListener motionEventListener)50 BubblesNavBarMotionEventHandler(Context context, BubblePositioner positioner, 51 Runnable onInterceptTouch, MotionEventListener motionEventListener) { 52 mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 53 mPositioner = positioner; 54 mOnInterceptTouch = onInterceptTouch; 55 mMotionEventListener = motionEventListener; 56 } 57 58 /** 59 * Handle {@link MotionEvent} and forward it to {@code motionEventListener} defined in 60 * constructor 61 * 62 * @return {@code true} if this {@link MotionEvent} is handled (it started in the gesture area) 63 */ onMotionEvent(MotionEvent motionEvent)64 public boolean onMotionEvent(MotionEvent motionEvent) { 65 float dx = motionEvent.getX() - mTouchDown.x; 66 float dy = motionEvent.getY() - mTouchDown.y; 67 68 switch (motionEvent.getAction()) { 69 case MotionEvent.ACTION_DOWN: 70 if (isInGestureRegion(motionEvent)) { 71 mTouchDown.set(motionEvent.getX(), motionEvent.getY()); 72 mMotionEventListener.onDown(motionEvent.getX(), motionEvent.getY()); 73 mTrackingTouches = true; 74 return true; 75 } 76 break; 77 case MotionEvent.ACTION_MOVE: 78 if (mTrackingTouches) { 79 if (!mInterceptingTouches && Math.hypot(dx, dy) > mTouchSlop) { 80 mInterceptingTouches = true; 81 mOnInterceptTouch.run(); 82 } 83 if (mInterceptingTouches) { 84 getVelocityTracker().addMovement(motionEvent); 85 mMotionEventListener.onMove(dx, dy); 86 } 87 return true; 88 } 89 break; 90 case MotionEvent.ACTION_CANCEL: 91 if (mTrackingTouches) { 92 mMotionEventListener.onCancel(); 93 finishTracking(); 94 return true; 95 } 96 break; 97 case MotionEvent.ACTION_UP: 98 if (mTrackingTouches) { 99 if (mInterceptingTouches) { 100 getVelocityTracker().computeCurrentVelocity(VELOCITY_UNITS); 101 mMotionEventListener.onUp(getVelocityTracker().getXVelocity(), 102 getVelocityTracker().getYVelocity()); 103 } 104 finishTracking(); 105 return true; 106 } 107 break; 108 } 109 return false; 110 } 111 isInGestureRegion(MotionEvent ev)112 private boolean isInGestureRegion(MotionEvent ev) { 113 // Only handles touch events beginning in navigation bar system gesture zone 114 if (mPositioner.getNavBarGestureZone().contains((int) ev.getX(), (int) ev.getY())) { 115 if (DEBUG_BUBBLE_GESTURE) { 116 Log.d(TAG, "handling touch y=" + ev.getY() 117 + " navBarGestureZone=" + mPositioner.getNavBarGestureZone()); 118 } 119 return true; 120 } 121 return false; 122 } 123 getVelocityTracker()124 private VelocityTracker getVelocityTracker() { 125 if (mVelocityTracker == null) { 126 mVelocityTracker = VelocityTracker.obtain(); 127 } 128 return mVelocityTracker; 129 } 130 finishTracking()131 private void finishTracking() { 132 mTouchDown.set(0, 0); 133 mTrackingTouches = false; 134 mInterceptingTouches = false; 135 if (mVelocityTracker != null) { 136 mVelocityTracker.recycle(); 137 mVelocityTracker = null; 138 } 139 } 140 141 /** 142 * Callback for receiving {@link MotionEvent} updates 143 */ 144 interface MotionEventListener { 145 /** 146 * Touch down action. 147 * 148 * @param x x coordinate 149 * @param y y coordinate 150 */ onDown(float x, float y)151 void onDown(float x, float y); 152 153 /** 154 * Move action. 155 * Reports distance from point reported in {@link #onDown(float, float)} 156 * 157 * @param dx distance moved on x-axis from starting point, in pixels 158 * @param dy distance moved on y-axis from starting point, in pixels 159 */ onMove(float dx, float dy)160 void onMove(float dx, float dy); 161 162 /** 163 * Touch up action. 164 * 165 * @param velX velocity of the move action on x axis 166 * @param velY velocity of the move actin on y axis 167 */ onUp(float velX, float velY)168 void onUp(float velX, float velY); 169 170 /** 171 * Motion action was cancelled. 172 */ onCancel()173 void onCancel(); 174 } 175 } 176