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 }