• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.internal.widget;
18 
19 import android.annotation.UnsupportedAppUsage;
20 import android.content.Context;
21 import android.graphics.Canvas;
22 import android.graphics.Paint;
23 import android.graphics.Paint.FontMetricsInt;
24 import android.graphics.Path;
25 import android.graphics.RectF;
26 import android.graphics.Region;
27 import android.hardware.input.InputManager;
28 import android.hardware.input.InputManager.InputDeviceListener;
29 import android.os.Handler;
30 import android.os.RemoteException;
31 import android.os.SystemProperties;
32 import android.util.Log;
33 import android.util.Slog;
34 import android.view.ISystemGestureExclusionListener;
35 import android.view.InputDevice;
36 import android.view.KeyEvent;
37 import android.view.MotionEvent;
38 import android.view.MotionEvent.PointerCoords;
39 import android.view.VelocityTracker;
40 import android.view.View;
41 import android.view.ViewConfiguration;
42 import android.view.WindowInsets;
43 import android.view.WindowManagerGlobal;
44 import android.view.WindowManagerPolicyConstants.PointerEventListener;
45 
46 import java.util.ArrayList;
47 
48 public class PointerLocationView extends View implements InputDeviceListener,
49         PointerEventListener {
50     private static final String TAG = "Pointer";
51 
52     // The system property key used to specify an alternate velocity tracker strategy
53     // to plot alongside the default one.  Useful for testing and comparison purposes.
54     private static final String ALT_STRATEGY_PROPERY_KEY = "debug.velocitytracker.alt";
55 
56     public static class PointerState {
57         // Trace of previous points.
58         private float[] mTraceX = new float[32];
59         private float[] mTraceY = new float[32];
60         private boolean[] mTraceCurrent = new boolean[32];
61         private int mTraceCount;
62 
63         // True if the pointer is down.
64         @UnsupportedAppUsage
65         private boolean mCurDown;
66 
67         // Most recent coordinates.
68         private PointerCoords mCoords = new PointerCoords();
69         private int mToolType;
70 
71         // Most recent velocity.
72         private float mXVelocity;
73         private float mYVelocity;
74         private float mAltXVelocity;
75         private float mAltYVelocity;
76 
77         // Current bounding box, if any
78         private boolean mHasBoundingBox;
79         private float mBoundingLeft;
80         private float mBoundingTop;
81         private float mBoundingRight;
82         private float mBoundingBottom;
83 
84         // Position estimator.
85         private VelocityTracker.Estimator mEstimator = new VelocityTracker.Estimator();
86         private VelocityTracker.Estimator mAltEstimator = new VelocityTracker.Estimator();
87 
clearTrace()88         public void clearTrace() {
89             mTraceCount = 0;
90         }
91 
addTrace(float x, float y, boolean current)92         public void addTrace(float x, float y, boolean current) {
93             int traceCapacity = mTraceX.length;
94             if (mTraceCount == traceCapacity) {
95                 traceCapacity *= 2;
96                 float[] newTraceX = new float[traceCapacity];
97                 System.arraycopy(mTraceX, 0, newTraceX, 0, mTraceCount);
98                 mTraceX = newTraceX;
99 
100                 float[] newTraceY = new float[traceCapacity];
101                 System.arraycopy(mTraceY, 0, newTraceY, 0, mTraceCount);
102                 mTraceY = newTraceY;
103 
104                 boolean[] newTraceCurrent = new boolean[traceCapacity];
105                 System.arraycopy(mTraceCurrent, 0, newTraceCurrent, 0, mTraceCount);
106                 mTraceCurrent= newTraceCurrent;
107             }
108 
109             mTraceX[mTraceCount] = x;
110             mTraceY[mTraceCount] = y;
111             mTraceCurrent[mTraceCount] = current;
112             mTraceCount += 1;
113         }
114     }
115 
116     private final InputManager mIm;
117 
118     private final ViewConfiguration mVC;
119     private final Paint mTextPaint;
120     private final Paint mTextBackgroundPaint;
121     private final Paint mTextLevelPaint;
122     private final Paint mPaint;
123     private final Paint mCurrentPointPaint;
124     private final Paint mTargetPaint;
125     private final Paint mPathPaint;
126     private final FontMetricsInt mTextMetrics = new FontMetricsInt();
127     private int mHeaderBottom;
128     private int mHeaderPaddingTop = 0;
129     @UnsupportedAppUsage
130     private boolean mCurDown;
131     @UnsupportedAppUsage
132     private int mCurNumPointers;
133     @UnsupportedAppUsage
134     private int mMaxNumPointers;
135     private int mActivePointerId;
136     @UnsupportedAppUsage
137     private final ArrayList<PointerState> mPointers = new ArrayList<PointerState>();
138     private final PointerCoords mTempCoords = new PointerCoords();
139 
140     private final Region mSystemGestureExclusion = new Region();
141     private final Path mSystemGestureExclusionPath = new Path();
142     private final Paint mSystemGestureExclusionPaint;
143 
144     private final VelocityTracker mVelocity;
145     private final VelocityTracker mAltVelocity;
146 
147     private final FasterStringBuilder mText = new FasterStringBuilder();
148 
149     @UnsupportedAppUsage
150     private boolean mPrintCoords = true;
151 
PointerLocationView(Context c)152     public PointerLocationView(Context c) {
153         super(c);
154         setFocusableInTouchMode(true);
155 
156         mIm = c.getSystemService(InputManager.class);
157 
158         mVC = ViewConfiguration.get(c);
159         mTextPaint = new Paint();
160         mTextPaint.setAntiAlias(true);
161         mTextPaint.setTextSize(10
162                 * getResources().getDisplayMetrics().density);
163         mTextPaint.setARGB(255, 0, 0, 0);
164         mTextBackgroundPaint = new Paint();
165         mTextBackgroundPaint.setAntiAlias(false);
166         mTextBackgroundPaint.setARGB(128, 255, 255, 255);
167         mTextLevelPaint = new Paint();
168         mTextLevelPaint.setAntiAlias(false);
169         mTextLevelPaint.setARGB(192, 255, 0, 0);
170         mPaint = new Paint();
171         mPaint.setAntiAlias(true);
172         mPaint.setARGB(255, 255, 255, 255);
173         mPaint.setStyle(Paint.Style.STROKE);
174         mPaint.setStrokeWidth(2);
175         mCurrentPointPaint = new Paint();
176         mCurrentPointPaint.setAntiAlias(true);
177         mCurrentPointPaint.setARGB(255, 255, 0, 0);
178         mCurrentPointPaint.setStyle(Paint.Style.STROKE);
179         mCurrentPointPaint.setStrokeWidth(2);
180         mTargetPaint = new Paint();
181         mTargetPaint.setAntiAlias(false);
182         mTargetPaint.setARGB(255, 0, 0, 192);
183         mPathPaint = new Paint();
184         mPathPaint.setAntiAlias(false);
185         mPathPaint.setARGB(255, 0, 96, 255);
186         mPaint.setStyle(Paint.Style.STROKE);
187         mPaint.setStrokeWidth(1);
188 
189         mSystemGestureExclusionPaint = new Paint();
190         mSystemGestureExclusionPaint.setARGB(25, 255, 0, 0);
191         mSystemGestureExclusionPaint.setStyle(Paint.Style.FILL_AND_STROKE);
192 
193         PointerState ps = new PointerState();
194         mPointers.add(ps);
195         mActivePointerId = 0;
196 
197         mVelocity = VelocityTracker.obtain();
198 
199         String altStrategy = SystemProperties.get(ALT_STRATEGY_PROPERY_KEY);
200         if (altStrategy.length() != 0) {
201             Log.d(TAG, "Comparing default velocity tracker strategy with " + altStrategy);
202             mAltVelocity = VelocityTracker.obtain(altStrategy);
203         } else {
204             mAltVelocity = null;
205         }
206     }
207 
setPrintCoords(boolean state)208     public void setPrintCoords(boolean state) {
209         mPrintCoords = state;
210     }
211 
212     @Override
onApplyWindowInsets(WindowInsets insets)213     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
214         if (insets.getDisplayCutout() != null) {
215             mHeaderPaddingTop = insets.getDisplayCutout().getSafeInsetTop();
216         } else {
217             mHeaderPaddingTop = 0;
218         }
219         return super.onApplyWindowInsets(insets);
220     }
221 
222     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)223     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
224         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
225         mTextPaint.getFontMetricsInt(mTextMetrics);
226         mHeaderBottom = mHeaderPaddingTop-mTextMetrics.ascent+mTextMetrics.descent+2;
227         if (false) {
228             Log.i("foo", "Metrics: ascent=" + mTextMetrics.ascent
229                     + " descent=" + mTextMetrics.descent
230                     + " leading=" + mTextMetrics.leading
231                     + " top=" + mTextMetrics.top
232                     + " bottom=" + mTextMetrics.bottom);
233         }
234     }
235 
236     // Draw an oval.  When angle is 0 radians, orients the major axis vertically,
237     // angles less than or greater than 0 radians rotate the major axis left or right.
238     private RectF mReusableOvalRect = new RectF();
drawOval(Canvas canvas, float x, float y, float major, float minor, float angle, Paint paint)239     private void drawOval(Canvas canvas, float x, float y, float major, float minor,
240             float angle, Paint paint) {
241         canvas.save(Canvas.MATRIX_SAVE_FLAG);
242         canvas.rotate((float) (angle * 180 / Math.PI), x, y);
243         mReusableOvalRect.left = x - minor / 2;
244         mReusableOvalRect.right = x + minor / 2;
245         mReusableOvalRect.top = y - major / 2;
246         mReusableOvalRect.bottom = y + major / 2;
247         canvas.drawOval(mReusableOvalRect, paint);
248         canvas.restore();
249     }
250 
251     @Override
onDraw(Canvas canvas)252     protected void onDraw(Canvas canvas) {
253         final int w = getWidth();
254         final int itemW = w/7;
255         final int base = mHeaderPaddingTop-mTextMetrics.ascent+1;
256         final int bottom = mHeaderBottom;
257 
258         final int NP = mPointers.size();
259 
260         if (!mSystemGestureExclusion.isEmpty()) {
261             mSystemGestureExclusionPath.reset();
262             mSystemGestureExclusion.getBoundaryPath(mSystemGestureExclusionPath);
263             canvas.drawPath(mSystemGestureExclusionPath, mSystemGestureExclusionPaint);
264         }
265 
266         // Labels
267         if (mActivePointerId >= 0) {
268             final PointerState ps = mPointers.get(mActivePointerId);
269 
270             canvas.drawRect(0, mHeaderPaddingTop, itemW-1, bottom,mTextBackgroundPaint);
271             canvas.drawText(mText.clear()
272                     .append("P: ").append(mCurNumPointers)
273                     .append(" / ").append(mMaxNumPointers)
274                     .toString(), 1, base, mTextPaint);
275 
276             final int N = ps.mTraceCount;
277             if ((mCurDown && ps.mCurDown) || N == 0) {
278                 canvas.drawRect(itemW, mHeaderPaddingTop, (itemW * 2) - 1, bottom,
279                         mTextBackgroundPaint);
280                 canvas.drawText(mText.clear()
281                         .append("X: ").append(ps.mCoords.x, 1)
282                         .toString(), 1 + itemW, base, mTextPaint);
283                 canvas.drawRect(itemW * 2, mHeaderPaddingTop, (itemW * 3) - 1, bottom,
284                         mTextBackgroundPaint);
285                 canvas.drawText(mText.clear()
286                         .append("Y: ").append(ps.mCoords.y, 1)
287                         .toString(), 1 + itemW * 2, base, mTextPaint);
288             } else {
289                 float dx = ps.mTraceX[N - 1] - ps.mTraceX[0];
290                 float dy = ps.mTraceY[N - 1] - ps.mTraceY[0];
291                 canvas.drawRect(itemW, mHeaderPaddingTop, (itemW * 2) - 1, bottom,
292                         Math.abs(dx) < mVC.getScaledTouchSlop()
293                         ? mTextBackgroundPaint : mTextLevelPaint);
294                 canvas.drawText(mText.clear()
295                         .append("dX: ").append(dx, 1)
296                         .toString(), 1 + itemW, base, mTextPaint);
297                 canvas.drawRect(itemW * 2, mHeaderPaddingTop, (itemW * 3) - 1, bottom,
298                         Math.abs(dy) < mVC.getScaledTouchSlop()
299                         ? mTextBackgroundPaint : mTextLevelPaint);
300                 canvas.drawText(mText.clear()
301                         .append("dY: ").append(dy, 1)
302                         .toString(), 1 + itemW * 2, base, mTextPaint);
303             }
304 
305             canvas.drawRect(itemW * 3, mHeaderPaddingTop, (itemW * 4) - 1, bottom,
306                     mTextBackgroundPaint);
307             canvas.drawText(mText.clear()
308                     .append("Xv: ").append(ps.mXVelocity, 3)
309                     .toString(), 1 + itemW * 3, base, mTextPaint);
310 
311             canvas.drawRect(itemW * 4, mHeaderPaddingTop, (itemW * 5) - 1, bottom,
312                     mTextBackgroundPaint);
313             canvas.drawText(mText.clear()
314                     .append("Yv: ").append(ps.mYVelocity, 3)
315                     .toString(), 1 + itemW * 4, base, mTextPaint);
316 
317             canvas.drawRect(itemW * 5, mHeaderPaddingTop, (itemW * 6) - 1, bottom,
318                     mTextBackgroundPaint);
319             canvas.drawRect(itemW * 5, mHeaderPaddingTop,
320                     (itemW * 5) + (ps.mCoords.pressure * itemW) - 1, bottom, mTextLevelPaint);
321             canvas.drawText(mText.clear()
322                     .append("Prs: ").append(ps.mCoords.pressure, 2)
323                     .toString(), 1 + itemW * 5, base, mTextPaint);
324 
325             canvas.drawRect(itemW * 6, mHeaderPaddingTop, w, bottom, mTextBackgroundPaint);
326             canvas.drawRect(itemW * 6, mHeaderPaddingTop,
327                     (itemW * 6) + (ps.mCoords.size * itemW) - 1, bottom, mTextLevelPaint);
328             canvas.drawText(mText.clear()
329                     .append("Size: ").append(ps.mCoords.size, 2)
330                     .toString(), 1 + itemW * 6, base, mTextPaint);
331         }
332 
333         // Pointer trace.
334         for (int p = 0; p < NP; p++) {
335             final PointerState ps = mPointers.get(p);
336 
337             // Draw path.
338             final int N = ps.mTraceCount;
339             float lastX = 0, lastY = 0;
340             boolean haveLast = false;
341             boolean drawn = false;
342             mPaint.setARGB(255, 128, 255, 255);
343             for (int i=0; i < N; i++) {
344                 float x = ps.mTraceX[i];
345                 float y = ps.mTraceY[i];
346                 if (Float.isNaN(x)) {
347                     haveLast = false;
348                     continue;
349                 }
350                 if (haveLast) {
351                     canvas.drawLine(lastX, lastY, x, y, mPathPaint);
352                     final Paint paint = ps.mTraceCurrent[i] ? mCurrentPointPaint : mPaint;
353                     canvas.drawPoint(lastX, lastY, paint);
354                     drawn = true;
355                 }
356                 lastX = x;
357                 lastY = y;
358                 haveLast = true;
359             }
360 
361             if (drawn) {
362                 // Draw velocity vector.
363                 mPaint.setARGB(255, 255, 64, 128);
364                 float xVel = ps.mXVelocity * (1000 / 60);
365                 float yVel = ps.mYVelocity * (1000 / 60);
366                 canvas.drawLine(lastX, lastY, lastX + xVel, lastY + yVel, mPaint);
367 
368                 // Draw velocity vector using an alternate VelocityTracker strategy.
369                 if (mAltVelocity != null) {
370                     mPaint.setARGB(255, 64, 255, 128);
371                     xVel = ps.mAltXVelocity * (1000 / 60);
372                     yVel = ps.mAltYVelocity * (1000 / 60);
373                     canvas.drawLine(lastX, lastY, lastX + xVel, lastY + yVel, mPaint);
374                 }
375             }
376 
377             if (mCurDown && ps.mCurDown) {
378                 // Draw crosshairs.
379                 canvas.drawLine(0, ps.mCoords.y, getWidth(), ps.mCoords.y, mTargetPaint);
380                 canvas.drawLine(ps.mCoords.x, 0, ps.mCoords.x, getHeight(), mTargetPaint);
381 
382                 // Draw current point.
383                 int pressureLevel = (int)(ps.mCoords.pressure * 255);
384                 mPaint.setARGB(255, pressureLevel, 255, 255 - pressureLevel);
385                 canvas.drawPoint(ps.mCoords.x, ps.mCoords.y, mPaint);
386 
387                 // Draw current touch ellipse.
388                 mPaint.setARGB(255, pressureLevel, 255 - pressureLevel, 128);
389                 drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.touchMajor,
390                         ps.mCoords.touchMinor, ps.mCoords.orientation, mPaint);
391 
392                 // Draw current tool ellipse.
393                 mPaint.setARGB(255, pressureLevel, 128, 255 - pressureLevel);
394                 drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.toolMajor,
395                         ps.mCoords.toolMinor, ps.mCoords.orientation, mPaint);
396 
397                 // Draw the orientation arrow.
398                 float arrowSize = ps.mCoords.toolMajor * 0.7f;
399                 if (arrowSize < 20) {
400                     arrowSize = 20;
401                 }
402                 mPaint.setARGB(255, pressureLevel, 255, 0);
403                 float orientationVectorX = (float) (Math.sin(ps.mCoords.orientation)
404                         * arrowSize);
405                 float orientationVectorY = (float) (-Math.cos(ps.mCoords.orientation)
406                         * arrowSize);
407                 if (ps.mToolType == MotionEvent.TOOL_TYPE_STYLUS
408                         || ps.mToolType == MotionEvent.TOOL_TYPE_ERASER) {
409                     // Show full circle orientation.
410                     canvas.drawLine(ps.mCoords.x, ps.mCoords.y,
411                             ps.mCoords.x + orientationVectorX,
412                             ps.mCoords.y + orientationVectorY,
413                             mPaint);
414                 } else {
415                     // Show half circle orientation.
416                     canvas.drawLine(
417                             ps.mCoords.x - orientationVectorX,
418                             ps.mCoords.y - orientationVectorY,
419                             ps.mCoords.x + orientationVectorX,
420                             ps.mCoords.y + orientationVectorY,
421                             mPaint);
422                 }
423 
424                 // Draw the tilt point along the orientation arrow.
425                 float tiltScale = (float) Math.sin(
426                         ps.mCoords.getAxisValue(MotionEvent.AXIS_TILT));
427                 canvas.drawCircle(
428                         ps.mCoords.x + orientationVectorX * tiltScale,
429                         ps.mCoords.y + orientationVectorY * tiltScale,
430                         3.0f, mPaint);
431 
432                 // Draw the current bounding box
433                 if (ps.mHasBoundingBox) {
434                     canvas.drawRect(ps.mBoundingLeft, ps.mBoundingTop,
435                             ps.mBoundingRight, ps.mBoundingBottom, mPaint);
436                 }
437             }
438         }
439     }
440 
logMotionEvent(String type, MotionEvent event)441     private void logMotionEvent(String type, MotionEvent event) {
442         final int action = event.getAction();
443         final int N = event.getHistorySize();
444         final int NI = event.getPointerCount();
445         for (int historyPos = 0; historyPos < N; historyPos++) {
446             for (int i = 0; i < NI; i++) {
447                 final int id = event.getPointerId(i);
448                 event.getHistoricalPointerCoords(i, historyPos, mTempCoords);
449                 logCoords(type, action, i, mTempCoords, id, event);
450             }
451         }
452         for (int i = 0; i < NI; i++) {
453             final int id = event.getPointerId(i);
454             event.getPointerCoords(i, mTempCoords);
455             logCoords(type, action, i, mTempCoords, id, event);
456         }
457     }
458 
logCoords(String type, int action, int index, MotionEvent.PointerCoords coords, int id, MotionEvent event)459     private void logCoords(String type, int action, int index,
460             MotionEvent.PointerCoords coords, int id, MotionEvent event) {
461         final int toolType = event.getToolType(index);
462         final int buttonState = event.getButtonState();
463         final String prefix;
464         switch (action & MotionEvent.ACTION_MASK) {
465             case MotionEvent.ACTION_DOWN:
466                 prefix = "DOWN";
467                 break;
468             case MotionEvent.ACTION_UP:
469                 prefix = "UP";
470                 break;
471             case MotionEvent.ACTION_MOVE:
472                 prefix = "MOVE";
473                 break;
474             case MotionEvent.ACTION_CANCEL:
475                 prefix = "CANCEL";
476                 break;
477             case MotionEvent.ACTION_OUTSIDE:
478                 prefix = "OUTSIDE";
479                 break;
480             case MotionEvent.ACTION_POINTER_DOWN:
481                 if (index == ((action & MotionEvent.ACTION_POINTER_INDEX_MASK)
482                         >> MotionEvent.ACTION_POINTER_INDEX_SHIFT)) {
483                     prefix = "DOWN";
484                 } else {
485                     prefix = "MOVE";
486                 }
487                 break;
488             case MotionEvent.ACTION_POINTER_UP:
489                 if (index == ((action & MotionEvent.ACTION_POINTER_INDEX_MASK)
490                         >> MotionEvent.ACTION_POINTER_INDEX_SHIFT)) {
491                     prefix = "UP";
492                 } else {
493                     prefix = "MOVE";
494                 }
495                 break;
496             case MotionEvent.ACTION_HOVER_MOVE:
497                 prefix = "HOVER MOVE";
498                 break;
499             case MotionEvent.ACTION_HOVER_ENTER:
500                 prefix = "HOVER ENTER";
501                 break;
502             case MotionEvent.ACTION_HOVER_EXIT:
503                 prefix = "HOVER EXIT";
504                 break;
505             case MotionEvent.ACTION_SCROLL:
506                 prefix = "SCROLL";
507                 break;
508             default:
509                 prefix = Integer.toString(action);
510                 break;
511         }
512 
513         Log.i(TAG, mText.clear()
514                 .append(type).append(" id ").append(id + 1)
515                 .append(": ")
516                 .append(prefix)
517                 .append(" (").append(coords.x, 3).append(", ").append(coords.y, 3)
518                 .append(") Pressure=").append(coords.pressure, 3)
519                 .append(" Size=").append(coords.size, 3)
520                 .append(" TouchMajor=").append(coords.touchMajor, 3)
521                 .append(" TouchMinor=").append(coords.touchMinor, 3)
522                 .append(" ToolMajor=").append(coords.toolMajor, 3)
523                 .append(" ToolMinor=").append(coords.toolMinor, 3)
524                 .append(" Orientation=").append((float)(coords.orientation * 180 / Math.PI), 1)
525                 .append("deg")
526                 .append(" Tilt=").append((float)(
527                         coords.getAxisValue(MotionEvent.AXIS_TILT) * 180 / Math.PI), 1)
528                 .append("deg")
529                 .append(" Distance=").append(coords.getAxisValue(MotionEvent.AXIS_DISTANCE), 1)
530                 .append(" VScroll=").append(coords.getAxisValue(MotionEvent.AXIS_VSCROLL), 1)
531                 .append(" HScroll=").append(coords.getAxisValue(MotionEvent.AXIS_HSCROLL), 1)
532                 .append(" BoundingBox=[(")
533                 .append(event.getAxisValue(MotionEvent.AXIS_GENERIC_1), 3)
534                 .append(", ").append(event.getAxisValue(MotionEvent.AXIS_GENERIC_2), 3).append(")")
535                 .append(", (").append(event.getAxisValue(MotionEvent.AXIS_GENERIC_3), 3)
536                 .append(", ").append(event.getAxisValue(MotionEvent.AXIS_GENERIC_4), 3)
537                 .append(")]")
538                 .append(" ToolType=").append(MotionEvent.toolTypeToString(toolType))
539                 .append(" ButtonState=").append(MotionEvent.buttonStateToString(buttonState))
540                 .toString());
541     }
542 
543     @Override
onPointerEvent(MotionEvent event)544     public void onPointerEvent(MotionEvent event) {
545         final int action = event.getAction();
546         int NP = mPointers.size();
547 
548         if (action == MotionEvent.ACTION_DOWN
549                 || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN) {
550             final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
551                     >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for down
552             if (action == MotionEvent.ACTION_DOWN) {
553                 for (int p=0; p<NP; p++) {
554                     final PointerState ps = mPointers.get(p);
555                     ps.clearTrace();
556                     ps.mCurDown = false;
557                 }
558                 mCurDown = true;
559                 mCurNumPointers = 0;
560                 mMaxNumPointers = 0;
561                 mVelocity.clear();
562                 if (mAltVelocity != null) {
563                     mAltVelocity.clear();
564                 }
565             }
566 
567             mCurNumPointers += 1;
568             if (mMaxNumPointers < mCurNumPointers) {
569                 mMaxNumPointers = mCurNumPointers;
570             }
571 
572             final int id = event.getPointerId(index);
573             while (NP <= id) {
574                 PointerState ps = new PointerState();
575                 mPointers.add(ps);
576                 NP++;
577             }
578 
579             if (mActivePointerId < 0 ||
580                     !mPointers.get(mActivePointerId).mCurDown) {
581                 mActivePointerId = id;
582             }
583 
584             final PointerState ps = mPointers.get(id);
585             ps.mCurDown = true;
586             InputDevice device = InputDevice.getDevice(event.getDeviceId());
587             ps.mHasBoundingBox = device != null &&
588                     device.getMotionRange(MotionEvent.AXIS_GENERIC_1) != null;
589         }
590 
591         final int NI = event.getPointerCount();
592 
593         mVelocity.addMovement(event);
594         mVelocity.computeCurrentVelocity(1);
595         if (mAltVelocity != null) {
596             mAltVelocity.addMovement(event);
597             mAltVelocity.computeCurrentVelocity(1);
598         }
599 
600         final int N = event.getHistorySize();
601         for (int historyPos = 0; historyPos < N; historyPos++) {
602             for (int i = 0; i < NI; i++) {
603                 final int id = event.getPointerId(i);
604                 final PointerState ps = mCurDown ? mPointers.get(id) : null;
605                 final PointerCoords coords = ps != null ? ps.mCoords : mTempCoords;
606                 event.getHistoricalPointerCoords(i, historyPos, coords);
607                 if (mPrintCoords) {
608                     logCoords("Pointer", action, i, coords, id, event);
609                 }
610                 if (ps != null) {
611                     ps.addTrace(coords.x, coords.y, false);
612                 }
613             }
614         }
615         for (int i = 0; i < NI; i++) {
616             final int id = event.getPointerId(i);
617             final PointerState ps = mCurDown ? mPointers.get(id) : null;
618             final PointerCoords coords = ps != null ? ps.mCoords : mTempCoords;
619             event.getPointerCoords(i, coords);
620             if (mPrintCoords) {
621                 logCoords("Pointer", action, i, coords, id, event);
622             }
623             if (ps != null) {
624                 ps.addTrace(coords.x, coords.y, true);
625                 ps.mXVelocity = mVelocity.getXVelocity(id);
626                 ps.mYVelocity = mVelocity.getYVelocity(id);
627                 mVelocity.getEstimator(id, ps.mEstimator);
628                 if (mAltVelocity != null) {
629                     ps.mAltXVelocity = mAltVelocity.getXVelocity(id);
630                     ps.mAltYVelocity = mAltVelocity.getYVelocity(id);
631                     mAltVelocity.getEstimator(id, ps.mAltEstimator);
632                 }
633                 ps.mToolType = event.getToolType(i);
634 
635                 if (ps.mHasBoundingBox) {
636                     ps.mBoundingLeft = event.getAxisValue(MotionEvent.AXIS_GENERIC_1, i);
637                     ps.mBoundingTop = event.getAxisValue(MotionEvent.AXIS_GENERIC_2, i);
638                     ps.mBoundingRight = event.getAxisValue(MotionEvent.AXIS_GENERIC_3, i);
639                     ps.mBoundingBottom = event.getAxisValue(MotionEvent.AXIS_GENERIC_4, i);
640                 }
641             }
642         }
643 
644         if (action == MotionEvent.ACTION_UP
645                 || action == MotionEvent.ACTION_CANCEL
646                 || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP) {
647             final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
648                     >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for UP
649 
650             final int id = event.getPointerId(index);
651             if (id >= NP) {
652                 Slog.wtf(TAG, "Got pointer ID out of bounds: id=" + id + " arraysize="
653                         + NP + " pointerindex=" + index
654                         + " action=0x" + Integer.toHexString(action));
655                 return;
656             }
657             final PointerState ps = mPointers.get(id);
658             ps.mCurDown = false;
659 
660             if (action == MotionEvent.ACTION_UP
661                     || action == MotionEvent.ACTION_CANCEL) {
662                 mCurDown = false;
663                 mCurNumPointers = 0;
664             } else {
665                 mCurNumPointers -= 1;
666                 if (mActivePointerId == id) {
667                     mActivePointerId = event.getPointerId(index == 0 ? 1 : 0);
668                 }
669                 ps.addTrace(Float.NaN, Float.NaN, false);
670             }
671         }
672 
673         invalidate();
674     }
675 
676     @Override
onTouchEvent(MotionEvent event)677     public boolean onTouchEvent(MotionEvent event) {
678         onPointerEvent(event);
679 
680         if (event.getAction() == MotionEvent.ACTION_DOWN && !isFocused()) {
681             requestFocus();
682         }
683         return true;
684     }
685 
686     @Override
onGenericMotionEvent(MotionEvent event)687     public boolean onGenericMotionEvent(MotionEvent event) {
688         final int source = event.getSource();
689         if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
690             onPointerEvent(event);
691         } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
692             logMotionEvent("Joystick", event);
693         } else if ((source & InputDevice.SOURCE_CLASS_POSITION) != 0) {
694             logMotionEvent("Position", event);
695         } else {
696             logMotionEvent("Generic", event);
697         }
698         return true;
699     }
700 
701     @Override
onKeyDown(int keyCode, KeyEvent event)702     public boolean onKeyDown(int keyCode, KeyEvent event) {
703         if (shouldLogKey(keyCode)) {
704             final int repeatCount = event.getRepeatCount();
705             if (repeatCount == 0) {
706                 Log.i(TAG, "Key Down: " + event);
707             } else {
708                 Log.i(TAG, "Key Repeat #" + repeatCount + ": " + event);
709             }
710             return true;
711         }
712         return super.onKeyDown(keyCode, event);
713     }
714 
715     @Override
onKeyUp(int keyCode, KeyEvent event)716     public boolean onKeyUp(int keyCode, KeyEvent event) {
717         if (shouldLogKey(keyCode)) {
718             Log.i(TAG, "Key Up: " + event);
719             return true;
720         }
721         return super.onKeyUp(keyCode, event);
722     }
723 
shouldLogKey(int keyCode)724     private static boolean shouldLogKey(int keyCode) {
725         switch (keyCode) {
726             case KeyEvent.KEYCODE_DPAD_UP:
727             case KeyEvent.KEYCODE_DPAD_DOWN:
728             case KeyEvent.KEYCODE_DPAD_LEFT:
729             case KeyEvent.KEYCODE_DPAD_RIGHT:
730             case KeyEvent.KEYCODE_DPAD_CENTER:
731                 return true;
732             default:
733                 return KeyEvent.isGamepadButton(keyCode)
734                     || KeyEvent.isModifierKey(keyCode);
735         }
736     }
737 
738     @Override
onTrackballEvent(MotionEvent event)739     public boolean onTrackballEvent(MotionEvent event) {
740         logMotionEvent("Trackball", event);
741         return true;
742     }
743 
744     @Override
onAttachedToWindow()745     protected void onAttachedToWindow() {
746         super.onAttachedToWindow();
747 
748         mIm.registerInputDeviceListener(this, getHandler());
749         if (shouldShowSystemGestureExclusion()) {
750             try {
751                 WindowManagerGlobal.getWindowManagerService()
752                         .registerSystemGestureExclusionListener(mSystemGestureExclusionListener,
753                                 mContext.getDisplayId());
754             } catch (RemoteException e) {
755                 throw e.rethrowFromSystemServer();
756             }
757         } else {
758             mSystemGestureExclusion.setEmpty();
759         }
760         logInputDevices();
761     }
762 
763     @Override
onDetachedFromWindow()764     protected void onDetachedFromWindow() {
765         super.onDetachedFromWindow();
766 
767         mIm.unregisterInputDeviceListener(this);
768         try {
769             WindowManagerGlobal.getWindowManagerService().unregisterSystemGestureExclusionListener(
770                     mSystemGestureExclusionListener, mContext.getDisplayId());
771         } catch (RemoteException e) {
772             throw e.rethrowFromSystemServer();
773         }
774     }
775 
776     @Override
onInputDeviceAdded(int deviceId)777     public void onInputDeviceAdded(int deviceId) {
778         logInputDeviceState(deviceId, "Device Added");
779     }
780 
781     @Override
onInputDeviceChanged(int deviceId)782     public void onInputDeviceChanged(int deviceId) {
783         logInputDeviceState(deviceId, "Device Changed");
784     }
785 
786     @Override
onInputDeviceRemoved(int deviceId)787     public void onInputDeviceRemoved(int deviceId) {
788         logInputDeviceState(deviceId, "Device Removed");
789     }
790 
logInputDevices()791     private void logInputDevices() {
792         int[] deviceIds = InputDevice.getDeviceIds();
793         for (int i = 0; i < deviceIds.length; i++) {
794             logInputDeviceState(deviceIds[i], "Device Enumerated");
795         }
796     }
797 
logInputDeviceState(int deviceId, String state)798     private void logInputDeviceState(int deviceId, String state) {
799         InputDevice device = mIm.getInputDevice(deviceId);
800         if (device != null) {
801             Log.i(TAG, state + ": " + device);
802         } else {
803             Log.i(TAG, state + ": " + deviceId);
804         }
805     }
806 
shouldShowSystemGestureExclusion()807     private static boolean shouldShowSystemGestureExclusion() {
808         return SystemProperties.getBoolean("debug.pointerlocation.showexclusion", false);
809     }
810 
811     // HACK
812     // A quick and dirty string builder implementation optimized for GC.
813     // Using String.format causes the application grind to a halt when
814     // more than a couple of pointers are down due to the number of
815     // temporary objects allocated while formatting strings for drawing or logging.
816     private static final class FasterStringBuilder {
817         private char[] mChars;
818         private int mLength;
819 
FasterStringBuilder()820         public FasterStringBuilder() {
821             mChars = new char[64];
822         }
823 
clear()824         public FasterStringBuilder clear() {
825             mLength = 0;
826             return this;
827         }
828 
append(String value)829         public FasterStringBuilder append(String value) {
830             final int valueLength = value.length();
831             final int index = reserve(valueLength);
832             value.getChars(0, valueLength, mChars, index);
833             mLength += valueLength;
834             return this;
835         }
836 
append(int value)837         public FasterStringBuilder append(int value) {
838             return append(value, 0);
839         }
840 
append(int value, int zeroPadWidth)841         public FasterStringBuilder append(int value, int zeroPadWidth) {
842             final boolean negative = value < 0;
843             if (negative) {
844                 value = - value;
845                 if (value < 0) {
846                     append("-2147483648");
847                     return this;
848                 }
849             }
850 
851             int index = reserve(11);
852             final char[] chars = mChars;
853 
854             if (value == 0) {
855                 chars[index++] = '0';
856                 mLength += 1;
857                 return this;
858             }
859 
860             if (negative) {
861                 chars[index++] = '-';
862             }
863 
864             int divisor = 1000000000;
865             int numberWidth = 10;
866             while (value < divisor) {
867                 divisor /= 10;
868                 numberWidth -= 1;
869                 if (numberWidth < zeroPadWidth) {
870                     chars[index++] = '0';
871                 }
872             }
873 
874             do {
875                 int digit = value / divisor;
876                 value -= digit * divisor;
877                 divisor /= 10;
878                 chars[index++] = (char) (digit + '0');
879             } while (divisor != 0);
880 
881             mLength = index;
882             return this;
883         }
884 
885         public FasterStringBuilder append(float value, int precision) {
886             int scale = 1;
887             for (int i = 0; i < precision; i++) {
888                 scale *= 10;
889             }
890             value = (float) (Math.rint(value * scale) / scale);
891 
892             // Corner case: (int)-0.1 will become zero, so the negative sign gets lost
893             if ((int) value == 0 && value < 0) {
894                 append("-");
895             }
896             append((int) value);
897 
898             if (precision != 0) {
899                 append(".");
900                 value = Math.abs(value);
901                 value -= Math.floor(value);
902                 append((int) (value * scale), precision);
903             }
904 
905             return this;
906         }
907 
908         @Override
909         public String toString() {
910             return new String(mChars, 0, mLength);
911         }
912 
913         private int reserve(int length) {
914             final int oldLength = mLength;
915             final int newLength = mLength + length;
916             final char[] oldChars = mChars;
917             final int oldCapacity = oldChars.length;
918             if (newLength > oldCapacity) {
919                 final int newCapacity = oldCapacity * 2;
920                 final char[] newChars = new char[newCapacity];
921                 System.arraycopy(oldChars, 0, newChars, 0, oldLength);
922                 mChars = newChars;
923             }
924             return oldLength;
925         }
926     }
927 
928     private ISystemGestureExclusionListener mSystemGestureExclusionListener =
929             new ISystemGestureExclusionListener.Stub() {
930         @Override
931         public void onSystemGestureExclusionChanged(int displayId, Region systemGestureExclusion) {
932             Region exclusion = Region.obtain(systemGestureExclusion);
933             Handler handler = getHandler();
934             if (handler != null) {
935                 handler.post(() -> {
936                     mSystemGestureExclusion.set(exclusion);
937                     exclusion.recycle();
938                     invalidate();
939                 });
940             }
941         }
942     };
943 }
944