1 /*
<lambda>null2 * Copyright 2023 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 test.multideviceinput
18
19 import android.content.Context
20 import android.graphics.Canvas
21 import android.graphics.Color
22 import android.graphics.Paint
23 import android.util.AttributeSet
24 import android.view.InputDevice
25 import android.view.InputDevice.SOURCE_MOUSE
26 import android.view.InputDevice.SOURCE_STYLUS
27 import android.view.InputDevice.SOURCE_TOUCHSCREEN
28 import android.view.InputDevice.SOURCE_TOUCHPAD
29 import android.view.MotionEvent
30 import android.view.MotionEvent.ACTION_CANCEL
31 import android.view.MotionEvent.ACTION_DOWN
32 import android.view.MotionEvent.ACTION_HOVER_EXIT
33 import android.view.MotionEvent.ACTION_UP
34 import android.view.ScaleGestureDetector
35 import android.view.View
36
37 import java.util.Vector
38
39 private fun drawLine(canvas: Canvas, from: MotionEvent, to: MotionEvent, paint: Paint) {
40 // Correct implementation here would require us to build a set of pointers and then iterate
41 // through them. Instead, we are taking a few shortcuts and ignore some of the events, which
42 // causes occasional gaps in the drawings.
43 if (from.pointerCount != to.pointerCount) {
44 return
45 }
46 // Now, 'from' is guaranteed to have as many pointers as the 'to' event. It doesn't
47 // necessarily mean they are the same pointers, though.
48 for (p in 0..<from.pointerCount) {
49 val x0 = from.getX(p)
50 val y0 = from.getY(p)
51 if (to.getPointerId(p) == from.getPointerId(p)) {
52 // This only works when the i-th pointer in "to" is the same pointer
53 // as the i-th pointer in "from"`. It's not guaranteed by the input APIs,
54 // but it works in practice.
55 val x1 = to.getX(p)
56 val y1 = to.getY(p)
57 // Ignoring historical data here for simplicity
58 canvas.drawLine(x0, y0, x1, y1, paint)
59 }
60 }
61 }
62
drawCirclenull63 private fun drawCircle(canvas: Canvas, event: MotionEvent, paint: Paint, radius: Float) {
64 val x = event.x
65 val y = event.y
66 canvas.drawCircle(x, y, radius, paint)
67 }
68
69 /**
70 * Draw the current stroke
71 */
72 class DrawingView : View {
73 private val TAG = "DrawingView"
74
75 private var myState: SharedScaledPointerSize? = null
76 private var otherState: SharedScaledPointerSize? = null
77
78 constructor(
79 context: Context,
80 myState: SharedScaledPointerSize,
81 otherState: SharedScaledPointerSize
82 ) : super(context) {
83 this.myState = myState
84 this.otherState = otherState
85 init()
86 }
87
88 constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
89 init()
90 }
91
92 val touchEvents = mutableMapOf<Int, Vector<Pair<MotionEvent, Paint>>>()
93 val hoverEvents = mutableMapOf<Int, MotionEvent>()
94
95 val scaleGestureListener = object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
96
onScaleBeginnull97 override fun onScaleBegin(scaleGestureDetector: ScaleGestureDetector): Boolean {
98 return true
99 }
100
onScalenull101 override fun onScale(scaleGestureDetector: ScaleGestureDetector): Boolean {
102 val scaleFactor = scaleGestureDetector.scaleFactor
103 when (otherState?.state) {
104 PointerState.DOWN -> {
105 otherState?.lineSize = (otherState?.lineSize ?: 5f) * scaleFactor
106 }
107 PointerState.HOVER -> {
108 otherState?.circleSize = (otherState?.circleSize ?: 20f) * scaleFactor
109 }
110 else -> {}
111 }
112 return true
113 }
114 }
115 private val scaleGestureDetector = ScaleGestureDetector(context, scaleGestureListener, null)
116
117 private var touchPaint = Paint()
118 private var touchpadPaint = Paint()
119 private var stylusPaint = Paint()
120 private var mousePaint = Paint()
121 private var drawingTabletPaint = Paint()
122
initnull123 private fun init() {
124 touchPaint.color = Color.RED
125 touchPaint.strokeWidth = 5f
126 touchpadPaint.color = Color.BLACK
127 touchpadPaint.strokeWidth = 5f
128 stylusPaint.color = Color.YELLOW
129 stylusPaint.strokeWidth = 5f
130 mousePaint.color = Color.BLUE
131 mousePaint.strokeWidth = 5f
132 drawingTabletPaint.color = Color.GREEN
133 drawingTabletPaint.strokeWidth = 5f
134 setOnHoverListener { _, event -> processHoverEvent(event); true }
135 }
136
resolvePaintnull137 private fun resolvePaint(event: MotionEvent): Paint? {
138 val inputDevice = InputDevice.getDevice(event.deviceId)
139 val isTouchpadDevice = inputDevice != null && inputDevice.supportsSource(SOURCE_TOUCHPAD)
140 return if (event.isFromSource(SOURCE_STYLUS or SOURCE_MOUSE)) {
141 // External stylus / drawing tablet
142 drawingTabletPaint
143 } else if (event.isFromSource(SOURCE_TOUCHSCREEN) && !event.isFromSource(SOURCE_STYLUS)) {
144 // Touchscreen event
145 touchPaint
146 } else if (event.isFromSource(SOURCE_MOUSE) &&
147 (event.isFromSource(SOURCE_TOUCHPAD) || isTouchpadDevice)) {
148 // Touchpad event
149 touchpadPaint
150 } else if (event.isFromSource(SOURCE_MOUSE)) {
151 // Mouse event
152 mousePaint
153 } else if (event.isFromSource(SOURCE_STYLUS)) {
154 // Stylus event
155 stylusPaint
156 } else {
157 // Drop the event
158 null
159 }
160 }
161
processTouchEventnull162 private fun processTouchEvent(event: MotionEvent) {
163 scaleGestureDetector.onTouchEvent(event)
164 if (event.actionMasked == ACTION_DOWN) {
165 touchEvents.remove(event.deviceId)
166 myState?.state = PointerState.DOWN
167 } else if (event.actionMasked == ACTION_UP || event.actionMasked == ACTION_CANCEL) {
168 myState?.state = PointerState.NONE
169 }
170 val paint = resolvePaint(event)
171 if (paint != null) {
172 val vec = touchEvents.getOrPut(event.deviceId) { Vector<Pair<MotionEvent, Paint>>() }
173 val size = myState?.lineSize ?: 5f
174 paint.strokeWidth = size
175 vec.add(Pair(MotionEvent.obtain(event), Paint(paint)))
176 invalidate()
177 }
178 }
179
processHoverEventnull180 private fun processHoverEvent(event: MotionEvent) {
181 hoverEvents.remove(event.deviceId)
182 if (event.actionMasked != ACTION_HOVER_EXIT) {
183 hoverEvents[event.deviceId] = MotionEvent.obtain(event)
184 myState?.state = PointerState.HOVER
185 } else {
186 myState?.state = PointerState.NONE
187 }
188 invalidate()
189 }
190
onTouchEventnull191 override fun onTouchEvent(event: MotionEvent): Boolean {
192 processTouchEvent(event)
193 return true
194 }
195
onDrawnull196 public override fun onDraw(canvas: Canvas) {
197 super.onDraw(canvas)
198
199 // Draw touch and stylus MotionEvents
200 for ((_, vec) in touchEvents ) {
201 for (i in 1 until vec.size) {
202 drawLine(canvas, vec[i - 1].first, vec[i].first, vec[i].second)
203 }
204 }
205 // Draw hovers
206 for ((_, event) in hoverEvents ) {
207 val paint = resolvePaint(event)
208 if (paint != null) {
209 val size = myState?.circleSize ?: 20f
210 drawCircle(canvas, event, paint, size)
211 }
212 }
213 }
214 }
215