• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 android.graphics.PointF
20 import android.os.Handler
21 import android.os.Looper
22 import android.view.MotionEvent
23 import android.view.VelocityTracker
24 import android.view.View
25 import android.view.ViewConfiguration
26 import kotlin.math.hypot
27 
28 /**
29  * Listener which receives [onDown], [onMove], and [onUp] events, with relevant information about
30  * the coordinates of the touch and the view relative to the initial ACTION_DOWN event and the
31  * view's initial position.
32  */
33 abstract class RelativeTouchListener : View.OnTouchListener {
34 
35     /**
36      * Called when an ACTION_DOWN event is received for the given view.
37      *
38      * @return False if the object is not interested in MotionEvents at this time, or true if we
39      * should consume this event and subsequent events, and begin calling [onMove].
40      */
onDownnull41     abstract fun onDown(v: View, ev: MotionEvent): Boolean
42 
43     /**
44      * Called when an ACTION_MOVE event is received for the given view. This signals that the view
45      * is being dragged.
46      *
47      * @param viewInitialX The view's translationX value when this touch gesture started.
48      * @param viewInitialY The view's translationY value when this touch gesture started.
49      * @param dx Horizontal distance covered since the initial ACTION_DOWN event, in pixels.
50      * @param dy Vertical distance covered since the initial ACTION_DOWN event, in pixels.
51      */
52     abstract fun onMove(
53         v: View,
54         ev: MotionEvent,
55         viewInitialX: Float,
56         viewInitialY: Float,
57         dx: Float,
58         dy: Float
59     )
60 
61     /**
62      * Called when an ACTION_UP event is received for the given view. This signals that a drag or
63      * fling gesture has completed.
64      *
65      * @param viewInitialX The view's translationX value when this touch gesture started.
66      * @param viewInitialY The view's translationY value when this touch gesture started.
67      * @param dx Horizontal distance covered, in pixels.
68      * @param dy Vertical distance covered, in pixels.
69      * @param velX The final horizontal velocity of the gesture, in pixels/second.
70      * @param velY The final vertical velocity of the gesture, in pixels/second.
71      */
72     abstract fun onUp(
73         v: View,
74         ev: MotionEvent,
75         viewInitialX: Float,
76         viewInitialY: Float,
77         dx: Float,
78         dy: Float,
79         velX: Float,
80         velY: Float
81     )
82 
83     /** The raw coordinates of the last ACTION_DOWN event. */
84     private val touchDown = PointF()
85 
86     /** The coordinates of the view, at the time of the last ACTION_DOWN event. */
87     private val viewPositionOnTouchDown = PointF()
88 
89     private val velocityTracker = VelocityTracker.obtain()
90 
91     private var touchSlop: Int = -1
92     private var movedEnough = false
93 
94     private var performedLongClick = false
95 
96     @Suppress("UNCHECKED_CAST")
97     override fun onTouch(v: View, ev: MotionEvent): Boolean {
98         addMovement(ev)
99 
100         val dx = ev.rawX - touchDown.x
101         val dy = ev.rawY - touchDown.y
102 
103         when (ev.action) {
104             MotionEvent.ACTION_DOWN -> {
105                 if (!onDown(v, ev)) {
106                     return false
107                 }
108 
109                 // Grab the touch slop, it might have changed if the config changed since the
110                 // last gesture.
111                 touchSlop = ViewConfiguration.get(v.context).scaledTouchSlop
112 
113                 touchDown.set(ev.rawX, ev.rawY)
114                 viewPositionOnTouchDown.set(v.translationX, v.translationY)
115 
116                 performedLongClick = false
117                 v.handler.postDelayed({
118                     if (v.isLongClickable) {
119                         performedLongClick = v.performLongClick()
120                     }
121                 }, ViewConfiguration.getLongPressTimeout().toLong())
122             }
123 
124             MotionEvent.ACTION_MOVE -> {
125                 if (!movedEnough && hypot(dx, dy) > touchSlop && !performedLongClick) {
126                     movedEnough = true
127                     v.handler.removeCallbacksAndMessages(null)
128                 }
129 
130                 if (movedEnough) {
131                     onMove(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy)
132                 }
133             }
134 
135             MotionEvent.ACTION_UP -> {
136                 if (movedEnough) {
137                     velocityTracker.computeCurrentVelocity(1000 /* units */)
138                     onUp(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy,
139                             velocityTracker.xVelocity, velocityTracker.yVelocity)
140                 } else if (!performedLongClick) {
141                     v.performClick()
142                 } else {
143                     v.handler.removeCallbacksAndMessages(null)
144                 }
145 
146                 velocityTracker.clear()
147                 movedEnough = false
148             }
149         }
150 
151         return true
152     }
153 
154     /**
155      * Adds a movement to the velocity tracker using raw screen coordinates.
156      */
addMovementnull157     private fun addMovement(event: MotionEvent) {
158         val deltaX = event.rawX - event.x
159         val deltaY = event.rawY - event.y
160         event.offsetLocation(deltaX, deltaY)
161         velocityTracker.addMovement(event)
162         event.offsetLocation(-deltaX, -deltaY)
163     }
164 }