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