• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2024 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.server.input.debug;
18 
19 import android.content.Context;
20 import android.graphics.Canvas;
21 import android.graphics.Color;
22 import android.graphics.Paint;
23 import android.graphics.RectF;
24 import android.util.Slog;
25 import android.view.View;
26 
27 import com.android.server.input.TouchpadFingerState;
28 import com.android.server.input.TouchpadHardwareProperties;
29 import com.android.server.input.TouchpadHardwareState;
30 
31 import java.util.ArrayDeque;
32 import java.util.HashMap;
33 import java.util.Locale;
34 import java.util.Map;
35 
36 public class TouchpadVisualizationView extends View {
37     private static final String TAG = "TouchpadVizMain";
38     private static final boolean DEBUG = true;
39     private static final float DEFAULT_RES_X = 47f;
40     private static final float DEFAULT_RES_Y = 45f;
41     private static final float MAX_TRACE_HISTORY_DURATION_SECONDS = 1f;
42 
43     private final TouchpadHardwareProperties mTouchpadHardwareProperties;
44     private float mScaleFactor;
45 
46     private final ArrayDeque<TouchpadHardwareState> mHardwareStateHistory =
47             new ArrayDeque<TouchpadHardwareState>();
48     private final Map<Integer, TouchpadFingerState> mTempFingerStatesByTrackingId = new HashMap<>();
49 
50     private final Paint mOvalStrokePaint;
51     private final Paint mOvalFillPaint;
52     private final Paint mTracePaint;
53     private final Paint mCenterPointPaint;
54     private final Paint mPressureTextPaint;
55     private final RectF mTempOvalRect = new RectF();
56 
TouchpadVisualizationView(Context context, TouchpadHardwareProperties touchpadHardwareProperties)57     public TouchpadVisualizationView(Context context,
58             TouchpadHardwareProperties touchpadHardwareProperties) {
59         super(context);
60         mTouchpadHardwareProperties = touchpadHardwareProperties;
61         mScaleFactor = 1;
62         mOvalStrokePaint = new Paint();
63         mOvalStrokePaint.setAntiAlias(true);
64         mOvalStrokePaint.setStyle(Paint.Style.STROKE);
65         mOvalFillPaint = new Paint();
66         mOvalFillPaint.setAntiAlias(true);
67         mTracePaint = new Paint();
68         mTracePaint.setAntiAlias(false);
69         mTracePaint.setARGB(255, 0, 0, 255);
70         mTracePaint.setStyle(Paint.Style.STROKE);
71         mTracePaint.setStrokeWidth(2);
72         mCenterPointPaint = new Paint();
73         mCenterPointPaint.setAntiAlias(true);
74         mCenterPointPaint.setARGB(255, 255, 0, 0);
75         mCenterPointPaint.setStrokeWidth(2);
76         mPressureTextPaint = new Paint();
77         mPressureTextPaint.setAntiAlias(true);
78     }
79 
removeOldPoints()80     private void removeOldPoints() {
81         float latestTimestamp = mHardwareStateHistory.getLast().getTimestamp();
82 
83         while (!mHardwareStateHistory.isEmpty()) {
84             TouchpadHardwareState oldestPoint = mHardwareStateHistory.getFirst();
85             float onScreenTime = latestTimestamp - oldestPoint.getTimestamp();
86             if (onScreenTime >= MAX_TRACE_HISTORY_DURATION_SECONDS) {
87                 mHardwareStateHistory.removeFirst();
88             } else {
89                 break;
90             }
91         }
92     }
93 
drawOval(Canvas canvas, float x, float y, float major, float minor, float angle)94     private void drawOval(Canvas canvas, float x, float y, float major, float minor, float angle) {
95         canvas.save(Canvas.MATRIX_SAVE_FLAG);
96         canvas.rotate(angle, x, y);
97         mTempOvalRect.left = x - minor / 2;
98         mTempOvalRect.right = x + minor / 2;
99         mTempOvalRect.top = y - major / 2;
100         mTempOvalRect.bottom = y + major / 2;
101         canvas.drawOval(mTempOvalRect, mOvalStrokePaint);
102         canvas.drawOval(mTempOvalRect, mOvalFillPaint);
103         canvas.restore();
104     }
105 
106     @Override
onDraw(Canvas canvas)107     protected void onDraw(Canvas canvas) {
108         if (mHardwareStateHistory.isEmpty()) {
109             return;
110         }
111 
112         TouchpadHardwareState latestHardwareState = mHardwareStateHistory.getLast();
113 
114         float maximumPressure = 0;
115         for (TouchpadFingerState touchpadFingerState : latestHardwareState.getFingerStates()) {
116             maximumPressure = Math.max(maximumPressure, touchpadFingerState.getPressure());
117         }
118 
119         // Visualizing fingers as ovals
120         for (TouchpadFingerState touchpadFingerState : latestHardwareState.getFingerStates()) {
121             float newX = translateX(touchpadFingerState.getPositionX());
122 
123             float newY = translateY(touchpadFingerState.getPositionY());
124 
125             float newAngle = translateRange(0, mTouchpadHardwareProperties.getOrientationMaximum(),
126                     0, 90, touchpadFingerState.getOrientation());
127 
128             float resX = mTouchpadHardwareProperties.getResX() == 0f ? DEFAULT_RES_X
129                     : mTouchpadHardwareProperties.getResX();
130             float resY = mTouchpadHardwareProperties.getResY() == 0f ? DEFAULT_RES_Y
131                     : mTouchpadHardwareProperties.getResY();
132 
133             float newTouchMajor = touchpadFingerState.getTouchMajor() * mScaleFactor / resY;
134             float newTouchMinor = touchpadFingerState.getTouchMinor() * mScaleFactor / resX;
135 
136             float pressureToOpacity = translateRange(0, maximumPressure, 0, 255,
137                     touchpadFingerState.getPressure());
138             mOvalFillPaint.setAlpha((int) pressureToOpacity);
139 
140             drawOval(canvas, newX, newY, newTouchMajor, newTouchMinor, newAngle);
141 
142             String formattedPressure = String.format(Locale.getDefault(), "Ps: %.2f",
143                     touchpadFingerState.getPressure());
144             float textWidth = mPressureTextPaint.measureText(formattedPressure);
145 
146             canvas.drawText(formattedPressure, newX - textWidth / 2,
147                     newY - newTouchMajor / 2, mPressureTextPaint);
148         }
149 
150         mTempFingerStatesByTrackingId.clear();
151 
152         // Drawing the trace
153         for (TouchpadHardwareState currentHardwareState : mHardwareStateHistory) {
154             for (TouchpadFingerState currentFingerState : currentHardwareState.getFingerStates()) {
155                 TouchpadFingerState prevFingerState = mTempFingerStatesByTrackingId.put(
156                         currentFingerState.getTrackingId(), currentFingerState);
157 
158                 if (prevFingerState == null) {
159                     continue;
160                 }
161 
162                 float currentX = translateX(currentFingerState.getPositionX());
163                 float currentY = translateY(currentFingerState.getPositionY());
164                 float prevX = translateX(prevFingerState.getPositionX());
165                 float prevY = translateY(prevFingerState.getPositionY());
166 
167                 canvas.drawLine(prevX, prevY, currentX, currentY, mTracePaint);
168                 canvas.drawPoint(currentX, currentY, mCenterPointPaint);
169             }
170         }
171     }
172 
173     /**
174      * Receiving the touchpad hardware state and based on it update the latest hardware state.
175      *
176      * @param schs The new hardware state received.
177      */
onTouchpadHardwareStateNotified(TouchpadHardwareState schs)178     public void onTouchpadHardwareStateNotified(TouchpadHardwareState schs) {
179         if (DEBUG) {
180             logHardwareState(schs);
181         }
182 
183         if (!mHardwareStateHistory.isEmpty()
184                 && mHardwareStateHistory.getLast().getFingerCount() == 0
185                 && schs.getFingerCount() > 0) {
186             mHardwareStateHistory.clear();
187         }
188 
189         mHardwareStateHistory.addLast(schs);
190         removeOldPoints();
191 
192         if (DEBUG) {
193             logFingerTrace();
194         }
195 
196         invalidate();
197     }
198 
199     /**
200      * Update the scale factor of the drawings in the view.
201      *
202      * @param scaleFactor the new scale factor
203      */
updateScaleFactor(float scaleFactor)204     public void updateScaleFactor(float scaleFactor) {
205         mScaleFactor = scaleFactor;
206     }
207 
208     /**
209      * Change the colors of the objects inside the view to light mode theme.
210      */
setLightModeTheme()211     public void setLightModeTheme() {
212         this.setBackgroundColor(Color.rgb(20, 20, 20));
213         mPressureTextPaint.setARGB(255, 255, 255, 255);
214         mOvalFillPaint.setARGB(255, 255, 255, 255);
215         mOvalStrokePaint.setARGB(255, 255, 255, 255);
216     }
217 
218     /**
219      * Change the colors of the objects inside the view to night mode theme.
220      */
setNightModeTheme()221     public void setNightModeTheme() {
222         this.setBackgroundColor(Color.rgb(240, 240, 240));
223         mPressureTextPaint.setARGB(255, 0, 0, 0);
224         mOvalFillPaint.setARGB(255, 0, 0, 0);
225         mOvalStrokePaint.setARGB(255, 0, 0, 0);
226     }
227 
translateX(float x)228     private float translateX(float x) {
229         return translateRange(mTouchpadHardwareProperties.getLeft(),
230                 mTouchpadHardwareProperties.getRight(), 0, getWidth(), x);
231     }
232 
translateY(float y)233     private float translateY(float y) {
234         return translateRange(mTouchpadHardwareProperties.getTop(),
235                 mTouchpadHardwareProperties.getBottom(), 0, getHeight(), y);
236     }
237 
translateRange(float rangeBeforeMin, float rangeBeforeMax, float rangeAfterMin, float rangeAfterMax, float value)238     private float translateRange(float rangeBeforeMin, float rangeBeforeMax,
239             float rangeAfterMin, float rangeAfterMax, float value) {
240         return rangeAfterMin + (value - rangeBeforeMin) / (rangeBeforeMax - rangeBeforeMin) * (
241                 rangeAfterMax - rangeAfterMin);
242     }
243 
logHardwareState(TouchpadHardwareState schs)244     private void logHardwareState(TouchpadHardwareState schs) {
245         Slog.d(TAG, "notifyTouchpadHardwareState: Time: "
246                 + schs.getTimestamp() + ", No. Buttons: "
247                 + schs.getButtonsDown() + ", No. Fingers: "
248                 + schs.getFingerCount() + ", No. Touch: "
249                 + schs.getTouchCount());
250 
251         for (TouchpadFingerState finger : schs.getFingerStates()) {
252             Slog.d(TAG, "Finger #" + finger.getTrackingId()
253                     + ": touchMajor= " + finger.getTouchMajor()
254                     + ", touchMinor= " + finger.getTouchMinor()
255                     + ", widthMajor= " + finger.getWidthMajor()
256                     + ", widthMinor= " + finger.getWidthMinor()
257                     + ", pressure= " + finger.getPressure()
258                     + ", orientation= " + finger.getOrientation()
259                     + ", positionX= " + finger.getPositionX()
260                     + ", positionY= " + finger.getPositionY());
261         }
262     }
263 
logFingerTrace()264     private void logFingerTrace() {
265         Slog.d(TAG, "Trace size= " + mHardwareStateHistory.size());
266         for (TouchpadFingerState tfs : mHardwareStateHistory.getLast().getFingerStates()) {
267             Slog.d(TAG, "ID= " + tfs.getTrackingId());
268         }
269     }
270 }