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