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