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