blob: d82831f8ceac1be01d208149f09b50cb010bea47 [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;
Jeff Browncc0c1592011-02-19 05:07:28 -080034import android.view.MotionEvent.PointerCoords;
Dianne Hackborn90d2db32010-02-11 22:19:06 -080035
36import java.util.ArrayList;
37
Jeff Brownaf9e8d32012-04-12 17:32:48 -070038public class PointerLocationView extends View implements InputDeviceListener {
Jeff Brown8d608662010-08-30 03:02:23 -070039 private static final String TAG = "Pointer";
Jeff Brown9eb7d862012-06-01 12:39:25 -070040
41 // The system property key used to specify an alternate velocity tracker strategy
42 // to plot alongside the default one. Useful for testing and comparison purposes.
43 private static final String ALT_STRATEGY_PROPERY_KEY = "debug.velocitytracker.alt";
44
Dianne Hackborn90d2db32010-02-11 22:19:06 -080045 public static class PointerState {
Jeff Brown8d608662010-08-30 03:02:23 -070046 // Trace of previous points.
47 private float[] mTraceX = new float[32];
48 private float[] mTraceY = new float[32];
Michael Wright76936eb2013-06-21 15:18:06 -070049 private boolean[] mTraceCurrent = new boolean[32];
Jeff Brown8d608662010-08-30 03:02:23 -070050 private int mTraceCount;
51
52 // True if the pointer is down.
Dianne Hackborn90d2db32010-02-11 22:19:06 -080053 private boolean mCurDown;
Jeff Brown8d608662010-08-30 03:02:23 -070054
55 // Most recent coordinates.
Jeff Browncc0c1592011-02-19 05:07:28 -080056 private PointerCoords mCoords = new PointerCoords();
Jeff Brown65fd2512011-08-18 11:20:58 -070057 private int mToolType;
Jeff Brown8d608662010-08-30 03:02:23 -070058
59 // Most recent velocity.
Jeff Brown9e2ad362010-07-30 19:20:11 -070060 private float mXVelocity;
61 private float mYVelocity;
Jeff Brown9eb7d862012-06-01 12:39:25 -070062 private float mAltXVelocity;
63 private float mAltYVelocity;
Jeff Brownb59ab9f2011-09-14 10:53:18 -070064
Michael Wright86172f62013-05-15 23:16:54 -070065 // Current bounding box, if any
66 private boolean mHasBoundingBox;
67 private float mBoundingLeft;
68 private float mBoundingTop;
69 private float mBoundingRight;
70 private float mBoundingBottom;
71
Jeff Brownb59ab9f2011-09-14 10:53:18 -070072 // Position estimator.
73 private VelocityTracker.Estimator mEstimator = new VelocityTracker.Estimator();
Jeff Brown9eb7d862012-06-01 12:39:25 -070074 private VelocityTracker.Estimator mAltEstimator = new VelocityTracker.Estimator();
Jeff Brownb59ab9f2011-09-14 10:53:18 -070075
Jeff Brown8d608662010-08-30 03:02:23 -070076 public void clearTrace() {
77 mTraceCount = 0;
78 }
79
Michael Wright76936eb2013-06-21 15:18:06 -070080 public void addTrace(float x, float y, boolean current) {
Jeff Brown8d608662010-08-30 03:02:23 -070081 int traceCapacity = mTraceX.length;
82 if (mTraceCount == traceCapacity) {
83 traceCapacity *= 2;
84 float[] newTraceX = new float[traceCapacity];
85 System.arraycopy(mTraceX, 0, newTraceX, 0, mTraceCount);
86 mTraceX = newTraceX;
87
88 float[] newTraceY = new float[traceCapacity];
89 System.arraycopy(mTraceY, 0, newTraceY, 0, mTraceCount);
90 mTraceY = newTraceY;
Michael Wright76936eb2013-06-21 15:18:06 -070091
92 boolean[] newTraceCurrent = new boolean[traceCapacity];
93 System.arraycopy(mTraceCurrent, 0, newTraceCurrent, 0, mTraceCount);
94 mTraceCurrent= newTraceCurrent;
Jeff Brown8d608662010-08-30 03:02:23 -070095 }
96
97 mTraceX[mTraceCount] = x;
98 mTraceY[mTraceCount] = y;
Michael Wright76936eb2013-06-21 15:18:06 -070099 mTraceCurrent[mTraceCount] = current;
Jeff Brown8d608662010-08-30 03:02:23 -0700100 mTraceCount += 1;
101 }
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800102 }
103
Jeff Brownb59ab9f2011-09-14 10:53:18 -0700104 private final int ESTIMATE_PAST_POINTS = 4;
105 private final int ESTIMATE_FUTURE_POINTS = 2;
106 private final float ESTIMATE_INTERVAL = 0.02f;
107
Jeff Brownaf9e8d32012-04-12 17:32:48 -0700108 private final InputManager mIm;
109
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800110 private final ViewConfiguration mVC;
111 private final Paint mTextPaint;
112 private final Paint mTextBackgroundPaint;
113 private final Paint mTextLevelPaint;
114 private final Paint mPaint;
Michael Wright76936eb2013-06-21 15:18:06 -0700115 private final Paint mCurrentPointPaint;
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800116 private final Paint mTargetPaint;
117 private final Paint mPathPaint;
118 private final FontMetricsInt mTextMetrics = new FontMetricsInt();
119 private int mHeaderBottom;
120 private boolean mCurDown;
121 private int mCurNumPointers;
122 private int mMaxNumPointers;
Jeff Brownd1e0c372010-09-12 19:14:26 -0700123 private int mActivePointerId;
Jeff Brown8d608662010-08-30 03:02:23 -0700124 private final ArrayList<PointerState> mPointers = new ArrayList<PointerState>();
Jeff Brown65fd2512011-08-18 11:20:58 -0700125 private final PointerCoords mTempCoords = new PointerCoords();
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800126
Jeff Brown9e2ad362010-07-30 19:20:11 -0700127 private final VelocityTracker mVelocity;
Jeff Brown9eb7d862012-06-01 12:39:25 -0700128 private final VelocityTracker mAltVelocity;
129
Jeff Brown8d608662010-08-30 03:02:23 -0700130 private final FasterStringBuilder mText = new FasterStringBuilder();
131
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800132 private boolean mPrintCoords = true;
133
134 public PointerLocationView(Context c) {
135 super(c);
Jeff Brownc3fe7662011-03-07 14:35:59 -0800136 setFocusableInTouchMode(true);
137
Jeff Brownaf9e8d32012-04-12 17:32:48 -0700138 mIm = (InputManager)c.getSystemService(Context.INPUT_SERVICE);
139
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800140 mVC = ViewConfiguration.get(c);
141 mTextPaint = new Paint();
142 mTextPaint.setAntiAlias(true);
143 mTextPaint.setTextSize(10
144 * getResources().getDisplayMetrics().density);
145 mTextPaint.setARGB(255, 0, 0, 0);
146 mTextBackgroundPaint = new Paint();
147 mTextBackgroundPaint.setAntiAlias(false);
148 mTextBackgroundPaint.setARGB(128, 255, 255, 255);
149 mTextLevelPaint = new Paint();
150 mTextLevelPaint.setAntiAlias(false);
151 mTextLevelPaint.setARGB(192, 255, 0, 0);
152 mPaint = new Paint();
153 mPaint.setAntiAlias(true);
154 mPaint.setARGB(255, 255, 255, 255);
155 mPaint.setStyle(Paint.Style.STROKE);
156 mPaint.setStrokeWidth(2);
Michael Wright76936eb2013-06-21 15:18:06 -0700157 mCurrentPointPaint = new Paint();
158 mCurrentPointPaint.setAntiAlias(true);
159 mCurrentPointPaint.setARGB(255, 255, 0, 0);
160 mCurrentPointPaint.setStyle(Paint.Style.STROKE);
161 mCurrentPointPaint.setStrokeWidth(2);
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800162 mTargetPaint = new Paint();
163 mTargetPaint.setAntiAlias(false);
164 mTargetPaint.setARGB(255, 0, 0, 192);
165 mPathPaint = new Paint();
166 mPathPaint.setAntiAlias(false);
167 mPathPaint.setARGB(255, 0, 96, 255);
168 mPaint.setStyle(Paint.Style.STROKE);
169 mPaint.setStrokeWidth(1);
170
171 PointerState ps = new PointerState();
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800172 mPointers.add(ps);
Jeff Brownd1e0c372010-09-12 19:14:26 -0700173 mActivePointerId = 0;
Jeff Brown9e2ad362010-07-30 19:20:11 -0700174
175 mVelocity = VelocityTracker.obtain();
Jeff Brown9eb7d862012-06-01 12:39:25 -0700176
177 String altStrategy = SystemProperties.get(ALT_STRATEGY_PROPERY_KEY);
178 if (altStrategy.length() != 0) {
179 Log.d(TAG, "Comparing default velocity tracker strategy with " + altStrategy);
180 mAltVelocity = VelocityTracker.obtain(altStrategy);
181 } else {
182 mAltVelocity = null;
183 }
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800184 }
185
186 public void setPrintCoords(boolean state) {
187 mPrintCoords = state;
188 }
189
190 @Override
191 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
192 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
193 mTextPaint.getFontMetricsInt(mTextMetrics);
194 mHeaderBottom = -mTextMetrics.ascent+mTextMetrics.descent+2;
195 if (false) {
196 Log.i("foo", "Metrics: ascent=" + mTextMetrics.ascent
197 + " descent=" + mTextMetrics.descent
198 + " leading=" + mTextMetrics.leading
199 + " top=" + mTextMetrics.top
200 + " bottom=" + mTextMetrics.bottom);
201 }
202 }
Jeff Brown8d608662010-08-30 03:02:23 -0700203
204 // Draw an oval. When angle is 0 radians, orients the major axis vertically,
205 // angles less than or greater than 0 radians rotate the major axis left or right.
206 private RectF mReusableOvalRect = new RectF();
207 private void drawOval(Canvas canvas, float x, float y, float major, float minor,
208 float angle, Paint paint) {
209 canvas.save(Canvas.MATRIX_SAVE_FLAG);
Jeff Brown5068ad82010-09-29 20:14:56 -0700210 canvas.rotate((float) (angle * 180 / Math.PI), x, y);
Jeff Brown8d608662010-08-30 03:02:23 -0700211 mReusableOvalRect.left = x - minor / 2;
212 mReusableOvalRect.right = x + minor / 2;
213 mReusableOvalRect.top = y - major / 2;
214 mReusableOvalRect.bottom = y + major / 2;
215 canvas.drawOval(mReusableOvalRect, paint);
216 canvas.restore();
217 }
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800218
219 @Override
220 protected void onDraw(Canvas canvas) {
Jeff Brown70825162012-03-28 17:27:48 -0700221 final int w = getWidth();
222 final int itemW = w/7;
223 final int base = -mTextMetrics.ascent+1;
224 final int bottom = mHeaderBottom;
Jeff Brownd1e0c372010-09-12 19:14:26 -0700225
Jeff Brown70825162012-03-28 17:27:48 -0700226 final int NP = mPointers.size();
227
228 // Labels
229 if (mActivePointerId >= 0) {
230 final PointerState ps = mPointers.get(mActivePointerId);
231
232 canvas.drawRect(0, 0, itemW-1, bottom,mTextBackgroundPaint);
233 canvas.drawText(mText.clear()
234 .append("P: ").append(mCurNumPointers)
235 .append(" / ").append(mMaxNumPointers)
236 .toString(), 1, base, mTextPaint);
237
238 final int N = ps.mTraceCount;
239 if ((mCurDown && ps.mCurDown) || N == 0) {
240 canvas.drawRect(itemW, 0, (itemW * 2) - 1, bottom, mTextBackgroundPaint);
Jeff Brown8d608662010-08-30 03:02:23 -0700241 canvas.drawText(mText.clear()
Jeff Brown70825162012-03-28 17:27:48 -0700242 .append("X: ").append(ps.mCoords.x, 1)
243 .toString(), 1 + itemW, base, mTextPaint);
244 canvas.drawRect(itemW * 2, 0, (itemW * 3) - 1, bottom, mTextBackgroundPaint);
Jeff Brown8d608662010-08-30 03:02:23 -0700245 canvas.drawText(mText.clear()
Jeff Brown70825162012-03-28 17:27:48 -0700246 .append("Y: ").append(ps.mCoords.y, 1)
247 .toString(), 1 + itemW * 2, base, mTextPaint);
248 } else {
249 float dx = ps.mTraceX[N - 1] - ps.mTraceX[0];
250 float dy = ps.mTraceY[N - 1] - ps.mTraceY[0];
251 canvas.drawRect(itemW, 0, (itemW * 2) - 1, bottom,
252 Math.abs(dx) < mVC.getScaledTouchSlop()
253 ? mTextBackgroundPaint : mTextLevelPaint);
Jeff Brown8d608662010-08-30 03:02:23 -0700254 canvas.drawText(mText.clear()
Jeff Brown70825162012-03-28 17:27:48 -0700255 .append("dX: ").append(dx, 1)
256 .toString(), 1 + itemW, base, mTextPaint);
257 canvas.drawRect(itemW * 2, 0, (itemW * 3) - 1, bottom,
258 Math.abs(dy) < mVC.getScaledTouchSlop()
259 ? mTextBackgroundPaint : mTextLevelPaint);
Jeff Brown8d608662010-08-30 03:02:23 -0700260 canvas.drawText(mText.clear()
Jeff Brown70825162012-03-28 17:27:48 -0700261 .append("dY: ").append(dy, 1)
262 .toString(), 1 + itemW * 2, base, mTextPaint);
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800263 }
Jeff Brownb59ab9f2011-09-14 10:53:18 -0700264
Jeff Brown70825162012-03-28 17:27:48 -0700265 canvas.drawRect(itemW * 3, 0, (itemW * 4) - 1, bottom, mTextBackgroundPaint);
266 canvas.drawText(mText.clear()
267 .append("Xv: ").append(ps.mXVelocity, 3)
268 .toString(), 1 + itemW * 3, base, mTextPaint);
Jeff Brown517bb4c2011-01-14 19:09:23 -0800269
Jeff Brown70825162012-03-28 17:27:48 -0700270 canvas.drawRect(itemW * 4, 0, (itemW * 5) - 1, bottom, mTextBackgroundPaint);
271 canvas.drawText(mText.clear()
272 .append("Yv: ").append(ps.mYVelocity, 3)
273 .toString(), 1 + itemW * 4, base, mTextPaint);
Jeff Brown65fd2512011-08-18 11:20:58 -0700274
Jeff Brown70825162012-03-28 17:27:48 -0700275 canvas.drawRect(itemW * 5, 0, (itemW * 6) - 1, bottom, mTextBackgroundPaint);
276 canvas.drawRect(itemW * 5, 0, (itemW * 5) + (ps.mCoords.pressure * itemW) - 1,
277 bottom, mTextLevelPaint);
278 canvas.drawText(mText.clear()
279 .append("Prs: ").append(ps.mCoords.pressure, 2)
280 .toString(), 1 + itemW * 5, base, mTextPaint);
281
282 canvas.drawRect(itemW * 6, 0, w, bottom, mTextBackgroundPaint);
283 canvas.drawRect(itemW * 6, 0, (itemW * 6) + (ps.mCoords.size * itemW) - 1,
284 bottom, mTextLevelPaint);
285 canvas.drawText(mText.clear()
286 .append("Size: ").append(ps.mCoords.size, 2)
287 .toString(), 1 + itemW * 6, base, mTextPaint);
288 }
289
290 // Pointer trace.
291 for (int p = 0; p < NP; p++) {
292 final PointerState ps = mPointers.get(p);
293
294 // Draw path.
295 final int N = ps.mTraceCount;
296 float lastX = 0, lastY = 0;
297 boolean haveLast = false;
298 boolean drawn = false;
299 mPaint.setARGB(255, 128, 255, 255);
300 for (int i=0; i < N; i++) {
301 float x = ps.mTraceX[i];
302 float y = ps.mTraceY[i];
303 if (Float.isNaN(x)) {
304 haveLast = false;
305 continue;
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800306 }
Jeff Brown70825162012-03-28 17:27:48 -0700307 if (haveLast) {
308 canvas.drawLine(lastX, lastY, x, y, mPathPaint);
Michael Wright76936eb2013-06-21 15:18:06 -0700309 final Paint paint = ps.mTraceCurrent[i] ? mCurrentPointPaint : mPaint;
310 canvas.drawPoint(lastX, lastY, paint);
Jeff Brown70825162012-03-28 17:27:48 -0700311 drawn = true;
312 }
313 lastX = x;
314 lastY = y;
315 haveLast = true;
316 }
317
318 if (drawn) {
319 // Draw movement estimate curve.
320 mPaint.setARGB(128, 128, 0, 128);
321 float lx = ps.mEstimator.estimateX(-ESTIMATE_PAST_POINTS * ESTIMATE_INTERVAL);
322 float ly = ps.mEstimator.estimateY(-ESTIMATE_PAST_POINTS * ESTIMATE_INTERVAL);
323 for (int i = -ESTIMATE_PAST_POINTS + 1; i <= ESTIMATE_FUTURE_POINTS; i++) {
324 float x = ps.mEstimator.estimateX(i * ESTIMATE_INTERVAL);
325 float y = ps.mEstimator.estimateY(i * ESTIMATE_INTERVAL);
326 canvas.drawLine(lx, ly, x, y, mPaint);
327 lx = x;
328 ly = y;
329 }
330
331 // Draw velocity vector.
332 mPaint.setARGB(255, 255, 64, 128);
333 float xVel = ps.mXVelocity * (1000 / 60);
334 float yVel = ps.mYVelocity * (1000 / 60);
335 canvas.drawLine(lastX, lastY, lastX + xVel, lastY + yVel, mPaint);
Jeff Brown9eb7d862012-06-01 12:39:25 -0700336
337 // Draw alternate estimate.
338 if (mAltVelocity != null) {
339 mPaint.setARGB(128, 0, 128, 128);
340 lx = ps.mAltEstimator.estimateX(-ESTIMATE_PAST_POINTS * ESTIMATE_INTERVAL);
341 ly = ps.mAltEstimator.estimateY(-ESTIMATE_PAST_POINTS * ESTIMATE_INTERVAL);
342 for (int i = -ESTIMATE_PAST_POINTS + 1; i <= ESTIMATE_FUTURE_POINTS; i++) {
343 float x = ps.mAltEstimator.estimateX(i * ESTIMATE_INTERVAL);
344 float y = ps.mAltEstimator.estimateY(i * ESTIMATE_INTERVAL);
345 canvas.drawLine(lx, ly, x, y, mPaint);
346 lx = x;
347 ly = y;
348 }
349
350 mPaint.setARGB(255, 64, 255, 128);
351 xVel = ps.mAltXVelocity * (1000 / 60);
352 yVel = ps.mAltYVelocity * (1000 / 60);
353 canvas.drawLine(lastX, lastY, lastX + xVel, lastY + yVel, mPaint);
354 }
Jeff Brown70825162012-03-28 17:27:48 -0700355 }
356
357 if (mCurDown && ps.mCurDown) {
358 // Draw crosshairs.
359 canvas.drawLine(0, ps.mCoords.y, getWidth(), ps.mCoords.y, mTargetPaint);
360 canvas.drawLine(ps.mCoords.x, 0, ps.mCoords.x, getHeight(), mTargetPaint);
361
362 // Draw current point.
363 int pressureLevel = (int)(ps.mCoords.pressure * 255);
364 mPaint.setARGB(255, pressureLevel, 255, 255 - pressureLevel);
365 canvas.drawPoint(ps.mCoords.x, ps.mCoords.y, mPaint);
366
367 // Draw current touch ellipse.
368 mPaint.setARGB(255, pressureLevel, 255 - pressureLevel, 128);
369 drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.touchMajor,
370 ps.mCoords.touchMinor, ps.mCoords.orientation, mPaint);
371
372 // Draw current tool ellipse.
373 mPaint.setARGB(255, pressureLevel, 128, 255 - pressureLevel);
374 drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.toolMajor,
375 ps.mCoords.toolMinor, ps.mCoords.orientation, mPaint);
376
377 // Draw the orientation arrow.
378 float arrowSize = ps.mCoords.toolMajor * 0.7f;
379 if (arrowSize < 20) {
380 arrowSize = 20;
381 }
382 mPaint.setARGB(255, pressureLevel, 255, 0);
383 float orientationVectorX = (float) (Math.sin(ps.mCoords.orientation)
384 * arrowSize);
385 float orientationVectorY = (float) (-Math.cos(ps.mCoords.orientation)
386 * arrowSize);
387 if (ps.mToolType == MotionEvent.TOOL_TYPE_STYLUS
388 || ps.mToolType == MotionEvent.TOOL_TYPE_ERASER) {
389 // Show full circle orientation.
390 canvas.drawLine(ps.mCoords.x, ps.mCoords.y,
391 ps.mCoords.x + orientationVectorX,
392 ps.mCoords.y + orientationVectorY,
393 mPaint);
394 } else {
395 // Show half circle orientation.
396 canvas.drawLine(
397 ps.mCoords.x - orientationVectorX,
398 ps.mCoords.y - orientationVectorY,
399 ps.mCoords.x + orientationVectorX,
400 ps.mCoords.y + orientationVectorY,
401 mPaint);
402 }
403
404 // Draw the tilt point along the orientation arrow.
405 float tiltScale = (float) Math.sin(
406 ps.mCoords.getAxisValue(MotionEvent.AXIS_TILT));
407 canvas.drawCircle(
408 ps.mCoords.x + orientationVectorX * tiltScale,
409 ps.mCoords.y + orientationVectorY * tiltScale,
410 3.0f, mPaint);
Michael Wright86172f62013-05-15 23:16:54 -0700411
412 // Draw the current bounding box
413 if (ps.mHasBoundingBox) {
414 canvas.drawRect(ps.mBoundingLeft, ps.mBoundingTop,
415 ps.mBoundingRight, ps.mBoundingBottom, mPaint);
416 }
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800417 }
418 }
419 }
Jeff Brown65fd2512011-08-18 11:20:58 -0700420
421 private void logMotionEvent(String type, MotionEvent event) {
422 final int action = event.getAction();
423 final int N = event.getHistorySize();
424 final int NI = event.getPointerCount();
425 for (int historyPos = 0; historyPos < N; historyPos++) {
426 for (int i = 0; i < NI; i++) {
427 final int id = event.getPointerId(i);
428 event.getHistoricalPointerCoords(i, historyPos, mTempCoords);
Michael Wright86172f62013-05-15 23:16:54 -0700429 logCoords(type, action, i, mTempCoords, id, event);
Jeff Brown65fd2512011-08-18 11:20:58 -0700430 }
431 }
432 for (int i = 0; i < NI; i++) {
433 final int id = event.getPointerId(i);
434 event.getPointerCoords(i, mTempCoords);
Michael Wright86172f62013-05-15 23:16:54 -0700435 logCoords(type, action, i, mTempCoords, id, event);
Jeff Brown65fd2512011-08-18 11:20:58 -0700436 }
437 }
438
439 private void logCoords(String type, int action, int index,
Michael Wright86172f62013-05-15 23:16:54 -0700440 MotionEvent.PointerCoords coords, int id, MotionEvent event) {
441 final int toolType = event.getToolType(index);
442 final int buttonState = event.getButtonState();
Jeff Brown33bbfd22011-02-24 20:55:35 -0800443 final String prefix;
444 switch (action & MotionEvent.ACTION_MASK) {
445 case MotionEvent.ACTION_DOWN:
446 prefix = "DOWN";
447 break;
448 case MotionEvent.ACTION_UP:
449 prefix = "UP";
450 break;
451 case MotionEvent.ACTION_MOVE:
452 prefix = "MOVE";
453 break;
454 case MotionEvent.ACTION_CANCEL:
455 prefix = "CANCEL";
456 break;
457 case MotionEvent.ACTION_OUTSIDE:
458 prefix = "OUTSIDE";
459 break;
460 case MotionEvent.ACTION_POINTER_DOWN:
461 if (index == ((action & MotionEvent.ACTION_POINTER_INDEX_MASK)
462 >> MotionEvent.ACTION_POINTER_INDEX_SHIFT)) {
463 prefix = "DOWN";
464 } else {
465 prefix = "MOVE";
466 }
467 break;
468 case MotionEvent.ACTION_POINTER_UP:
469 if (index == ((action & MotionEvent.ACTION_POINTER_INDEX_MASK)
470 >> MotionEvent.ACTION_POINTER_INDEX_SHIFT)) {
471 prefix = "UP";
472 } else {
473 prefix = "MOVE";
474 }
475 break;
476 case MotionEvent.ACTION_HOVER_MOVE:
477 prefix = "HOVER MOVE";
478 break;
Jeff Browna032cc02011-03-07 16:56:21 -0800479 case MotionEvent.ACTION_HOVER_ENTER:
480 prefix = "HOVER ENTER";
481 break;
482 case MotionEvent.ACTION_HOVER_EXIT:
483 prefix = "HOVER EXIT";
484 break;
Jeff Brown33bbfd22011-02-24 20:55:35 -0800485 case MotionEvent.ACTION_SCROLL:
486 prefix = "SCROLL";
487 break;
488 default:
489 prefix = Integer.toString(action);
490 break;
491 }
492
Jeff Brown8d608662010-08-30 03:02:23 -0700493 Log.i(TAG, mText.clear()
Jeff Brown65fd2512011-08-18 11:20:58 -0700494 .append(type).append(" id ").append(id + 1)
Jeff Brown33bbfd22011-02-24 20:55:35 -0800495 .append(": ")
496 .append(prefix)
497 .append(" (").append(coords.x, 3).append(", ").append(coords.y, 3)
Jeff Brown8d608662010-08-30 03:02:23 -0700498 .append(") Pressure=").append(coords.pressure, 3)
499 .append(" Size=").append(coords.size, 3)
500 .append(" TouchMajor=").append(coords.touchMajor, 3)
501 .append(" TouchMinor=").append(coords.touchMinor, 3)
502 .append(" ToolMajor=").append(coords.toolMajor, 3)
503 .append(" ToolMinor=").append(coords.toolMinor, 3)
504 .append(" Orientation=").append((float)(coords.orientation * 180 / Math.PI), 1)
Jeff Brown6f2fba42011-02-19 01:08:02 -0800505 .append("deg")
Jeff Brown65fd2512011-08-18 11:20:58 -0700506 .append(" Tilt=").append((float)(
507 coords.getAxisValue(MotionEvent.AXIS_TILT) * 180 / Math.PI), 1)
508 .append("deg")
Jeff Brown80fd47c2011-05-24 01:07:44 -0700509 .append(" Distance=").append(coords.getAxisValue(MotionEvent.AXIS_DISTANCE), 1)
Jeff Brown6f2fba42011-02-19 01:08:02 -0800510 .append(" VScroll=").append(coords.getAxisValue(MotionEvent.AXIS_VSCROLL), 1)
511 .append(" HScroll=").append(coords.getAxisValue(MotionEvent.AXIS_HSCROLL), 1)
Michael Wright86172f62013-05-15 23:16:54 -0700512 .append(" BoundingBox=[(")
513 .append(event.getAxisValue(MotionEvent.AXIS_GENERIC_1), 3)
514 .append(", ").append(event.getAxisValue(MotionEvent.AXIS_GENERIC_2), 3).append(")")
515 .append(", (").append(event.getAxisValue(MotionEvent.AXIS_GENERIC_3), 3)
516 .append(", ").append(event.getAxisValue(MotionEvent.AXIS_GENERIC_4), 3)
517 .append(")]")
Jeff Brownfe9f8ab2011-05-06 18:20:01 -0700518 .append(" ToolType=").append(MotionEvent.toolTypeToString(toolType))
519 .append(" ButtonState=").append(MotionEvent.buttonStateToString(buttonState))
Jeff Brown6f2fba42011-02-19 01:08:02 -0800520 .toString());
Jeff Brown8d608662010-08-30 03:02:23 -0700521 }
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800522
Jeff Brown33bbfd22011-02-24 20:55:35 -0800523 public void addPointerEvent(MotionEvent event) {
Jeff Brown70825162012-03-28 17:27:48 -0700524 final int action = event.getAction();
525 int NP = mPointers.size();
Jeff Brownfe9f8ab2011-05-06 18:20:01 -0700526
Jeff Brown70825162012-03-28 17:27:48 -0700527 if (action == MotionEvent.ACTION_DOWN
528 || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN) {
529 final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
530 >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for down
531 if (action == MotionEvent.ACTION_DOWN) {
532 for (int p=0; p<NP; p++) {
533 final PointerState ps = mPointers.get(p);
534 ps.clearTrace();
535 ps.mCurDown = false;
Jeff Brown8d608662010-08-30 03:02:23 -0700536 }
Jeff Brown70825162012-03-28 17:27:48 -0700537 mCurDown = true;
538 mCurNumPointers = 0;
539 mMaxNumPointers = 0;
540 mVelocity.clear();
Jeff Brown9eb7d862012-06-01 12:39:25 -0700541 if (mAltVelocity != null) {
542 mAltVelocity.clear();
543 }
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800544 }
Jeff Browncc0c1592011-02-19 05:07:28 -0800545
Jeff Brown70825162012-03-28 17:27:48 -0700546 mCurNumPointers += 1;
547 if (mMaxNumPointers < mCurNumPointers) {
548 mMaxNumPointers = mCurNumPointers;
Jeff Brown33bbfd22011-02-24 20:55:35 -0800549 }
Jeff Brown70825162012-03-28 17:27:48 -0700550
551 final int id = event.getPointerId(index);
552 while (NP <= id) {
553 PointerState ps = new PointerState();
554 mPointers.add(ps);
555 NP++;
556 }
557
558 if (mActivePointerId < 0 ||
559 !mPointers.get(mActivePointerId).mCurDown) {
560 mActivePointerId = id;
561 }
562
563 final PointerState ps = mPointers.get(id);
564 ps.mCurDown = true;
Michael Wright2f1cd7e2013-06-11 20:50:19 -0700565 InputDevice device = InputDevice.getDevice(event.getDeviceId());
566 ps.mHasBoundingBox = device != null &&
567 device.getMotionRange(MotionEvent.AXIS_GENERIC_1) != null;
Jeff Brown70825162012-03-28 17:27:48 -0700568 }
569
570 final int NI = event.getPointerCount();
571
572 mVelocity.addMovement(event);
573 mVelocity.computeCurrentVelocity(1);
Jeff Brown9eb7d862012-06-01 12:39:25 -0700574 if (mAltVelocity != null) {
575 mAltVelocity.addMovement(event);
576 mAltVelocity.computeCurrentVelocity(1);
577 }
Jeff Brown70825162012-03-28 17:27:48 -0700578
579 final int N = event.getHistorySize();
580 for (int historyPos = 0; historyPos < N; historyPos++) {
Jeff Brown33bbfd22011-02-24 20:55:35 -0800581 for (int i = 0; i < NI; i++) {
582 final int id = event.getPointerId(i);
583 final PointerState ps = mCurDown ? mPointers.get(id) : null;
Jeff Brown65fd2512011-08-18 11:20:58 -0700584 final PointerCoords coords = ps != null ? ps.mCoords : mTempCoords;
Jeff Brown70825162012-03-28 17:27:48 -0700585 event.getHistoricalPointerCoords(i, historyPos, coords);
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800586 if (mPrintCoords) {
Michael Wright86172f62013-05-15 23:16:54 -0700587 logCoords("Pointer", action, i, coords, id, event);
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800588 }
Jeff Browncc0c1592011-02-19 05:07:28 -0800589 if (ps != null) {
Michael Wright76936eb2013-06-21 15:18:06 -0700590 ps.addTrace(coords.x, coords.y, false);
Jeff Browncc0c1592011-02-19 05:07:28 -0800591 }
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800592 }
Jeff Brown70825162012-03-28 17:27:48 -0700593 }
594 for (int i = 0; i < NI; i++) {
595 final int id = event.getPointerId(i);
596 final PointerState ps = mCurDown ? mPointers.get(id) : null;
597 final PointerCoords coords = ps != null ? ps.mCoords : mTempCoords;
598 event.getPointerCoords(i, coords);
599 if (mPrintCoords) {
Michael Wright86172f62013-05-15 23:16:54 -0700600 logCoords("Pointer", action, i, coords, id, event);
Jeff Brown70825162012-03-28 17:27:48 -0700601 }
602 if (ps != null) {
Michael Wright76936eb2013-06-21 15:18:06 -0700603 ps.addTrace(coords.x, coords.y, true);
Jeff Brown70825162012-03-28 17:27:48 -0700604 ps.mXVelocity = mVelocity.getXVelocity(id);
605 ps.mYVelocity = mVelocity.getYVelocity(id);
Jeff Brown85bd0d62012-05-13 15:30:42 -0700606 mVelocity.getEstimator(id, ps.mEstimator);
Jeff Brown9eb7d862012-06-01 12:39:25 -0700607 if (mAltVelocity != null) {
608 ps.mAltXVelocity = mAltVelocity.getXVelocity(id);
609 ps.mAltYVelocity = mAltVelocity.getYVelocity(id);
610 mAltVelocity.getEstimator(id, ps.mAltEstimator);
611 }
Jeff Brown70825162012-03-28 17:27:48 -0700612 ps.mToolType = event.getToolType(i);
Michael Wright86172f62013-05-15 23:16:54 -0700613
614 if (ps.mHasBoundingBox) {
615 ps.mBoundingLeft = event.getAxisValue(MotionEvent.AXIS_GENERIC_1, i);
616 ps.mBoundingTop = event.getAxisValue(MotionEvent.AXIS_GENERIC_2, i);
617 ps.mBoundingRight = event.getAxisValue(MotionEvent.AXIS_GENERIC_3, i);
618 ps.mBoundingBottom = event.getAxisValue(MotionEvent.AXIS_GENERIC_4, i);
619 }
Jeff Brown70825162012-03-28 17:27:48 -0700620 }
621 }
622
623 if (action == MotionEvent.ACTION_UP
624 || action == MotionEvent.ACTION_CANCEL
625 || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP) {
626 final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
627 >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for UP
628
629 final int id = event.getPointerId(index);
630 final PointerState ps = mPointers.get(id);
631 ps.mCurDown = false;
Jeff Brown33bbfd22011-02-24 20:55:35 -0800632
Jeff Brown8d608662010-08-30 03:02:23 -0700633 if (action == MotionEvent.ACTION_UP
Jeff Brown70825162012-03-28 17:27:48 -0700634 || action == MotionEvent.ACTION_CANCEL) {
635 mCurDown = false;
636 mCurNumPointers = 0;
637 } else {
638 mCurNumPointers -= 1;
639 if (mActivePointerId == id) {
640 mActivePointerId = event.getPointerId(index == 0 ? 1 : 0);
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800641 }
Michael Wright76936eb2013-06-21 15:18:06 -0700642 ps.addTrace(Float.NaN, Float.NaN, false);
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800643 }
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800644 }
Jeff Brown70825162012-03-28 17:27:48 -0700645
646 invalidate();
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800647 }
648
649 @Override
650 public boolean onTouchEvent(MotionEvent event) {
Jeff Brown33bbfd22011-02-24 20:55:35 -0800651 addPointerEvent(event);
Jeff Brownc3fe7662011-03-07 14:35:59 -0800652
653 if (event.getAction() == MotionEvent.ACTION_DOWN && !isFocused()) {
654 requestFocus();
655 }
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800656 return true;
657 }
Dianne Hackborn7d9af5a2010-03-18 23:40:21 -0700658
659 @Override
Jeff Brown33bbfd22011-02-24 20:55:35 -0800660 public boolean onGenericMotionEvent(MotionEvent event) {
Jeff Brownc3fe7662011-03-07 14:35:59 -0800661 final int source = event.getSource();
662 if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
Jeff Brown33bbfd22011-02-24 20:55:35 -0800663 addPointerEvent(event);
Jeff Brownc3fe7662011-03-07 14:35:59 -0800664 } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
Jeff Brown65fd2512011-08-18 11:20:58 -0700665 logMotionEvent("Joystick", event);
Jeff Brownc3fe7662011-03-07 14:35:59 -0800666 } else if ((source & InputDevice.SOURCE_CLASS_POSITION) != 0) {
Jeff Brown65fd2512011-08-18 11:20:58 -0700667 logMotionEvent("Position", event);
Jeff Brownc3fe7662011-03-07 14:35:59 -0800668 } else {
Jeff Brown65fd2512011-08-18 11:20:58 -0700669 logMotionEvent("Generic", event);
Jeff Brownc3fe7662011-03-07 14:35:59 -0800670 }
671 return true;
672 }
673
674 @Override
675 public boolean onKeyDown(int keyCode, KeyEvent event) {
676 if (shouldLogKey(keyCode)) {
677 final int repeatCount = event.getRepeatCount();
678 if (repeatCount == 0) {
679 Log.i(TAG, "Key Down: " + event);
680 } else {
681 Log.i(TAG, "Key Repeat #" + repeatCount + ": " + event);
682 }
Jeff Brown33bbfd22011-02-24 20:55:35 -0800683 return true;
684 }
Jeff Brownc3fe7662011-03-07 14:35:59 -0800685 return super.onKeyDown(keyCode, event);
686 }
687
688 @Override
689 public boolean onKeyUp(int keyCode, KeyEvent event) {
690 if (shouldLogKey(keyCode)) {
691 Log.i(TAG, "Key Up: " + event);
692 return true;
693 }
694 return super.onKeyUp(keyCode, event);
695 }
696
697 private static boolean shouldLogKey(int keyCode) {
698 switch (keyCode) {
699 case KeyEvent.KEYCODE_DPAD_UP:
700 case KeyEvent.KEYCODE_DPAD_DOWN:
701 case KeyEvent.KEYCODE_DPAD_LEFT:
702 case KeyEvent.KEYCODE_DPAD_RIGHT:
703 case KeyEvent.KEYCODE_DPAD_CENTER:
704 return true;
705 default:
706 return KeyEvent.isGamepadButton(keyCode)
707 || KeyEvent.isModifierKey(keyCode);
708 }
Jeff Brown33bbfd22011-02-24 20:55:35 -0800709 }
710
711 @Override
Dianne Hackborn7d9af5a2010-03-18 23:40:21 -0700712 public boolean onTrackballEvent(MotionEvent event) {
Jeff Brown65fd2512011-08-18 11:20:58 -0700713 logMotionEvent("Trackball", event);
Jeff Brownc3fe7662011-03-07 14:35:59 -0800714 return true;
Dianne Hackborn7d9af5a2010-03-18 23:40:21 -0700715 }
Jeff Brownaf9e8d32012-04-12 17:32:48 -0700716
717 @Override
718 protected void onAttachedToWindow() {
719 super.onAttachedToWindow();
720
721 mIm.registerInputDeviceListener(this, getHandler());
722 logInputDevices();
723 }
724
725 @Override
726 protected void onDetachedFromWindow() {
727 super.onDetachedFromWindow();
728
729 mIm.unregisterInputDeviceListener(this);
730 }
731
732 @Override
733 public void onInputDeviceAdded(int deviceId) {
734 logInputDeviceState(deviceId, "Device Added");
735 }
736
737 @Override
738 public void onInputDeviceChanged(int deviceId) {
739 logInputDeviceState(deviceId, "Device Changed");
740 }
741
742 @Override
743 public void onInputDeviceRemoved(int deviceId) {
744 logInputDeviceState(deviceId, "Device Removed");
745 }
746
747 private void logInputDevices() {
748 int[] deviceIds = InputDevice.getDeviceIds();
749 for (int i = 0; i < deviceIds.length; i++) {
750 logInputDeviceState(deviceIds[i], "Device Enumerated");
751 }
752 }
753
754 private void logInputDeviceState(int deviceId, String state) {
755 InputDevice device = mIm.getInputDevice(deviceId);
756 if (device != null) {
757 Log.i(TAG, state + ": " + device);
758 } else {
759 Log.i(TAG, state + ": " + deviceId);
760 }
761 }
762
Jeff Brown8d608662010-08-30 03:02:23 -0700763 // HACK
764 // A quick and dirty string builder implementation optimized for GC.
Jeff Brownd1e0c372010-09-12 19:14:26 -0700765 // Using String.format causes the application grind to a halt when
766 // more than a couple of pointers are down due to the number of
767 // temporary objects allocated while formatting strings for drawing or logging.
Jeff Brown8d608662010-08-30 03:02:23 -0700768 private static final class FasterStringBuilder {
769 private char[] mChars;
770 private int mLength;
771
772 public FasterStringBuilder() {
773 mChars = new char[64];
774 }
775
776 public FasterStringBuilder clear() {
777 mLength = 0;
778 return this;
779 }
780
781 public FasterStringBuilder append(String value) {
782 final int valueLength = value.length();
783 final int index = reserve(valueLength);
784 value.getChars(0, valueLength, mChars, index);
785 mLength += valueLength;
786 return this;
787 }
788
789 public FasterStringBuilder append(int value) {
790 return append(value, 0);
791 }
792
793 public FasterStringBuilder append(int value, int zeroPadWidth) {
794 final boolean negative = value < 0;
795 if (negative) {
796 value = - value;
797 if (value < 0) {
798 append("-2147483648");
799 return this;
800 }
801 }
802
803 int index = reserve(11);
804 final char[] chars = mChars;
805
806 if (value == 0) {
807 chars[index++] = '0';
808 mLength += 1;
809 return this;
810 }
811
812 if (negative) {
813 chars[index++] = '-';
814 }
815
816 int divisor = 1000000000;
817 int numberWidth = 10;
818 while (value < divisor) {
819 divisor /= 10;
820 numberWidth -= 1;
821 if (numberWidth < zeroPadWidth) {
822 chars[index++] = '0';
823 }
824 }
825
826 do {
827 int digit = value / divisor;
828 value -= digit * divisor;
829 divisor /= 10;
830 chars[index++] = (char) (digit + '0');
831 } while (divisor != 0);
832
833 mLength = index;
834 return this;
835 }
836
837 public FasterStringBuilder append(float value, int precision) {
838 int scale = 1;
839 for (int i = 0; i < precision; i++) {
840 scale *= 10;
841 }
842 value = (float) (Math.rint(value * scale) / scale);
843
844 append((int) value);
845
846 if (precision != 0) {
847 append(".");
848 value = Math.abs(value);
849 value -= Math.floor(value);
850 append((int) (value * scale), precision);
851 }
852
853 return this;
854 }
855
856 @Override
857 public String toString() {
858 return new String(mChars, 0, mLength);
859 }
860
861 private int reserve(int length) {
862 final int oldLength = mLength;
863 final int newLength = mLength + length;
864 final char[] oldChars = mChars;
865 final int oldCapacity = oldChars.length;
866 if (newLength > oldCapacity) {
867 final int newCapacity = oldCapacity * 2;
868 final char[] newChars = new char[newCapacity];
869 System.arraycopy(oldChars, 0, newChars, 0, oldLength);
870 mChars = newChars;
871 }
872 return oldLength;
873 }
874 }
Dianne Hackborn90d2db32010-02-11 22:19:06 -0800875}