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.Insets;
23 import android.graphics.Paint;
24 import android.graphics.Paint.FontMetricsInt;
25 import android.graphics.Path;
26 import android.graphics.RectF;
27 import android.graphics.Region;
28 import android.hardware.input.InputManager;
29 import android.hardware.input.InputManager.InputDeviceListener;
30 import android.os.Handler;
31 import android.os.RemoteException;
32 import android.os.SystemProperties;
33 import android.util.Log;
34 import android.util.Slog;
35 import android.view.ISystemGestureExclusionListener;
36 import android.view.InputDevice;
37 import android.view.KeyEvent;
38 import android.view.MotionEvent;
39 import android.view.MotionEvent.PointerCoords;
40 import android.view.VelocityTracker;
41 import android.view.View;
42 import android.view.ViewConfiguration;
43 import android.view.WindowInsets;
44 import android.view.WindowManagerGlobal;
45 import android.view.WindowManagerPolicyConstants.PointerEventListener;
46 
47 import java.util.ArrayList;
48 
49 public class PointerLocationView extends View implements InputDeviceListener,
50         PointerEventListener {
51     private static final String TAG = "Pointer";
52 
53     // The system property key used to specify an alternate velocity tracker strategy
54     // to plot alongside the default one.  Useful for testing and comparison purposes.
55     private static final String ALT_STRATEGY_PROPERY_KEY = "debug.velocitytracker.alt";
56 
57     /**
58      * If set to a positive value between 1-255, shows an overlay with the approved (red) and
59      * rejected (blue) exclusions.
60      */
61     private static final String GESTURE_EXCLUSION_PROP = "debug.pointerlocation.showexclusion";
62 
63     public static class PointerState {
64         // Trace of previous points.
65         private float[] mTraceX = new float[32];
66         private float[] mTraceY = new float[32];
67         private boolean[] mTraceCurrent = new boolean[32];
68         private int mTraceCount;
69 
70         // True if the pointer is down.
71         @UnsupportedAppUsage
72         private boolean mCurDown;
73 
74         // Most recent coordinates.
75         private PointerCoords mCoords = new PointerCoords();
76         private int mToolType;
77 
78         // Most recent velocity.
79         private float mXVelocity;
80         private float mYVelocity;
81         private float mAltXVelocity;
82         private float mAltYVelocity;
83 
84         // Current bounding box, if any
85         private boolean mHasBoundingBox;
86         private float mBoundingLeft;
87         private float mBoundingTop;
88         private float mBoundingRight;
89         private float mBoundingBottom;
90 
91         // Position estimator.
92         private VelocityTracker.Estimator mEstimator = new VelocityTracker.Estimator();
93         private VelocityTracker.Estimator mAltEstimator = new VelocityTracker.Estimator();
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 ArrayList<PointerState> mPointers = new ArrayList<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 
PointerLocationView(Context c)166     public PointerLocationView(Context c) {
167         super(c);
168         setFocusableInTouchMode(true);
169 
170         mIm = c.getSystemService(InputManager.class);
171 
172         mVC = ViewConfiguration.get(c);
173         mTextPaint = new Paint();
174         mTextPaint.setAntiAlias(true);
175         mTextPaint.setTextSize(10
176                 * getResources().getDisplayMetrics().density);
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         mPaint.setStrokeWidth(2);
189         mCurrentPointPaint = new Paint();
190         mCurrentPointPaint.setAntiAlias(true);
191         mCurrentPointPaint.setARGB(255, 255, 0, 0);
192         mCurrentPointPaint.setStyle(Paint.Style.STROKE);
193         mCurrentPointPaint.setStrokeWidth(2);
194         mTargetPaint = new Paint();
195         mTargetPaint.setAntiAlias(false);
196         mTargetPaint.setARGB(255, 0, 0, 192);
197         mPathPaint = new Paint();
198         mPathPaint.setAntiAlias(false);
199         mPathPaint.setARGB(255, 0, 96, 255);
200         mPaint.setStyle(Paint.Style.STROKE);
201         mPaint.setStrokeWidth(1);
202 
203         mSystemGestureExclusionPaint = new Paint();
204         mSystemGestureExclusionPaint.setARGB(25, 255, 0, 0);
205         mSystemGestureExclusionPaint.setStyle(Paint.Style.FILL_AND_STROKE);
206 
207         mSystemGestureExclusionRejectedPaint = new Paint();
208         mSystemGestureExclusionRejectedPaint.setARGB(25, 0, 0, 255);
209         mSystemGestureExclusionRejectedPaint.setStyle(Paint.Style.FILL_AND_STROKE);
210 
211         PointerState ps = new PointerState();
212         mPointers.add(ps);
213         mActivePointerId = 0;
214 
215         mVelocity = VelocityTracker.obtain();
216 
217         String altStrategy = SystemProperties.get(ALT_STRATEGY_PROPERY_KEY);
218         if (altStrategy.length() != 0) {
219             Log.d(TAG, "Comparing default velocity tracker strategy with " + altStrategy);
220             mAltVelocity = VelocityTracker.obtain(altStrategy);
221         } else {
222             mAltVelocity = null;
223         }
224     }
225 
setPrintCoords(boolean state)226     public void setPrintCoords(boolean state) {
227         mPrintCoords = state;
228     }
229 
230     @Override
onApplyWindowInsets(WindowInsets insets)231     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
232         if (insets.getDisplayCutout() != null) {
233             mHeaderPaddingTop = insets.getDisplayCutout().getSafeInsetTop();
234             mWaterfallInsets = insets.getDisplayCutout().getWaterfallInsets();
235         } else {
236             mHeaderPaddingTop = 0;
237             mWaterfallInsets = Insets.NONE;
238         }
239         return super.onApplyWindowInsets(insets);
240     }
241 
242     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)243     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
244         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
245         mTextPaint.getFontMetricsInt(mTextMetrics);
246         mHeaderBottom = mHeaderPaddingTop-mTextMetrics.ascent+mTextMetrics.descent+2;
247         if (false) {
248             Log.i("foo", "Metrics: ascent=" + mTextMetrics.ascent
249                     + " descent=" + mTextMetrics.descent
250                     + " leading=" + mTextMetrics.leading
251                     + " top=" + mTextMetrics.top
252                     + " bottom=" + mTextMetrics.bottom);
253         }
254     }
255 
256     // Draw an oval.  When angle is 0 radians, orients the major axis vertically,
257     // angles less than or greater than 0 radians rotate the major axis left or right.
258     private RectF mReusableOvalRect = new RectF();
drawOval(Canvas canvas, float x, float y, float major, float minor, float angle, Paint paint)259     private void drawOval(Canvas canvas, float x, float y, float major, float minor,
260             float angle, Paint paint) {
261         canvas.save(Canvas.MATRIX_SAVE_FLAG);
262         canvas.rotate((float) (angle * 180 / Math.PI), x, y);
263         mReusableOvalRect.left = x - minor / 2;
264         mReusableOvalRect.right = x + minor / 2;
265         mReusableOvalRect.top = y - major / 2;
266         mReusableOvalRect.bottom = y + major / 2;
267         canvas.drawOval(mReusableOvalRect, paint);
268         canvas.restore();
269     }
270 
271     @Override
onDraw(Canvas canvas)272     protected void onDraw(Canvas canvas) {
273         final int NP = mPointers.size();
274 
275         if (!mSystemGestureExclusion.isEmpty()) {
276             mSystemGestureExclusionPath.reset();
277             mSystemGestureExclusion.getBoundaryPath(mSystemGestureExclusionPath);
278             canvas.drawPath(mSystemGestureExclusionPath, mSystemGestureExclusionPaint);
279         }
280 
281         if (!mSystemGestureExclusionRejected.isEmpty()) {
282             mSystemGestureExclusionPath.reset();
283             mSystemGestureExclusionRejected.getBoundaryPath(mSystemGestureExclusionPath);
284             canvas.drawPath(mSystemGestureExclusionPath, mSystemGestureExclusionRejectedPaint);
285         }
286 
287         // Labels
288         drawLabels(canvas);
289 
290         // Pointer trace.
291         for (int p = 0; p < NP; p++) {
292             final PointerState ps = mPointers.get(p);
293 
294             // Draw path.
295             final int N = ps.mTraceCount;
296             float lastX = 0, lastY = 0;
297             boolean haveLast = false;
298             boolean drawn = false;
299             mPaint.setARGB(255, 128, 255, 255);
300             for (int i=0; i < N; i++) {
301                 float x = ps.mTraceX[i];
302                 float y = ps.mTraceY[i];
303                 if (Float.isNaN(x)) {
304                     haveLast = false;
305                     continue;
306                 }
307                 if (haveLast) {
308                     canvas.drawLine(lastX, lastY, x, y, mPathPaint);
309                     final Paint paint = ps.mTraceCurrent[i - 1] ? mCurrentPointPaint : mPaint;
310                     canvas.drawPoint(lastX, lastY, paint);
311                     drawn = true;
312                 }
313                 lastX = x;
314                 lastY = y;
315                 haveLast = true;
316             }
317 
318             if (drawn) {
319                 // Draw velocity vector.
320                 mPaint.setARGB(255, 255, 64, 128);
321                 float xVel = ps.mXVelocity * (1000 / 60);
322                 float yVel = ps.mYVelocity * (1000 / 60);
323                 canvas.drawLine(lastX, lastY, lastX + xVel, lastY + yVel, mPaint);
324 
325                 // Draw velocity vector using an alternate VelocityTracker strategy.
326                 if (mAltVelocity != null) {
327                     mPaint.setARGB(255, 64, 255, 128);
328                     xVel = ps.mAltXVelocity * (1000 / 60);
329                     yVel = ps.mAltYVelocity * (1000 / 60);
330                     canvas.drawLine(lastX, lastY, lastX + xVel, lastY + yVel, mPaint);
331                 }
332             }
333 
334             if (mCurDown && ps.mCurDown) {
335                 // Draw crosshairs.
336                 canvas.drawLine(0, ps.mCoords.y, getWidth(), ps.mCoords.y, mTargetPaint);
337                 // Extend crosshairs to cover screen regardless of rotation (ie. since the rotated
338                 // canvas can "expose" content past 0 and up-to the largest screen dimension).
339                 canvas.drawLine(ps.mCoords.x, -getHeight(), ps.mCoords.x,
340                         Math.max(getHeight(), getWidth()), mTargetPaint);
341 
342                 // Draw current point.
343                 int pressureLevel = (int)(ps.mCoords.pressure * 255);
344                 mPaint.setARGB(255, pressureLevel, 255, 255 - pressureLevel);
345                 canvas.drawPoint(ps.mCoords.x, ps.mCoords.y, mPaint);
346 
347                 // Draw current touch ellipse.
348                 mPaint.setARGB(255, pressureLevel, 255 - pressureLevel, 128);
349                 drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.touchMajor,
350                         ps.mCoords.touchMinor, ps.mCoords.orientation, mPaint);
351 
352                 // Draw current tool ellipse.
353                 mPaint.setARGB(255, pressureLevel, 128, 255 - pressureLevel);
354                 drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.toolMajor,
355                         ps.mCoords.toolMinor, ps.mCoords.orientation, mPaint);
356 
357                 // Draw the orientation arrow.
358                 float arrowSize = ps.mCoords.toolMajor * 0.7f;
359                 if (arrowSize < 20) {
360                     arrowSize = 20;
361                 }
362                 mPaint.setARGB(255, pressureLevel, 255, 0);
363                 float orientationVectorX = (float) (Math.sin(ps.mCoords.orientation)
364                         * arrowSize);
365                 float orientationVectorY = (float) (-Math.cos(ps.mCoords.orientation)
366                         * arrowSize);
367                 if (ps.mToolType == MotionEvent.TOOL_TYPE_STYLUS
368                         || ps.mToolType == MotionEvent.TOOL_TYPE_ERASER) {
369                     // Show full circle orientation.
370                     canvas.drawLine(ps.mCoords.x, ps.mCoords.y,
371                             ps.mCoords.x + orientationVectorX,
372                             ps.mCoords.y + orientationVectorY,
373                             mPaint);
374                 } else {
375                     // Show half circle orientation.
376                     canvas.drawLine(
377                             ps.mCoords.x - orientationVectorX,
378                             ps.mCoords.y - orientationVectorY,
379                             ps.mCoords.x + orientationVectorX,
380                             ps.mCoords.y + orientationVectorY,
381                             mPaint);
382                 }
383 
384                 // Draw the tilt point along the orientation arrow.
385                 float tiltScale = (float) Math.sin(
386                         ps.mCoords.getAxisValue(MotionEvent.AXIS_TILT));
387                 canvas.drawCircle(
388                         ps.mCoords.x + orientationVectorX * tiltScale,
389                         ps.mCoords.y + orientationVectorY * tiltScale,
390                         3.0f, mPaint);
391 
392                 // Draw the current bounding box
393                 if (ps.mHasBoundingBox) {
394                     canvas.drawRect(ps.mBoundingLeft, ps.mBoundingTop,
395                             ps.mBoundingRight, ps.mBoundingBottom, mPaint);
396                 }
397             }
398         }
399     }
400 
drawLabels(Canvas canvas)401     private void drawLabels(Canvas canvas) {
402         if (mActivePointerId < 0) {
403             return;
404         }
405 
406         final int w = getWidth() - mWaterfallInsets.left - mWaterfallInsets.right;
407         final int itemW = w / 7;
408         final int base = mHeaderPaddingTop - mTextMetrics.ascent + 1;
409         final int bottom = mHeaderBottom;
410 
411         canvas.save();
412         canvas.translate(mWaterfallInsets.left, 0);
413         final PointerState ps = mPointers.get(mActivePointerId);
414 
415         canvas.drawRect(0, mHeaderPaddingTop, itemW - 1, bottom, mTextBackgroundPaint);
416         canvas.drawText(mText.clear()
417                 .append("P: ").append(mCurNumPointers)
418                 .append(" / ").append(mMaxNumPointers)
419                 .toString(), 1, base, mTextPaint);
420 
421         final int count = ps.mTraceCount;
422         if ((mCurDown && ps.mCurDown) || count == 0) {
423             canvas.drawRect(itemW, mHeaderPaddingTop, (itemW * 2) - 1, bottom,
424                     mTextBackgroundPaint);
425             canvas.drawText(mText.clear()
426                     .append("X: ").append(ps.mCoords.x, 1)
427                     .toString(), 1 + itemW, base, mTextPaint);
428             canvas.drawRect(itemW * 2, mHeaderPaddingTop, (itemW * 3) - 1, bottom,
429                     mTextBackgroundPaint);
430             canvas.drawText(mText.clear()
431                     .append("Y: ").append(ps.mCoords.y, 1)
432                     .toString(), 1 + itemW * 2, base, mTextPaint);
433         } else {
434             float dx = ps.mTraceX[count - 1] - ps.mTraceX[0];
435             float dy = ps.mTraceY[count - 1] - ps.mTraceY[0];
436             canvas.drawRect(itemW, mHeaderPaddingTop, (itemW * 2) - 1, bottom,
437                     Math.abs(dx) < mVC.getScaledTouchSlop()
438                             ? mTextBackgroundPaint : mTextLevelPaint);
439             canvas.drawText(mText.clear()
440                     .append("dX: ").append(dx, 1)
441                     .toString(), 1 + itemW, base, mTextPaint);
442             canvas.drawRect(itemW * 2, mHeaderPaddingTop, (itemW * 3) - 1, bottom,
443                     Math.abs(dy) < mVC.getScaledTouchSlop()
444                             ? mTextBackgroundPaint : mTextLevelPaint);
445             canvas.drawText(mText.clear()
446                     .append("dY: ").append(dy, 1)
447                     .toString(), 1 + itemW * 2, base, mTextPaint);
448         }
449 
450         canvas.drawRect(itemW * 3, mHeaderPaddingTop, (itemW * 4) - 1, bottom,
451                 mTextBackgroundPaint);
452         canvas.drawText(mText.clear()
453                 .append("Xv: ").append(ps.mXVelocity, 3)
454                 .toString(), 1 + itemW * 3, base, mTextPaint);
455 
456         canvas.drawRect(itemW * 4, mHeaderPaddingTop, (itemW * 5) - 1, bottom,
457                 mTextBackgroundPaint);
458         canvas.drawText(mText.clear()
459                 .append("Yv: ").append(ps.mYVelocity, 3)
460                 .toString(), 1 + itemW * 4, base, mTextPaint);
461 
462         canvas.drawRect(itemW * 5, mHeaderPaddingTop, (itemW * 6) - 1, bottom,
463                 mTextBackgroundPaint);
464         canvas.drawRect(itemW * 5, mHeaderPaddingTop,
465                 (itemW * 5) + (ps.mCoords.pressure * itemW) - 1, bottom, mTextLevelPaint);
466         canvas.drawText(mText.clear()
467                 .append("Prs: ").append(ps.mCoords.pressure, 2)
468                 .toString(), 1 + itemW * 5, base, mTextPaint);
469 
470         canvas.drawRect(itemW * 6, mHeaderPaddingTop, w, bottom, mTextBackgroundPaint);
471         canvas.drawRect(itemW * 6, mHeaderPaddingTop,
472                 (itemW * 6) + (ps.mCoords.size * itemW) - 1, bottom, mTextLevelPaint);
473         canvas.drawText(mText.clear()
474                 .append("Size: ").append(ps.mCoords.size, 2)
475                 .toString(), 1 + itemW * 6, base, mTextPaint);
476         canvas.restore();
477     }
478 
logMotionEvent(String type, MotionEvent event)479     private void logMotionEvent(String type, MotionEvent event) {
480         final int action = event.getAction();
481         final int N = event.getHistorySize();
482         final int NI = event.getPointerCount();
483         for (int historyPos = 0; historyPos < N; historyPos++) {
484             for (int i = 0; i < NI; i++) {
485                 final int id = event.getPointerId(i);
486                 event.getHistoricalPointerCoords(i, historyPos, mTempCoords);
487                 logCoords(type, action, i, mTempCoords, id, event);
488             }
489         }
490         for (int i = 0; i < NI; i++) {
491             final int id = event.getPointerId(i);
492             event.getPointerCoords(i, mTempCoords);
493             logCoords(type, action, i, mTempCoords, id, event);
494         }
495     }
496 
logCoords(String type, int action, int index, MotionEvent.PointerCoords coords, int id, MotionEvent event)497     private void logCoords(String type, int action, int index,
498             MotionEvent.PointerCoords coords, int id, MotionEvent event) {
499         final int toolType = event.getToolType(index);
500         final int buttonState = event.getButtonState();
501         final String prefix;
502         switch (action & MotionEvent.ACTION_MASK) {
503             case MotionEvent.ACTION_DOWN:
504                 prefix = "DOWN";
505                 break;
506             case MotionEvent.ACTION_UP:
507                 prefix = "UP";
508                 break;
509             case MotionEvent.ACTION_MOVE:
510                 prefix = "MOVE";
511                 break;
512             case MotionEvent.ACTION_CANCEL:
513                 prefix = "CANCEL";
514                 break;
515             case MotionEvent.ACTION_OUTSIDE:
516                 prefix = "OUTSIDE";
517                 break;
518             case MotionEvent.ACTION_POINTER_DOWN:
519                 if (index == ((action & MotionEvent.ACTION_POINTER_INDEX_MASK)
520                         >> MotionEvent.ACTION_POINTER_INDEX_SHIFT)) {
521                     prefix = "DOWN";
522                 } else {
523                     prefix = "MOVE";
524                 }
525                 break;
526             case MotionEvent.ACTION_POINTER_UP:
527                 if (index == ((action & MotionEvent.ACTION_POINTER_INDEX_MASK)
528                         >> MotionEvent.ACTION_POINTER_INDEX_SHIFT)) {
529                     prefix = "UP";
530                 } else {
531                     prefix = "MOVE";
532                 }
533                 break;
534             case MotionEvent.ACTION_HOVER_MOVE:
535                 prefix = "HOVER MOVE";
536                 break;
537             case MotionEvent.ACTION_HOVER_ENTER:
538                 prefix = "HOVER ENTER";
539                 break;
540             case MotionEvent.ACTION_HOVER_EXIT:
541                 prefix = "HOVER EXIT";
542                 break;
543             case MotionEvent.ACTION_SCROLL:
544                 prefix = "SCROLL";
545                 break;
546             default:
547                 prefix = Integer.toString(action);
548                 break;
549         }
550 
551         Log.i(TAG, mText.clear()
552                 .append(type).append(" id ").append(id + 1)
553                 .append(": ")
554                 .append(prefix)
555                 .append(" (").append(coords.x, 3).append(", ").append(coords.y, 3)
556                 .append(") Pressure=").append(coords.pressure, 3)
557                 .append(" Size=").append(coords.size, 3)
558                 .append(" TouchMajor=").append(coords.touchMajor, 3)
559                 .append(" TouchMinor=").append(coords.touchMinor, 3)
560                 .append(" ToolMajor=").append(coords.toolMajor, 3)
561                 .append(" ToolMinor=").append(coords.toolMinor, 3)
562                 .append(" Orientation=").append((float)(coords.orientation * 180 / Math.PI), 1)
563                 .append("deg")
564                 .append(" Tilt=").append((float)(
565                         coords.getAxisValue(MotionEvent.AXIS_TILT) * 180 / Math.PI), 1)
566                 .append("deg")
567                 .append(" Distance=").append(coords.getAxisValue(MotionEvent.AXIS_DISTANCE), 1)
568                 .append(" VScroll=").append(coords.getAxisValue(MotionEvent.AXIS_VSCROLL), 1)
569                 .append(" HScroll=").append(coords.getAxisValue(MotionEvent.AXIS_HSCROLL), 1)
570                 .append(" BoundingBox=[(")
571                 .append(event.getAxisValue(MotionEvent.AXIS_GENERIC_1), 3)
572                 .append(", ").append(event.getAxisValue(MotionEvent.AXIS_GENERIC_2), 3).append(")")
573                 .append(", (").append(event.getAxisValue(MotionEvent.AXIS_GENERIC_3), 3)
574                 .append(", ").append(event.getAxisValue(MotionEvent.AXIS_GENERIC_4), 3)
575                 .append(")]")
576                 .append(" ToolType=").append(MotionEvent.toolTypeToString(toolType))
577                 .append(" ButtonState=").append(MotionEvent.buttonStateToString(buttonState))
578                 .toString());
579     }
580 
581     @Override
onPointerEvent(MotionEvent event)582     public void onPointerEvent(MotionEvent event) {
583         final int action = event.getAction();
584         int NP = mPointers.size();
585 
586         if (action == MotionEvent.ACTION_DOWN
587                 || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN) {
588             final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
589                     >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for down
590             if (action == MotionEvent.ACTION_DOWN) {
591                 for (int p=0; p<NP; p++) {
592                     final PointerState ps = mPointers.get(p);
593                     ps.clearTrace();
594                     ps.mCurDown = false;
595                 }
596                 mCurDown = true;
597                 mCurNumPointers = 0;
598                 mMaxNumPointers = 0;
599                 mVelocity.clear();
600                 if (mAltVelocity != null) {
601                     mAltVelocity.clear();
602                 }
603             }
604 
605             mCurNumPointers += 1;
606             if (mMaxNumPointers < mCurNumPointers) {
607                 mMaxNumPointers = mCurNumPointers;
608             }
609 
610             final int id = event.getPointerId(index);
611             while (NP <= id) {
612                 PointerState ps = new PointerState();
613                 mPointers.add(ps);
614                 NP++;
615             }
616 
617             if (mActivePointerId < 0 ||
618                     !mPointers.get(mActivePointerId).mCurDown) {
619                 mActivePointerId = id;
620             }
621 
622             final PointerState ps = mPointers.get(id);
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                 mVelocity.getEstimator(id, ps.mEstimator);
666                 if (mAltVelocity != null) {
667                     ps.mAltXVelocity = mAltVelocity.getXVelocity(id);
668                     ps.mAltYVelocity = mAltVelocity.getYVelocity(id);
669                     mAltVelocity.getEstimator(id, ps.mAltEstimator);
670                 }
671                 ps.mToolType = event.getToolType(i);
672 
673                 if (ps.mHasBoundingBox) {
674                     ps.mBoundingLeft = event.getAxisValue(MotionEvent.AXIS_GENERIC_1, i);
675                     ps.mBoundingTop = event.getAxisValue(MotionEvent.AXIS_GENERIC_2, i);
676                     ps.mBoundingRight = event.getAxisValue(MotionEvent.AXIS_GENERIC_3, i);
677                     ps.mBoundingBottom = event.getAxisValue(MotionEvent.AXIS_GENERIC_4, i);
678                 }
679             }
680         }
681 
682         if (action == MotionEvent.ACTION_UP
683                 || action == MotionEvent.ACTION_CANCEL
684                 || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP) {
685             final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
686                     >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for UP
687 
688             final int id = event.getPointerId(index);
689             if (id >= NP) {
690                 Slog.wtf(TAG, "Got pointer ID out of bounds: id=" + id + " arraysize="
691                         + NP + " pointerindex=" + index
692                         + " action=0x" + Integer.toHexString(action));
693                 return;
694             }
695             final PointerState ps = mPointers.get(id);
696             ps.mCurDown = false;
697 
698             if (action == MotionEvent.ACTION_UP
699                     || action == MotionEvent.ACTION_CANCEL) {
700                 mCurDown = false;
701                 mCurNumPointers = 0;
702             } else {
703                 mCurNumPointers -= 1;
704                 if (mActivePointerId == id) {
705                     mActivePointerId = event.getPointerId(index == 0 ? 1 : 0);
706                 }
707                 ps.addTrace(Float.NaN, Float.NaN, false);
708             }
709         }
710 
711         invalidate();
712     }
713 
714     @Override
onTouchEvent(MotionEvent event)715     public boolean onTouchEvent(MotionEvent event) {
716         onPointerEvent(event);
717 
718         if (event.getAction() == MotionEvent.ACTION_DOWN && !isFocused()) {
719             requestFocus();
720         }
721         return true;
722     }
723 
724     @Override
onGenericMotionEvent(MotionEvent event)725     public boolean onGenericMotionEvent(MotionEvent event) {
726         final int source = event.getSource();
727         if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
728             onPointerEvent(event);
729         } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
730             logMotionEvent("Joystick", event);
731         } else if ((source & InputDevice.SOURCE_CLASS_POSITION) != 0) {
732             logMotionEvent("Position", event);
733         } else {
734             logMotionEvent("Generic", event);
735         }
736         return true;
737     }
738 
739     @Override
onKeyDown(int keyCode, KeyEvent event)740     public boolean onKeyDown(int keyCode, KeyEvent event) {
741         if (shouldLogKey(keyCode)) {
742             final int repeatCount = event.getRepeatCount();
743             if (repeatCount == 0) {
744                 Log.i(TAG, "Key Down: " + event);
745             } else {
746                 Log.i(TAG, "Key Repeat #" + repeatCount + ": " + event);
747             }
748             return true;
749         }
750         return super.onKeyDown(keyCode, event);
751     }
752 
753     @Override
onKeyUp(int keyCode, KeyEvent event)754     public boolean onKeyUp(int keyCode, KeyEvent event) {
755         if (shouldLogKey(keyCode)) {
756             Log.i(TAG, "Key Up: " + event);
757             return true;
758         }
759         return super.onKeyUp(keyCode, event);
760     }
761 
shouldLogKey(int keyCode)762     private static boolean shouldLogKey(int keyCode) {
763         switch (keyCode) {
764             case KeyEvent.KEYCODE_DPAD_UP:
765             case KeyEvent.KEYCODE_DPAD_DOWN:
766             case KeyEvent.KEYCODE_DPAD_LEFT:
767             case KeyEvent.KEYCODE_DPAD_RIGHT:
768             case KeyEvent.KEYCODE_DPAD_CENTER:
769                 return true;
770             default:
771                 return KeyEvent.isGamepadButton(keyCode)
772                     || KeyEvent.isModifierKey(keyCode);
773         }
774     }
775 
776     @Override
onTrackballEvent(MotionEvent event)777     public boolean onTrackballEvent(MotionEvent event) {
778         logMotionEvent("Trackball", event);
779         return true;
780     }
781 
782     @Override
onAttachedToWindow()783     protected void onAttachedToWindow() {
784         super.onAttachedToWindow();
785 
786         mIm.registerInputDeviceListener(this, getHandler());
787         if (shouldShowSystemGestureExclusion()) {
788             try {
789                 WindowManagerGlobal.getWindowManagerService()
790                         .registerSystemGestureExclusionListener(mSystemGestureExclusionListener,
791                                 mContext.getDisplayId());
792             } catch (RemoteException e) {
793                 throw e.rethrowFromSystemServer();
794             }
795             final int alpha = systemGestureExclusionOpacity();
796             mSystemGestureExclusionPaint.setAlpha(alpha);
797             mSystemGestureExclusionRejectedPaint.setAlpha(alpha);
798         } else {
799             mSystemGestureExclusion.setEmpty();
800         }
801         logInputDevices();
802     }
803 
804     @Override
onDetachedFromWindow()805     protected void onDetachedFromWindow() {
806         super.onDetachedFromWindow();
807 
808         mIm.unregisterInputDeviceListener(this);
809         try {
810             WindowManagerGlobal.getWindowManagerService().unregisterSystemGestureExclusionListener(
811                     mSystemGestureExclusionListener, mContext.getDisplayId());
812         } catch (RemoteException e) {
813             throw e.rethrowFromSystemServer();
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