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 }