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.TAG_BUBBLES;
20 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
21 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
22 
23 import android.content.Context;
24 import android.graphics.PointF;
25 import android.view.MotionEvent;
26 import android.view.VelocityTracker;
27 import android.view.ViewConfiguration;
28 
29 import androidx.annotation.Nullable;
30 
31 import com.android.internal.protolog.common.ProtoLog;
32 
33 /**
34  * Handles {@link MotionEvent}s for bubbles that begin in the nav bar area
35  */
36 class BubblesNavBarMotionEventHandler {
37     private static final String TAG =
38             TAG_WITH_CLASS_NAME ? "BubblesNavBarMotionEventHandler" : TAG_BUBBLES;
39     private static final int VELOCITY_UNITS = 1000;
40 
41     private final Runnable mOnInterceptTouch;
42     private final MotionEventListener mMotionEventListener;
43     private final int mTouchSlop;
44     private final BubblePositioner mPositioner;
45     private final PointF mTouchDown = new PointF();
46     private boolean mTrackingTouches;
47     private boolean mInterceptingTouches;
48     @Nullable
49     private VelocityTracker mVelocityTracker;
50 
BubblesNavBarMotionEventHandler(Context context, BubblePositioner positioner, Runnable onInterceptTouch, MotionEventListener motionEventListener)51     BubblesNavBarMotionEventHandler(Context context, BubblePositioner positioner,
52             Runnable onInterceptTouch, MotionEventListener motionEventListener) {
53         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
54         mPositioner = positioner;
55         mOnInterceptTouch = onInterceptTouch;
56         mMotionEventListener = motionEventListener;
57     }
58 
59     /**
60      * Handle {@link MotionEvent} and forward it to {@code motionEventListener} defined in
61      * constructor
62      *
63      * @return {@code true} if this {@link MotionEvent} is handled (it started in the gesture area)
64      */
onMotionEvent(MotionEvent motionEvent)65     public boolean onMotionEvent(MotionEvent motionEvent) {
66         float dx = motionEvent.getX() - mTouchDown.x;
67         float dy = motionEvent.getY() - mTouchDown.y;
68 
69         switch (motionEvent.getAction()) {
70             case MotionEvent.ACTION_DOWN:
71                 if (isInGestureRegion(motionEvent)) {
72                     mTouchDown.set(motionEvent.getX(), motionEvent.getY());
73                     mMotionEventListener.onDown(motionEvent.getX(), motionEvent.getY());
74                     mTrackingTouches = true;
75                     return true;
76                 }
77                 break;
78             case MotionEvent.ACTION_MOVE:
79                 if (mTrackingTouches) {
80                     if (!mInterceptingTouches && Math.hypot(dx, dy) > mTouchSlop) {
81                         mInterceptingTouches = true;
82                         mOnInterceptTouch.run();
83                     }
84                     if (mInterceptingTouches) {
85                         getVelocityTracker().addMovement(motionEvent);
86                         mMotionEventListener.onMove(dx, dy);
87                     }
88                     return true;
89                 }
90                 break;
91             case MotionEvent.ACTION_CANCEL:
92                 if (mTrackingTouches) {
93                     mMotionEventListener.onCancel();
94                     finishTracking();
95                     return true;
96                 }
97                 break;
98             case MotionEvent.ACTION_UP:
99                 if (mTrackingTouches) {
100                     if (mInterceptingTouches) {
101                         getVelocityTracker().computeCurrentVelocity(VELOCITY_UNITS);
102                         mMotionEventListener.onUp(getVelocityTracker().getXVelocity(),
103                                 getVelocityTracker().getYVelocity());
104                     }
105                     finishTracking();
106                     return true;
107                 }
108                 break;
109         }
110         return false;
111     }
112 
isInGestureRegion(MotionEvent ev)113     private boolean isInGestureRegion(MotionEvent ev) {
114         // Only handles touch events beginning in navigation bar system gesture zone
115         if (mPositioner.getNavBarGestureZone().contains((int) ev.getX(), (int) ev.getY())) {
116             ProtoLog.d(WM_SHELL_BUBBLES, "handling touch x=%d y=%d navBarGestureZone=%s",
117                     (int) ev.getX(), (int) ev.getY(), mPositioner.getNavBarGestureZone());
118             return true;
119         }
120         return false;
121     }
122 
getVelocityTracker()123     private VelocityTracker getVelocityTracker() {
124         if (mVelocityTracker == null) {
125             mVelocityTracker = VelocityTracker.obtain();
126         }
127         return mVelocityTracker;
128     }
129 
finishTracking()130     private void finishTracking() {
131         mTouchDown.set(0, 0);
132         mTrackingTouches = false;
133         mInterceptingTouches = false;
134         if (mVelocityTracker != null) {
135             mVelocityTracker.recycle();
136             mVelocityTracker = null;
137         }
138     }
139 
140     /**
141      * Callback for receiving {@link MotionEvent} updates
142      */
143     interface MotionEventListener {
144         /**
145          * Touch down action.
146          *
147          * @param x x coordinate
148          * @param y y coordinate
149          */
onDown(float x, float y)150         void onDown(float x, float y);
151 
152         /**
153          * Move action.
154          * Reports distance from point reported in {@link #onDown(float, float)}
155          *
156          * @param dx distance moved on x-axis from starting point, in pixels
157          * @param dy distance moved on y-axis from starting point, in pixels
158          */
onMove(float dx, float dy)159         void onMove(float dx, float dy);
160 
161         /**
162          * Touch up action.
163          *
164          * @param velX velocity of the move action on x axis
165          * @param velY velocity of the move actin on y axis
166          */
onUp(float velX, float velY)167         void onUp(float velX, float velY);
168 
169         /**
170          * Motion action was cancelled.
171          */
onCancel()172         void onCancel();
173     }
174 }
175