blob: e339c44db16e718a36c15b208ed6c8ccec09eeaa [file] [log] [blame]
Kenny Rootf8d0f092010-03-12 08:15:28 -08001/*
2 * Copyright (C) 2010 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
Dianne Hackborn82977882010-03-01 21:42:15 -080017package com.android.internal.widget;
Dianne Hackborn90d2db32010-02-11 22:19:06 -080018
19import android.content.Context;
20import android.graphics.Canvas;
21import android.graphics.Paint;
Jeff Brown8d608662010-08-30 03:02:23 -070022import android.graphics.RectF;
Dianne Hackborn90d2db32010-02-11 22:19:06 -080023import android.graphics.Paint.FontMetricsInt;
Jeff Brownaf9e8d32012-04-12 17:32:48 -070024import android.hardware.input.InputManager;
25import android.hardware.input.InputManager.InputDeviceListener;
Jeff Brown9eb7d862012-06-01 12:39:25 -070026import android.os.SystemProperties;
Dianne Hackborn90d2db32010-02-11 22:19:06 -080027import android.util.Log;
Jeff Brown8d608662010-08-30 03:02:23 -070028import android.view.InputDevice;
Jeff Brownc3fe7662011-03-07 14:35:59 -080029import android.view.KeyEvent;
Dianne Hackborn90d2db32010-02-11 22:19:06 -080030import android.view.MotionEvent;
31import android.view.VelocityTracker;
32import android.view.View;
33import android.view.ViewConfiguration;
Michael Wrightc9c487e2013-11-07 18:55:09 -080034import android.view.WindowManagerPolicy.PointerEventListener;
Jeff Browncc0c1592011-02-19 05:07:28 -080035import android.view.MotionEvent.PointerCoords;
Dianne Hackborn90d2db32010-02-11 22:19:06 -080036
37import java.util.ArrayList;
38
Michael Wrightc9c487e2013-11-07 18:55:09 -080039public class PointerLocationView extends View implements InputDeviceListener,
40 PointerEventListener {
Jeff Brown8d608662010-08-30 03:02:23 -070041 private static final String TAG = "Pointer";
Jeff Brown9eb7d862012-06-01 12:39:25 -070042
43 // The system property key used to specify an alternate velocity tracker strategy
44 // to plot alongside the default one. Useful for testing and comparison purposes.
45 private static final String ALT_STRATEGY_PROPERY_KEY = "debug.velocitytracker.alt";
46
Dianne Hackborn90d2db32010-02-11 22:19:06 -080047 public static class PointerState {
Jeff Brown8d608662010-08-30 03:02:23 -070048 // Trace of previous points.
49 private float[] mTraceX = new float[32];
50 private float[] mTraceY = new float[32];
Michael Wright76936eb2013-06-21 15:18:06 -070051 private boolean[] mTraceCurrent = new boolean[32];
Jeff Brown8d608662010-08-30 03:02:23 -070052 private int mTraceCount;
53
54 // True if the pointer is down.
Dianne Hackborn90d2db32010-02-11 22:19:06 -080055 private boolean mCurDown;
Jeff Brown8d608662010-08-30 03:02:23 -070056
57 // Most recent coordinates.
Jeff Browncc0c1592011-02-19 05:07:28 -080058 private PointerCoords mCoords = new PointerCoords();
Jeff Brown65fd2512011-08-18 11:20:58 -070059 private int mToolType;
Jeff Brown8d608662010-08-30 03:02:23 -070060
61 // Most recent velocity.
Jeff Brown9e2ad362010-07-30 19:20:11 -070062 private float mXVelocity;
63 private float mYVelocity;
Jeff Brown9eb7d862012-06-01 12:39:25 -070064 private float mAltXVelocity;
65 private float mAltYVelocity;
Jeff Brownb59ab9f2011-09-14 10:53:18 -070066
Michael Wright86172f62013-05-15 23:16:54 -070067 // Current bounding box, if any
68 private boolean mHasBoundingBox;
69 private float mBoundingLeft;
70 private float mBoundingTop;
71 private float mBoundingRight;
72 private float mBoundingBottom;
73
Jeff Brownb59ab9f2011-09-14 10:53:18 -070074 // Position estimator.
75 private VelocityTracker.Estimator mEstimator = new VelocityTracker.Estimator();
Jeff Brown9eb7d862012-06-01 12:39:25 -070076 private VelocityTracker.Estimator mAltEstimator = new VelocityTracker.Estimator();
Jeff Brownb59ab9f2011-09-14 10:53:18 -070077
Jeff Brown8d608662010-08-30 03:02:23 -070078 public void clearTrace() {
79 mTraceCount = 0;
80 }
81
Michael Wright76936eb2013-06-21 15:18:06 -070082 public void addTrace(float x, float y, boolean current) {
Jeff Brown8d608662010-08-30 03:02:23 -070083 int traceCapacity = mTraceX.length;
84 if (mTraceCount == traceCapacity) {
85 traceCapacity *= 2;
86 float[] newTraceX = new float[traceCapacity];
87 System.arraycopy(mTraceX, 0, newTraceX, 0, mTraceCount);
88 mTraceX = newTraceX;
89
90 float[] newTraceY = new float[traceCapacity];
91 System.arraycopy(mTraceY, 0, newTraceY, 0, mTraceCount);
92 mTraceY = newTraceY;
Michael Wright76936eb2013-06-21 15:18:06 -070093
94 boolean[] newTraceCurrent = new boolean[traceCapacity];
95 System.arraycopy(mTraceCurrent, 0, newTraceCurrent, 0, mTraceCount);
96 mTraceCurrent= newTraceCurrent;
Jeff Brown8d608662010-08-30 03:02:23 -070097 }
98
99 mTraceX[mTraceCount] = x;
100 mTraceY[mTraceCount] = y;
Michael Wright76936eb2013-06-21 15:18:06 -0700101 mTraceCurrent[mTraceCount] = current;
Jeff Brown8d608662010-08-30 03:02:23 -0700102 mTraceCount += 1;
103 }
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800104 }
105
Jeff Brownb59ab9f2011-09-14 10:53:18 -0700106 private final int ESTIMATE_PAST_POINTS = 4;
107 private final int ESTIMATE_FUTURE_POINTS = 2;
108 private final float ESTIMATE_INTERVAL = 0.02f;
109
Jeff Brownaf9e8d32012-04-12 17:32:48 -0700110 private final InputManager mIm;
111
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800112 private final ViewConfiguration mVC;
113 private final Paint mTextPaint;
114 private final Paint mTextBackgroundPaint;
115 private final Paint mTextLevelPaint;
116 private final Paint mPaint;
Michael Wright76936eb2013-06-21 15:18:06 -0700117 private final Paint mCurrentPointPaint;
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800118 private final Paint mTargetPaint;
119 private final Paint mPathPaint;
120 private final FontMetricsInt mTextMetrics = new FontMetricsInt();
121 private int mHeaderBottom;
122 private boolean mCurDown;
123 private int mCurNumPointers;
124 private int mMaxNumPointers;
Jeff Brownd1e0c372010-09-12 19:14:26 -0700125 private int mActivePointerId;
Jeff Brown8d608662010-08-30 03:02:23 -0700126 private final ArrayList<PointerState> mPointers = new ArrayList<PointerState>();
Jeff Brown65fd2512011-08-18 11:20:58 -0700127 private final PointerCoords mTempCoords = new PointerCoords();
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800128
Jeff Brown9e2ad362010-07-30 19:20:11 -0700129 private final VelocityTracker mVelocity;
Jeff Brown9eb7d862012-06-01 12:39:25 -0700130 private final VelocityTracker mAltVelocity;
131
Jeff Brown8d608662010-08-30 03:02:23 -0700132 private final FasterStringBuilder mText = new FasterStringBuilder();
133
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800134 private boolean mPrintCoords = true;
135
136 public PointerLocationView(Context c) {
137 super(c);
Jeff Brownc3fe7662011-03-07 14:35:59 -0800138 setFocusableInTouchMode(true);
139
Jeff Brownaf9e8d32012-04-12 17:32:48 -0700140 mIm = (InputManager)c.getSystemService(Context.INPUT_SERVICE);
141
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800142 mVC = ViewConfiguration.get(c);
143 mTextPaint = new Paint();
144 mTextPaint.setAntiAlias(true);
145 mTextPaint.setTextSize(10
146 * getResources().getDisplayMetrics().density);
147 mTextPaint.setARGB(255, 0, 0, 0);
148 mTextBackgroundPaint = new Paint();
149 mTextBackgroundPaint.setAntiAlias(false);
150 mTextBackgroundPaint.setARGB(128, 255, 255, 255);
151 mTextLevelPaint = new Paint();
152 mTextLevelPaint.setAntiAlias(false);
153 mTextLevelPaint.setARGB(192, 255, 0, 0);
154 mPaint = new Paint();
155 mPaint.setAntiAlias(true);
156 mPaint.setARGB(255, 255, 255, 255);
157 mPaint.setStyle(Paint.Style.STROKE);
158 mPaint.setStrokeWidth(2);
Michael Wright76936eb2013-06-21 15:18:06 -0700159 mCurrentPointPaint = new Paint();
160 mCurrentPointPaint.setAntiAlias(true);
161 mCurrentPointPaint.setARGB(255, 255, 0, 0);
162 mCurrentPointPaint.setStyle(Paint.Style.STROKE);
163 mCurrentPointPaint.setStrokeWidth(2);
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800164 mTargetPaint = new Paint();
165 mTargetPaint.setAntiAlias(false);
166 mTargetPaint.setARGB(255, 0, 0, 192);
167 mPathPaint = new Paint();
168 mPathPaint.setAntiAlias(false);
169 mPathPaint.setARGB(255, 0, 96, 255);
170 mPaint.setStyle(Paint.Style.STROKE);
171 mPaint.setStrokeWidth(1);
172
173 PointerState ps = new PointerState();
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800174 mPointers.add(ps);
Jeff Brownd1e0c372010-09-12 19:14:26 -0700175 mActivePointerId = 0;
Jeff Brown9e2ad362010-07-30 19:20:11 -0700176
177 mVelocity = VelocityTracker.obtain();
Jeff Brown9eb7d862012-06-01 12:39:25 -0700178
179 String altStrategy = SystemProperties.get(ALT_STRATEGY_PROPERY_KEY);
180 if (altStrategy.length() != 0) {
181 Log.d(TAG, "Comparing default velocity tracker strategy with " + altStrategy);
182 mAltVelocity = VelocityTracker.obtain(altStrategy);
183 } else {
184 mAltVelocity = null;
185 }
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800186 }
187
188 public void setPrintCoords(boolean state) {
189 mPrintCoords = state;
190 }
191
192 @Override
193 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
194 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
195 mTextPaint.getFontMetricsInt(mTextMetrics);
196 mHeaderBottom = -mTextMetrics.ascent+mTextMetrics.descent+2;
197 if (false) {
198 Log.i("foo", "Metrics: ascent=" + mTextMetrics.ascent
199 + " descent=" + mTextMetrics.descent
200 + " leading=" + mTextMetrics.leading
201 + " top=" + mTextMetrics.top
202 + " bottom=" + mTextMetrics.bottom);
203 }
204 }
Jeff Brown8d608662010-08-30 03:02:23 -0700205
206 // Draw an oval. When angle is 0 radians, orients the major axis vertically,
207 // angles less than or greater than 0 radians rotate the major axis left or right.
208 private RectF mReusableOvalRect = new RectF();
209 private void drawOval(Canvas canvas, float x, float y, float major, float minor,
210 float angle, Paint paint) {
211 canvas.save(Canvas.MATRIX_SAVE_FLAG);
Jeff Brown5068ad82010-09-29 20:14:56 -0700212 canvas.rotate((float) (angle * 180 / Math.PI), x, y);
Jeff Brown8d608662010-08-30 03:02:23 -0700213 mReusableOvalRect.left = x - minor / 2;
214 mReusableOvalRect.right = x + minor / 2;
215 mReusableOvalRect.top = y - major / 2;
216 mReusableOvalRect.bottom = y + major / 2;
217 canvas.drawOval(mReusableOvalRect, paint);
218 canvas.restore();
219 }
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800220
221 @Override
222 protected void onDraw(Canvas canvas) {
Jeff Brown70825162012-03-28 17:27:48 -0700223 final int w = getWidth();
224 final int itemW = w/7;
225 final int base = -mTextMetrics.ascent+1;
226 final int bottom = mHeaderBottom;
Jeff Brownd1e0c372010-09-12 19:14:26 -0700227
Jeff Brown70825162012-03-28 17:27:48 -0700228 final int NP = mPointers.size();
229
230 // Labels
231 if (mActivePointerId >= 0) {
232 final PointerState ps = mPointers.get(mActivePointerId);
233
234 canvas.drawRect(0, 0, itemW-1, bottom,mTextBackgroundPaint);
235 canvas.drawText(mText.clear()
236 .append("P: ").append(mCurNumPointers)
237 .append(" / ").append(mMaxNumPointers)
238 .toString(), 1, base, mTextPaint);
239
240 final int N = ps.mTraceCount;
241 if ((mCurDown && ps.mCurDown) || N == 0) {
242 canvas.drawRect(itemW, 0, (itemW * 2) - 1, bottom, mTextBackgroundPaint);
Jeff Brown8d608662010-08-30 03:02:23 -0700243 canvas.drawText(mText.clear()
Jeff Brown70825162012-03-28 17:27:48 -0700244 .append("X: ").append(ps.mCoords.x, 1)
245 .toString(), 1 + itemW, base, mTextPaint);
246 canvas.drawRect(itemW * 2, 0, (itemW * 3) - 1, bottom, mTextBackgroundPaint);
Jeff Brown8d608662010-08-30 03:02:23 -0700247 canvas.drawText(mText.clear()
Jeff Brown70825162012-03-28 17:27:48 -0700248 .append("Y: ").append(ps.mCoords.y, 1)
249 .toString(), 1 + itemW * 2, base, mTextPaint);
250 } else {
251 float dx = ps.mTraceX[N - 1] - ps.mTraceX[0];
252 float dy = ps.mTraceY[N - 1] - ps.mTraceY[0];
253 canvas.drawRect(itemW, 0, (itemW * 2) - 1, bottom,
254 Math.abs(dx) < mVC.getScaledTouchSlop()
255 ? mTextBackgroundPaint : mTextLevelPaint);
Jeff Brown8d608662010-08-30 03:02:23 -0700256 canvas.drawText(mText.clear()
Jeff Brown70825162012-03-28 17:27:48 -0700257 .append("dX: ").append(dx, 1)
258 .toString(), 1 + itemW, base, mTextPaint);
259 canvas.drawRect(itemW * 2, 0, (itemW * 3) - 1, bottom,
260 Math.abs(dy) < mVC.getScaledTouchSlop()
261 ? mTextBackgroundPaint : mTextLevelPaint);
Jeff Brown8d608662010-08-30 03:02:23 -0700262 canvas.drawText(mText.clear()
Jeff Brown70825162012-03-28 17:27:48 -0700263 .append("dY: ").append(dy, 1)
264 .toString(), 1 + itemW * 2, base, mTextPaint);
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800265 }
Jeff Brownb59ab9f2011-09-14 10:53:18 -0700266
Jeff Brown70825162012-03-28 17:27:48 -0700267 canvas.drawRect(itemW * 3, 0, (itemW * 4) - 1, bottom, mTextBackgroundPaint);
268 canvas.drawText(mText.clear()
269 .append("Xv: ").append(ps.mXVelocity, 3)
270 .toString(), 1 + itemW * 3, base, mTextPaint);
Jeff Brown517bb4c2011-01-14 19:09:23 -0800271
Jeff Brown70825162012-03-28 17:27:48 -0700272 canvas.drawRect(itemW * 4, 0, (itemW * 5) - 1, bottom, mTextBackgroundPaint);
273 canvas.drawText(mText.clear()
274 .append("Yv: ").append(ps.mYVelocity, 3)
275 .toString(), 1 + itemW * 4, base, mTextPaint);
Jeff Brown65fd2512011-08-18 11:20:58 -0700276
Jeff Brown70825162012-03-28 17:27:48 -0700277 canvas.drawRect(itemW * 5, 0, (itemW * 6) - 1, bottom, mTextBackgroundPaint);
278 canvas.drawRect(itemW * 5, 0, (itemW * 5) + (ps.mCoords.pressure * itemW) - 1,
279 bottom, mTextLevelPaint);
280 canvas.drawText(mText.clear()
281 .append("Prs: ").append(ps.mCoords.pressure, 2)
282 .toString(), 1 + itemW * 5, base, mTextPaint);
283
284 canvas.drawRect(itemW * 6, 0, w, bottom, mTextBackgroundPaint);
285 canvas.drawRect(itemW * 6, 0, (itemW * 6) + (ps.mCoords.size * itemW) - 1,
286 bottom, mTextLevelPaint);
287 canvas.drawText(mText.clear()
288 .append("Size: ").append(ps.mCoords.size, 2)
289 .toString(), 1 + itemW * 6, base, mTextPaint);
290 }
291
292 // Pointer trace.
293 for (int p = 0; p < NP; p++) {
294 final PointerState ps = mPointers.get(p);
295
296 // Draw path.
297 final int N = ps.mTraceCount;
298 float lastX = 0, lastY = 0;
299 boolean haveLast = false;
300 boolean drawn = false;
301 mPaint.setARGB(255, 128, 255, 255);
302 for (int i=0; i < N; i++) {
303 float x = ps.mTraceX[i];
304 float y = ps.mTraceY[i];
305 if (Float.isNaN(x)) {
306 haveLast = false;
307 continue;
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800308 }
Jeff Brown70825162012-03-28 17:27:48 -0700309 if (haveLast) {
310 canvas.drawLine(lastX, lastY, x, y, mPathPaint);
Michael Wright76936eb2013-06-21 15:18:06 -0700311 final Paint paint = ps.mTraceCurrent[i] ? mCurrentPointPaint : mPaint;
312 canvas.drawPoint(lastX, lastY, paint);
Jeff Brown70825162012-03-28 17:27:48 -0700313 drawn = true;
314 }
315 lastX = x;
316 lastY = y;
317 haveLast = true;
318 }
319
320 if (drawn) {
321 // Draw movement estimate curve.
322 mPaint.setARGB(128, 128, 0, 128);
323 float lx = ps.mEstimator.estimateX(-ESTIMATE_PAST_POINTS * ESTIMATE_INTERVAL);
324 float ly = ps.mEstimator.estimateY(-ESTIMATE_PAST_POINTS * ESTIMATE_INTERVAL);
325 for (int i = -ESTIMATE_PAST_POINTS + 1; i <= ESTIMATE_FUTURE_POINTS; i++) {
326 float x = ps.mEstimator.estimateX(i * ESTIMATE_INTERVAL);
327 float y = ps.mEstimator.estimateY(i * ESTIMATE_INTERVAL);
328 canvas.drawLine(lx, ly, x, y, mPaint);
329 lx = x;
330 ly = y;
331 }
332
333 // Draw velocity vector.
334 mPaint.setARGB(255, 255, 64, 128);
335 float xVel = ps.mXVelocity * (1000 / 60);
336 float yVel = ps.mYVelocity * (1000 / 60);
337 canvas.drawLine(lastX, lastY, lastX + xVel, lastY + yVel, mPaint);
Jeff Brown9eb7d862012-06-01 12:39:25 -0700338
339 // Draw alternate estimate.
340 if (mAltVelocity != null) {
341 mPaint.setARGB(128, 0, 128, 128);
342 lx = ps.mAltEstimator.estimateX(-ESTIMATE_PAST_POINTS * ESTIMATE_INTERVAL);
343 ly = ps.mAltEstimator.estimateY(-ESTIMATE_PAST_POINTS * ESTIMATE_INTERVAL);
344 for (int i = -ESTIMATE_PAST_POINTS + 1; i <= ESTIMATE_FUTURE_POINTS; i++) {
345 float x = ps.mAltEstimator.estimateX(i * ESTIMATE_INTERVAL);
346 float y = ps.mAltEstimator.estimateY(i * ESTIMATE_INTERVAL);
347 canvas.drawLine(lx, ly, x, y, mPaint);
348 lx = x;
349 ly = y;
350 }
351
352 mPaint.setARGB(255, 64, 255, 128);
353 xVel = ps.mAltXVelocity * (1000 / 60);
354 yVel = ps.mAltYVelocity * (1000 / 60);
355 canvas.drawLine(lastX, lastY, lastX + xVel, lastY + yVel, mPaint);
356 }
Jeff Brown70825162012-03-28 17:27:48 -0700357 }
358
359 if (mCurDown && ps.mCurDown) {
360 // Draw crosshairs.
361 canvas.drawLine(0, ps.mCoords.y, getWidth(), ps.mCoords.y, mTargetPaint);
362 canvas.drawLine(ps.mCoords.x, 0, ps.mCoords.x, getHeight(), mTargetPaint);
363
364 // Draw current point.
365 int pressureLevel = (int)(ps.mCoords.pressure * 255);
366 mPaint.setARGB(255, pressureLevel, 255, 255 - pressureLevel);
367 canvas.drawPoint(ps.mCoords.x, ps.mCoords.y, mPaint);
368
369 // Draw current touch ellipse.
370 mPaint.setARGB(255, pressureLevel, 255 - pressureLevel, 128);
371 drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.touchMajor,
372 ps.mCoords.touchMinor, ps.mCoords.orientation, mPaint);
373
374 // Draw current tool ellipse.
375 mPaint.setARGB(255, pressureLevel, 128, 255 - pressureLevel);
376 drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.toolMajor,
377 ps.mCoords.toolMinor, ps.mCoords.orientation, mPaint);
378
379 // Draw the orientation arrow.
380 float arrowSize = ps.mCoords.toolMajor * 0.7f;
381 if (arrowSize < 20) {
382 arrowSize = 20;
383 }
384 mPaint.setARGB(255, pressureLevel, 255, 0);
385 float orientationVectorX = (float) (Math.sin(ps.mCoords.orientation)
386 * arrowSize);
387 float orientationVectorY = (float) (-Math.cos(ps.mCoords.orientation)
388 * arrowSize);
389 if (ps.mToolType == MotionEvent.TOOL_TYPE_STYLUS
390 || ps.mToolType == MotionEvent.TOOL_TYPE_ERASER) {
391 // Show full circle orientation.
392 canvas.drawLine(ps.mCoords.x, ps.mCoords.y,
393 ps.mCoords.x + orientationVectorX,
394 ps.mCoords.y + orientationVectorY,
395 mPaint);
396 } else {
397 // Show half circle orientation.
398 canvas.drawLine(
399 ps.mCoords.x - orientationVectorX,
400 ps.mCoords.y - orientationVectorY,
401 ps.mCoords.x + orientationVectorX,
402 ps.mCoords.y + orientationVectorY,
403 mPaint);
404 }
405
406 // Draw the tilt point along the orientation arrow.
407 float tiltScale = (float) Math.sin(
408 ps.mCoords.getAxisValue(MotionEvent.AXIS_TILT));
409 canvas.drawCircle(
410 ps.mCoords.x + orientationVectorX * tiltScale,
411 ps.mCoords.y + orientationVectorY * tiltScale,
412 3.0f, mPaint);
Michael Wright86172f62013-05-15 23:16:54 -0700413
414 // Draw the current bounding box
415 if (ps.mHasBoundingBox) {
416 canvas.drawRect(ps.mBoundingLeft, ps.mBoundingTop,
417 ps.mBoundingRight, ps.mBoundingBottom, mPaint);
418 }
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800419 }
420 }
421 }
Jeff Brown65fd2512011-08-18 11:20:58 -0700422
423 private void logMotionEvent(String type, MotionEvent event) {
424 final int action = event.getAction();
425 final int N = event.getHistorySize();
426 final int NI = event.getPointerCount();
427 for (int historyPos = 0; historyPos < N; historyPos++) {
428 for (int i = 0; i < NI; i++) {
429 final int id = event.getPointerId(i);
430 event.getHistoricalPointerCoords(i, historyPos, mTempCoords);
Michael Wright86172f62013-05-15 23:16:54 -0700431 logCoords(type, action, i, mTempCoords, id, event);
Jeff Brown65fd2512011-08-18 11:20:58 -0700432 }
433 }
434 for (int i = 0; i < NI; i++) {
435 final int id = event.getPointerId(i);
436 event.getPointerCoords(i, mTempCoords);
Michael Wright86172f62013-05-15 23:16:54 -0700437 logCoords(type, action, i, mTempCoords, id, event);
Jeff Brown65fd2512011-08-18 11:20:58 -0700438 }
439 }
440
441 private void logCoords(String type, int action, int index,
Michael Wright86172f62013-05-15 23:16:54 -0700442 MotionEvent.PointerCoords coords, int id, MotionEvent event) {
443 final int toolType = event.getToolType(index);
444 final int buttonState = event.getButtonState();
Jeff Brown33bbfd22011-02-24 20:55:35 -0800445 final String prefix;
446 switch (action & MotionEvent.ACTION_MASK) {
447 case MotionEvent.ACTION_DOWN:
448 prefix = "DOWN";
449 break;
450 case MotionEvent.ACTION_UP:
451 prefix = "UP";
452 break;
453 case MotionEvent.ACTION_MOVE:
454 prefix = "MOVE";
455 break;
456 case MotionEvent.ACTION_CANCEL:
457 prefix = "CANCEL";
458 break;
459 case MotionEvent.ACTION_OUTSIDE:
460 prefix = "OUTSIDE";
461 break;
462 case MotionEvent.ACTION_POINTER_DOWN:
463 if (index == ((action & MotionEvent.ACTION_POINTER_INDEX_MASK)
464 >> MotionEvent.ACTION_POINTER_INDEX_SHIFT)) {
465 prefix = "DOWN";
466 } else {
467 prefix = "MOVE";
468 }
469 break;
470 case MotionEvent.ACTION_POINTER_UP:
471 if (index == ((action & MotionEvent.ACTION_POINTER_INDEX_MASK)
472 >> MotionEvent.ACTION_POINTER_INDEX_SHIFT)) {
473 prefix = "UP";
474 } else {
475 prefix = "MOVE";
476 }
477 break;
478 case MotionEvent.ACTION_HOVER_MOVE:
479 prefix = "HOVER MOVE";
480 break;
Jeff Browna032cc02011-03-07 16:56:21 -0800481 case MotionEvent.ACTION_HOVER_ENTER:
482 prefix = "HOVER ENTER";
483 break;
484 case MotionEvent.ACTION_HOVER_EXIT:
485 prefix = "HOVER EXIT";
486 break;
Jeff Brown33bbfd22011-02-24 20:55:35 -0800487 case MotionEvent.ACTION_SCROLL:
488 prefix = "SCROLL";
489 break;
490 default:
491 prefix = Integer.toString(action);
492 break;
493 }
494
Jeff Brown8d608662010-08-30 03:02:23 -0700495 Log.i(TAG, mText.clear()
Jeff Brown65fd2512011-08-18 11:20:58 -0700496 .append(type).append(" id ").append(id + 1)
Jeff Brown33bbfd22011-02-24 20:55:35 -0800497 .append(": ")
498 .append(prefix)
499 .append(" (").append(coords.x, 3).append(", ").append(coords.y, 3)
Jeff Brown8d608662010-08-30 03:02:23 -0700500 .append(") Pressure=").append(coords.pressure, 3)
501 .append(" Size=").append(coords.size, 3)
502 .append(" TouchMajor=").append(coords.touchMajor, 3)
503 .append(" TouchMinor=").append(coords.touchMinor, 3)
504 .append(" ToolMajor=").append(coords.toolMajor, 3)
505 .append(" ToolMinor=").append(coords.toolMinor, 3)
506 .append(" Orientation=").append((float)(coords.orientation * 180 / Math.PI), 1)
Jeff Brown6f2fba42011-02-19 01:08:02 -0800507 .append("deg")
Jeff Brown65fd2512011-08-18 11:20:58 -0700508 .append(" Tilt=").append((float)(
509 coords.getAxisValue(MotionEvent.AXIS_TILT) * 180 / Math.PI), 1)
510 .append("deg")
Jeff Brown80fd47c2011-05-24 01:07:44 -0700511 .append(" Distance=").append(coords.getAxisValue(MotionEvent.AXIS_DISTANCE), 1)
Jeff Brown6f2fba42011-02-19 01:08:02 -0800512 .append(" VScroll=").append(coords.getAxisValue(MotionEvent.AXIS_VSCROLL), 1)
513 .append(" HScroll=").append(coords.getAxisValue(MotionEvent.AXIS_HSCROLL), 1)
Michael Wright86172f62013-05-15 23:16:54 -0700514 .append(" BoundingBox=[(")
515 .append(event.getAxisValue(MotionEvent.AXIS_GENERIC_1), 3)
516 .append(", ").append(event.getAxisValue(MotionEvent.AXIS_GENERIC_2), 3).append(")")
517 .append(", (").append(event.getAxisValue(MotionEvent.AXIS_GENERIC_3), 3)
518 .append(", ").append(event.getAxisValue(MotionEvent.AXIS_GENERIC_4), 3)
519 .append(")]")
Jeff Brownfe9f8ab2011-05-06 18:20:01 -0700520 .append(" ToolType=").append(MotionEvent.toolTypeToString(toolType))
521 .append(" ButtonState=").append(MotionEvent.buttonStateToString(buttonState))
Jeff Brown6f2fba42011-02-19 01:08:02 -0800522 .toString());
Jeff Brown8d608662010-08-30 03:02:23 -0700523 }
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800524
Michael Wrightc9c487e2013-11-07 18:55:09 -0800525 @Override
526 public void onPointerEvent(MotionEvent event) {
Jeff Brown70825162012-03-28 17:27:48 -0700527 final int action = event.getAction();
528 int NP = mPointers.size();
Jeff Brownfe9f8ab2011-05-06 18:20:01 -0700529
Jeff Brown70825162012-03-28 17:27:48 -0700530 if (action == MotionEvent.ACTION_DOWN
531 || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN) {
532 final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
533 >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for down
534 if (action == MotionEvent.ACTION_DOWN) {
535 for (int p=0; p<NP; p++) {
536 final PointerState ps = mPointers.get(p);
537 ps.clearTrace();
538 ps.mCurDown = false;
Jeff Brown8d608662010-08-30 03:02:23 -0700539 }
Jeff Brown70825162012-03-28 17:27:48 -0700540 mCurDown = true;
541 mCurNumPointers = 0;
542 mMaxNumPointers = 0;
543 mVelocity.clear();
Jeff Brown9eb7d862012-06-01 12:39:25 -0700544 if (mAltVelocity != null) {
545 mAltVelocity.clear();
546 }
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800547 }
Jeff Browncc0c1592011-02-19 05:07:28 -0800548
Jeff Brown70825162012-03-28 17:27:48 -0700549 mCurNumPointers += 1;
550 if (mMaxNumPointers < mCurNumPointers) {
551 mMaxNumPointers = mCurNumPointers;
Jeff Brown33bbfd22011-02-24 20:55:35 -0800552 }
Jeff Brown70825162012-03-28 17:27:48 -0700553
554 final int id = event.getPointerId(index);
555 while (NP <= id) {
556 PointerState ps = new PointerState();
557 mPointers.add(ps);
558 NP++;
559 }
560
561 if (mActivePointerId < 0 ||
562 !mPointers.get(mActivePointerId).mCurDown) {
563 mActivePointerId = id;
564 }
565
566 final PointerState ps = mPointers.get(id);
567 ps.mCurDown = true;
Michael Wright2f1cd7e2013-06-11 20:50:19 -0700568 InputDevice device = InputDevice.getDevice(event.getDeviceId());
569 ps.mHasBoundingBox = device != null &&
570 device.getMotionRange(MotionEvent.AXIS_GENERIC_1) != null;
Jeff Brown70825162012-03-28 17:27:48 -0700571 }
572
573 final int NI = event.getPointerCount();
574
575 mVelocity.addMovement(event);
576 mVelocity.computeCurrentVelocity(1);
Jeff Brown9eb7d862012-06-01 12:39:25 -0700577 if (mAltVelocity != null) {
578 mAltVelocity.addMovement(event);
579 mAltVelocity.computeCurrentVelocity(1);
580 }
Jeff Brown70825162012-03-28 17:27:48 -0700581
582 final int N = event.getHistorySize();
583 for (int historyPos = 0; historyPos < N; historyPos++) {
Jeff Brown33bbfd22011-02-24 20:55:35 -0800584 for (int i = 0; i < NI; i++) {
585 final int id = event.getPointerId(i);
586 final PointerState ps = mCurDown ? mPointers.get(id) : null;
Jeff Brown65fd2512011-08-18 11:20:58 -0700587 final PointerCoords coords = ps != null ? ps.mCoords : mTempCoords;
Jeff Brown70825162012-03-28 17:27:48 -0700588 event.getHistoricalPointerCoords(i, historyPos, coords);
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800589 if (mPrintCoords) {
Michael Wright86172f62013-05-15 23:16:54 -0700590 logCoords("Pointer", action, i, coords, id, event);
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800591 }
Jeff Browncc0c1592011-02-19 05:07:28 -0800592 if (ps != null) {
Michael Wright76936eb2013-06-21 15:18:06 -0700593 ps.addTrace(coords.x, coords.y, false);
Jeff Browncc0c1592011-02-19 05:07:28 -0800594 }
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800595 }
Jeff Brown70825162012-03-28 17:27:48 -0700596 }
597 for (int i = 0; i < NI; i++) {
598 final int id = event.getPointerId(i);
599 final PointerState ps = mCurDown ? mPointers.get(id) : null;
600 final PointerCoords coords = ps != null ? ps.mCoords : mTempCoords;
601 event.getPointerCoords(i, coords);
602 if (mPrintCoords) {
Michael Wright86172f62013-05-15 23:16:54 -0700603 logCoords("Pointer", action, i, coords, id, event);
Jeff Brown70825162012-03-28 17:27:48 -0700604 }
605 if (ps != null) {
Michael Wright76936eb2013-06-21 15:18:06 -0700606 ps.addTrace(coords.x, coords.y, true);
Jeff Brown70825162012-03-28 17:27:48 -0700607 ps.mXVelocity = mVelocity.getXVelocity(id);
608 ps.mYVelocity = mVelocity.getYVelocity(id);
Jeff Brown85bd0d62012-05-13 15:30:42 -0700609 mVelocity.getEstimator(id, ps.mEstimator);
Jeff Brown9eb7d862012-06-01 12:39:25 -0700610 if (mAltVelocity != null) {
611 ps.mAltXVelocity = mAltVelocity.getXVelocity(id);
612 ps.mAltYVelocity = mAltVelocity.getYVelocity(id);
613 mAltVelocity.getEstimator(id, ps.mAltEstimator);
614 }
Jeff Brown70825162012-03-28 17:27:48 -0700615 ps.mToolType = event.getToolType(i);
Michael Wright86172f62013-05-15 23:16:54 -0700616
617 if (ps.mHasBoundingBox) {
618 ps.mBoundingLeft = event.getAxisValue(MotionEvent.AXIS_GENERIC_1, i);
619 ps.mBoundingTop = event.getAxisValue(MotionEvent.AXIS_GENERIC_2, i);
620 ps.mBoundingRight = event.getAxisValue(MotionEvent.AXIS_GENERIC_3, i);
621 ps.mBoundingBottom = event.getAxisValue(MotionEvent.AXIS_GENERIC_4, i);
622 }
Jeff Brown70825162012-03-28 17:27:48 -0700623 }
624 }
625
626 if (action == MotionEvent.ACTION_UP
627 || action == MotionEvent.ACTION_CANCEL
628 || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP) {
629 final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
630 >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for UP
631
632 final int id = event.getPointerId(index);
633 final PointerState ps = mPointers.get(id);
634 ps.mCurDown = false;
Jeff Brown33bbfd22011-02-24 20:55:35 -0800635
Jeff Brown8d608662010-08-30 03:02:23 -0700636 if (action == MotionEvent.ACTION_UP
Jeff Brown70825162012-03-28 17:27:48 -0700637 || action == MotionEvent.ACTION_CANCEL) {
638 mCurDown = false;
639 mCurNumPointers = 0;
640 } else {
641 mCurNumPointers -= 1;
642 if (mActivePointerId == id) {
643 mActivePointerId = event.getPointerId(index == 0 ? 1 : 0);
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800644 }
Michael Wright76936eb2013-06-21 15:18:06 -0700645 ps.addTrace(Float.NaN, Float.NaN, false);
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800646 }
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800647 }
Jeff Brown70825162012-03-28 17:27:48 -0700648
649 invalidate();
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800650 }
651
652 @Override
653 public boolean onTouchEvent(MotionEvent event) {
Michael Wrightc9c487e2013-11-07 18:55:09 -0800654 onPointerEvent(event);
Jeff Brownc3fe7662011-03-07 14:35:59 -0800655
656 if (event.getAction() == MotionEvent.ACTION_DOWN && !isFocused()) {
657 requestFocus();
658 }
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800659 return true;
660 }
Dianne Hackborn7d9af5a2010-03-18 23:40:21 -0700661
662 @Override
Jeff Brown33bbfd22011-02-24 20:55:35 -0800663 public boolean onGenericMotionEvent(MotionEvent event) {
Jeff Brownc3fe7662011-03-07 14:35:59 -0800664 final int source = event.getSource();
665 if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
Michael Wrightc9c487e2013-11-07 18:55:09 -0800666 onPointerEvent(event);
Jeff Brownc3fe7662011-03-07 14:35:59 -0800667 } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
Jeff Brown65fd2512011-08-18 11:20:58 -0700668 logMotionEvent("Joystick", event);
Jeff Brownc3fe7662011-03-07 14:35:59 -0800669 } else if ((source & InputDevice.SOURCE_CLASS_POSITION) != 0) {
Jeff Brown65fd2512011-08-18 11:20:58 -0700670 logMotionEvent("Position", event);
Jeff Brownc3fe7662011-03-07 14:35:59 -0800671 } else {
Jeff Brown65fd2512011-08-18 11:20:58 -0700672 logMotionEvent("Generic", event);
Jeff Brownc3fe7662011-03-07 14:35:59 -0800673 }
674 return true;
675 }
676
677 @Override
678 public boolean onKeyDown(int keyCode, KeyEvent event) {
679 if (shouldLogKey(keyCode)) {
680 final int repeatCount = event.getRepeatCount();
681 if (repeatCount == 0) {
682 Log.i(TAG, "Key Down: " + event);
683 } else {
684 Log.i(TAG, "Key Repeat #" + repeatCount + ": " + event);
685 }
Jeff Brown33bbfd22011-02-24 20:55:35 -0800686 return true;
687 }
Jeff Brownc3fe7662011-03-07 14:35:59 -0800688 return super.onKeyDown(keyCode, event);
689 }
690
691 @Override
692 public boolean onKeyUp(int keyCode, KeyEvent event) {
693 if (shouldLogKey(keyCode)) {
694 Log.i(TAG, "Key Up: " + event);
695 return true;
696 }
697 return super.onKeyUp(keyCode, event);
698 }
699
700 private static boolean shouldLogKey(int keyCode) {
701 switch (keyCode) {
702 case KeyEvent.KEYCODE_DPAD_UP:
703 case KeyEvent.KEYCODE_DPAD_DOWN:
704 case KeyEvent.KEYCODE_DPAD_LEFT:
705 case KeyEvent.KEYCODE_DPAD_RIGHT:
706 case KeyEvent.KEYCODE_DPAD_CENTER:
707 return true;
708 default:
709 return KeyEvent.isGamepadButton(keyCode)
710 || KeyEvent.isModifierKey(keyCode);
711 }
Jeff Brown33bbfd22011-02-24 20:55:35 -0800712 }
713
714 @Override
Dianne Hackborn7d9af5a2010-03-18 23:40:21 -0700715 public boolean onTrackballEvent(MotionEvent event) {
Jeff Brown65fd2512011-08-18 11:20:58 -0700716 logMotionEvent("Trackball", event);
Jeff Brownc3fe7662011-03-07 14:35:59 -0800717 return true;
Dianne Hackborn7d9af5a2010-03-18 23:40:21 -0700718 }
Jeff Brownaf9e8d32012-04-12 17:32:48 -0700719
720 @Override
721 protected void onAttachedToWindow() {
722 super.onAttachedToWindow();
723
724 mIm.registerInputDeviceListener(this, getHandler());
725 logInputDevices();
726 }
727
728 @Override
729 protected void onDetachedFromWindow() {
730 super.onDetachedFromWindow();
731
732 mIm.unregisterInputDeviceListener(this);
733 }
734
735 @Override
736 public void onInputDeviceAdded(int deviceId) {
737 logInputDeviceState(deviceId, "Device Added");
738 }
739
740 @Override
741 public void onInputDeviceChanged(int deviceId) {
742 logInputDeviceState(deviceId, "Device Changed");
743 }
744
745 @Override
746 public void onInputDeviceRemoved(int deviceId) {
747 logInputDeviceState(deviceId, "Device Removed");
748 }
749
750 private void logInputDevices() {
751 int[] deviceIds = InputDevice.getDeviceIds();
752 for (int i = 0; i < deviceIds.length; i++) {
753 logInputDeviceState(deviceIds[i], "Device Enumerated");
754 }
755 }
756
757 private void logInputDeviceState(int deviceId, String state) {
758 InputDevice device = mIm.getInputDevice(deviceId);
759 if (device != null) {
760 Log.i(TAG, state + ": " + device);
761 } else {
762 Log.i(TAG, state + ": " + deviceId);
763 }
764 }
765
Jeff Brown8d608662010-08-30 03:02:23 -0700766 // HACK
767 // A quick and dirty string builder implementation optimized for GC.
Jeff Brownd1e0c372010-09-12 19:14:26 -0700768 // Using String.format causes the application grind to a halt when
769 // more than a couple of pointers are down due to the number of
770 // temporary objects allocated while formatting strings for drawing or logging.
Jeff Brown8d608662010-08-30 03:02:23 -0700771 private static final class FasterStringBuilder {
772 private char[] mChars;
773 private int mLength;
774
775 public FasterStringBuilder() {
776 mChars = new char[64];
777 }
778
779 public FasterStringBuilder clear() {
780 mLength = 0;
781 return this;
782 }
783
784 public FasterStringBuilder append(String value) {
785 final int valueLength = value.length();
786 final int index = reserve(valueLength);
787 value.getChars(0, valueLength, mChars, index);
788 mLength += valueLength;
789 return this;
790 }
791
792 public FasterStringBuilder append(int value) {
793 return append(value, 0);
794 }
795
796 public FasterStringBuilder append(int value, int zeroPadWidth) {
797 final boolean negative = value < 0;
798 if (negative) {
799 value = - value;
800 if (value < 0) {
801 append("-2147483648");
802 return this;
803 }
804 }
805
806 int index = reserve(11);
807 final char[] chars = mChars;
808
809 if (value == 0) {
810 chars[index++] = '0';
811 mLength += 1;
812 return this;
813 }
814
815 if (negative) {
816 chars[index++] = '-';
817 }
818
819 int divisor = 1000000000;
820 int numberWidth = 10;
821 while (value < divisor) {
822 divisor /= 10;
823 numberWidth -= 1;
824 if (numberWidth < zeroPadWidth) {
825 chars[index++] = '0';
826 }
827 }
828
829 do {
830 int digit = value / divisor;
831 value -= digit * divisor;
832 divisor /= 10;
833 chars[index++] = (char) (digit + '0');
834 } while (divisor != 0);
835
836 mLength = index;
837 return this;
838 }
839
840 public FasterStringBuilder append(float value, int precision) {
841 int scale = 1;
842 for (int i = 0; i < precision; i++) {
843 scale *= 10;
844 }
845 value = (float) (Math.rint(value * scale) / scale);
846
847 append((int) value);
848
849 if (precision != 0) {
850 append(".");
851 value = Math.abs(value);
852 value -= Math.floor(value);
853 append((int) (value * scale), precision);
854 }
855
856 return this;
857 }
858
859 @Override
860 public String toString() {
861 return new String(mChars, 0, mLength);
862 }
863
864 private int reserve(int length) {
865 final int oldLength = mLength;
866 final int newLength = mLength + length;
867 final char[] oldChars = mChars;
868 final int oldCapacity = oldChars.length;
869 if (newLength > oldCapacity) {
870 final int newCapacity = oldCapacity * 2;
871 final char[] newChars = new char[newCapacity];
872 System.arraycopy(oldChars, 0, newChars, 0, oldLength);
873 mChars = newChars;
874 }
875 return oldLength;
876 }
877 }
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800878}