1 /*
2  * Copyright (C) 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.motionprediction
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.MotionEvent
25 import android.view.MotionEvent.ACTION_DOWN
26 import android.view.MotionPredictor
27 import android.view.View
28 
29 import java.util.Vector
30 
drawLinenull31 private fun drawLine(canvas: Canvas, from: MotionEvent, to: MotionEvent, paint: Paint) {
32     canvas.apply {
33         val x0 = from.getX()
34         val y0 = from.getY()
35         val x1 = to.getX()
36         val y1 = to.getY()
37         // TODO: handle historical data
38         drawLine(x0, y0, x1, y1, paint)
39     }
40 }
41 
42 /**
43  * Draw the current stroke and predicted values
44  */
45 class DrawingView(context: Context, attrs: AttributeSet) : View(context, attrs) {
46     private val TAG = "DrawingView"
47 
48     val events: MutableMap<Int, Vector<MotionEvent>> = mutableMapOf<Int, Vector<MotionEvent>>()
49 
50     var isPredictionAvailable = false
51     private val predictor = MotionPredictor(getContext())
52 
53     private var predictionPaint = Paint()
54     private var realPaint = Paint()
55 
56     init {
57         setBackgroundColor(Color.WHITE)
58         predictionPaint.color = Color.BLACK
59         predictionPaint.setStrokeWidth(5f)
60         realPaint.color = Color.RED
61         realPaint.setStrokeWidth(5f)
62     }
63 
addEventnull64     private fun addEvent(event: MotionEvent) {
65         if (event.getActionMasked() == ACTION_DOWN) {
66             events.remove(event.deviceId)
67         }
68         var vec = events.getOrPut(event.deviceId) { Vector<MotionEvent>() }
69         vec.add(MotionEvent.obtain(event))
70         predictor.record(event)
71         invalidate()
72     }
73 
onTouchEventnull74     public override fun onTouchEvent(event: MotionEvent): Boolean {
75         isPredictionAvailable = predictor.isPredictionAvailable(event.getDeviceId(),
76                                                                 event.getSource())
77         addEvent(event)
78         return true
79     }
80 
onDrawnull81     public override fun onDraw(canvas: Canvas) {
82         super.onDraw(canvas)
83         if (!isPredictionAvailable) {
84             canvas.apply {
85                 drawRect(0f, 0f, 200f, 200f, realPaint)
86             }
87         }
88 
89         var eventTime = 0L
90 
91         // Draw real events
92         for ((_, vec) in events ) {
93             for (i in 1 until vec.size) {
94                 drawLine(canvas, vec[i - 1], vec[i], realPaint)
95             }
96             eventTime = vec.lastElement().eventTime
97         }
98 
99         // Draw predictions. Convert to nanos and hardcode to +20ms into the future
100         val prediction = predictor.predict(eventTime * 1000000 + 20000000)
101         if (prediction != null) {
102             val realEvents = events.get(prediction.deviceId)!!
103             drawLine(canvas, realEvents[realEvents.size - 1], prediction, predictionPaint)
104         }
105     }
106 }
107