summaryrefslogtreecommitdiffstats
path: root/services
diff options
context:
space:
mode:
authorSvetoslav Ganov <svetoslavganov@google.com>2011-04-04 16:09:08 -0700
committerSvetoslav Ganov <svetoslavganov@google.com>2011-04-21 18:33:52 -0700
commitac84d3ba81f08036308b17e1ab919e43987a3df5 (patch)
tree8bf9d7f0b110667e45186d90674465a63e868f26 /services
parent1cc1a41b5a095c2eae556c9be0a7ad3f2fc5bfbb (diff)
downloadframeworks_base-ac84d3ba81f08036308b17e1ab919e43987a3df5.zip
frameworks_base-ac84d3ba81f08036308b17e1ab919e43987a3df5.tar.gz
frameworks_base-ac84d3ba81f08036308b17e1ab919e43987a3df5.tar.bz2
Touch exploration feature, event bubling, refactor
1. Added an Input Filter that interprets the touch screen motion events to perfrom accessibility exploration. One finger explores. Tapping within a given time and distance slop on the last exlopred location does click and long press, respectively. Two fingers close and in the same diretion drag. Multiple finglers or two fingers in different directions or two fingers too far away are delegated to the view hierarchy. Non moving fingers "accidentally grabbed the device for the scrren" are ignored. 2. Added accessibility events for hover enter, hover exit, touch exoloration gesture start, and end. Accessibility hover events are fired by the hover pipeline. An accessibility event is dispatched up the view tree and the topmost view fires it. Thus predecessors can augment the fired event. An accessibility event has several records and a predecessor can optionally modify, delete, and add such to the event. 3. Added onPopulateAccessibilityEvent and refactored the existing accessibility code to use it. 4. Added API for querying the currently enabled accessibility services by feedback type. Change-Id: Iec03c6c3fe298de3f14cb6efdbb9b198cd531a0c
Diffstat (limited to 'services')
-rw-r--r--services/java/com/android/server/accessibility/AccessibilityInputFilter.java65
-rw-r--r--services/java/com/android/server/accessibility/AccessibilityManagerService.java74
-rw-r--r--services/java/com/android/server/accessibility/TouchExplorer.java1540
-rw-r--r--services/java/com/android/server/wm/InputFilter.java6
4 files changed, 1657 insertions, 28 deletions
diff --git a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
index ced8feb..8ba0a0b 100644
--- a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -20,8 +20,8 @@ import com.android.server.wm.InputFilter;
import android.content.Context;
import android.util.Slog;
+import android.view.InputDevice;
import android.view.InputEvent;
-import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.WindowManagerPolicy;
@@ -32,10 +32,35 @@ import android.view.WindowManagerPolicy;
*/
public class AccessibilityInputFilter extends InputFilter {
private static final String TAG = "AccessibilityInputFilter";
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
private final Context mContext;
+ /**
+ * This is an interface for explorers that take a {@link MotionEvent}
+ * stream and perform touch exploration of the screen content.
+ */
+ public interface Explorer {
+ /**
+ * Handles a {@link MotionEvent}.
+ *
+ * @param event The event to handle.
+ * @param policyFlags The policy flags associated with the event.
+ */
+ public void onMotionEvent(MotionEvent event, int policyFlags);
+
+ /**
+ * Requests that the explorer clears its internal state.
+ *
+ * @param event The last received event.
+ * @param policyFlags The policy flags associated with the event.
+ */
+ public void clear(MotionEvent event, int policyFlags);
+ }
+
+ private TouchExplorer mTouchExplorer;
+ private int mTouchscreenSourceDeviceId;
+
public AccessibilityInputFilter(Context context) {
super(context.getMainLooper());
mContext = context;
@@ -60,27 +85,27 @@ public class AccessibilityInputFilter extends InputFilter {
@Override
public void onInputEvent(InputEvent event, int policyFlags) {
if (DEBUG) {
- Slog.d(TAG, "Accessibility input filter received input event: "
- + event + ", policyFlags=0x" + Integer.toHexString(policyFlags));
+ Slog.d(TAG, "Received event: " + event + ", policyFlags=0x"
+ + Integer.toHexString(policyFlags));
}
-
- // To prove that this is working as intended, we will silently transform
- // Q key presses into non-repeating Z's as part of this stub implementation.
- // TODO: Replace with the real thing.
- if (event instanceof KeyEvent) {
- final KeyEvent keyEvent = (KeyEvent)event;
- if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_Q) {
- if (keyEvent.getRepeatCount() == 0) {
- sendInputEvent(new KeyEvent(keyEvent.getDownTime(), keyEvent.getEventTime(),
- keyEvent.getAction(), KeyEvent.KEYCODE_Z, keyEvent.getRepeatCount(),
- keyEvent.getMetaState(), keyEvent.getDeviceId(), keyEvent.getScanCode(),
- keyEvent.getFlags(), keyEvent.getSource()),
- policyFlags | WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT);
+ if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
+ MotionEvent motionEvent = (MotionEvent) event;
+ int deviceId = event.getDeviceId();
+ if (mTouchscreenSourceDeviceId != deviceId) {
+ mTouchscreenSourceDeviceId = deviceId;
+ if (mTouchExplorer != null) {
+ mTouchExplorer.clear(motionEvent, policyFlags);
+ } else {
+ mTouchExplorer = new TouchExplorer(this, mContext);
}
- return;
}
+ if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) != 0) {
+ mTouchExplorer.onMotionEvent(motionEvent, policyFlags);
+ } else {
+ mTouchExplorer.clear(motionEvent, policyFlags);
+ }
+ } else {
+ super.onInputEvent(event, policyFlags);
}
-
- super.onInputEvent(event, policyFlags);
}
}
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index 7a483aa..1ad8047 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -56,6 +56,7 @@ import android.view.accessibility.IAccessibilityManagerClient;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -74,6 +75,8 @@ import java.util.Set;
public class AccessibilityManagerService extends IAccessibilityManager.Stub
implements HandlerCaller.Callback {
+ private static final boolean DEBUG = false;
+
private static final String LOG_TAG = "AccessibilityManagerService";
private static int sIdCounter = 0;
@@ -102,6 +105,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private final SimpleStringSplitter mStringColonSplitter = new SimpleStringSplitter(':');
+ private final SparseArray<List<ServiceInfo>> mFeedbackTypeToEnabledServicesMap =
+ new SparseArray<List<ServiceInfo>>();
+
private PackageManager mPackageManager;
private int mHandledFeedbackTypes = 0;
@@ -211,7 +217,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
manageServicesLocked();
- updateInputFilterLocked();
}
return;
@@ -252,7 +257,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
unbindAllServicesLocked();
}
updateClientsLocked();
- updateInputFilterLocked();
}
}
});
@@ -300,6 +304,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
+ public List<ServiceInfo> getEnabledAccessibilityServiceList(int feedbackType) {
+ synchronized (mLock) {
+ List<ServiceInfo> enabledServices = mFeedbackTypeToEnabledServicesMap.get(feedbackType);
+ if (enabledServices == null) {
+ return Collections.emptyList();
+ }
+ return enabledServices;
+ }
+ }
+
public void interrupt() {
synchronized (mLock) {
for (int i = 0, count = mServices.size(); i < count; i++) {
@@ -339,6 +353,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
service.mNotificationTimeout = info.notificationTimeout;
service.mIsDefault = (info.flags & AccessibilityServiceInfo.DEFAULT) != 0;
+
+ updateStateOnEnabledService(service);
}
return;
default:
@@ -449,7 +465,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
try {
listener.onAccessibilityEvent(event);
- if (false) {
+ if (DEBUG) {
Slog.i(LOG_TAG, "Event " + event + " sent to " + listener);
}
} catch (RemoteException re) {
@@ -469,10 +485,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
* @return True if the service was removed, false otherwise.
*/
private boolean removeDeadServiceLocked(Service service) {
- if (false) {
+ if (DEBUG) {
Slog.i(LOG_TAG, "Dead service " + service.mService + " removed");
}
mHandler.removeMessages(service.mId);
+ updateStateOnDisabledService(service);
return mServices.remove(service);
}
@@ -593,7 +610,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (isEnabled) {
if (enabledServices.contains(componentName)) {
if (service == null) {
- service = new Service(componentName);
+ service = new Service(componentName, intalledService);
}
service.bind();
} else if (!enabledServices.contains(componentName)) {
@@ -644,6 +661,47 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
/**
+ * Updates the set of enabled services for a given feedback type and
+ * if more than one of them provides spoken feedback enables touch
+ * exploration.
+ *
+ * @param service An enable service.
+ */
+ private void updateStateOnEnabledService(Service service) {
+ int feedbackType = service.mFeedbackType;
+ List<ServiceInfo> enabledServices = mFeedbackTypeToEnabledServicesMap.get(feedbackType);
+ if (enabledServices == null) {
+ enabledServices = new ArrayList<ServiceInfo>();
+ mFeedbackTypeToEnabledServicesMap.put(feedbackType, enabledServices);
+ }
+ enabledServices.add(service.mServiceInfo);
+
+ // We enable touch exploration if at least one
+ // enabled service provides spoken feedback.
+ if (enabledServices.size() > 0
+ && service.mFeedbackType == AccessibilityServiceInfo.FEEDBACK_SPOKEN) {
+ updateClientsLocked();
+ updateInputFilterLocked();
+ }
+ }
+
+ private void updateStateOnDisabledService(Service service) {
+ List<ServiceInfo> enabledServices =
+ mFeedbackTypeToEnabledServicesMap.get(service.mFeedbackType);
+ if (enabledServices == null) {
+ return;
+ }
+ enabledServices.remove(service.mServiceInfo);
+ // We disable touch exploration if no
+ // enabled service provides spoken feedback.
+ if (enabledServices.isEmpty()
+ && service.mFeedbackType == AccessibilityServiceInfo.FEEDBACK_SPOKEN) {
+ updateClientsLocked();
+ updateInputFilterLocked();
+ }
+ }
+
+ /**
* This class represents an accessibility service. It stores all per service
* data required for the service management, provides API for starting/stopping the
* service and is responsible for adding/removing the service in the data structures
@@ -654,6 +712,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
class Service extends IAccessibilityServiceConnection.Stub implements ServiceConnection {
int mId = 0;
+ ServiceInfo mServiceInfo;
+
IBinder mService;
IEventListener mServiceInterface;
@@ -678,9 +738,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
final SparseArray<AccessibilityEvent> mPendingEvents =
new SparseArray<AccessibilityEvent>();
- Service(ComponentName componentName) {
+ Service(ComponentName componentName, ServiceInfo serviceInfo) {
mId = sIdCounter++;
mComponentName = componentName;
+ mServiceInfo = serviceInfo;
mIntent = new Intent().setComponent(mComponentName);
mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
com.android.internal.R.string.accessibility_binding_label);
@@ -712,6 +773,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mContext.unbindService(this);
mComponentNameToServiceMap.remove(mComponentName);
mServices.remove(this);
+ updateStateOnDisabledService(this);
return true;
}
return false;
diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java
new file mode 100644
index 0000000..4ba6060
--- /dev/null
+++ b/services/java/com/android/server/accessibility/TouchExplorer.java
@@ -0,0 +1,1540 @@
+/*
+ ** Copyright 2011, The Android Open Source Project
+ **
+ ** Licensed under the Apache License, Version 2.0 (the "License");
+ ** you may not use this file except in compliance with the License.
+ ** You may obtain a copy of the License at
+ **
+ ** http://www.apache.org/licenses/LICENSE-2.0
+ **
+ ** Unless required by applicable law or agreed to in writing, software
+ ** distributed under the License is distributed on an "AS IS" BASIS,
+ ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ** See the License for the specific language governing permissions and
+ ** limitations under the License.
+ */
+
+package com.android.server.accessibility;
+
+import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END;
+import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START;
+
+import com.android.server.accessibility.AccessibilityInputFilter.Explorer;
+import com.android.server.wm.InputFilter;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+import android.view.WindowManagerPolicy;
+import android.view.MotionEvent.PointerCoords;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+
+import java.util.Arrays;
+
+/**
+ * This class is a strategy for performing touch exploration. It
+ * transforms the motion event stream by modifying, adding, replacing,
+ * and consuming certain events. The interaction model is:
+ *
+ * <ol>
+ * <li>1. One finger moving around performs touch exploration.</li>
+ * <li>2. Two close fingers moving in the same direction perform a drag.</li>
+ * <li>3. Multi-finger gestures are delivered to view hierarchy.</li>
+ * <li>4. Pointers that have not moved more than a specified distance after they
+ * went down are considered inactive.</li>
+ * <li>5. Two fingers moving too far from each other or in different directions
+ * are considered a multi-finger gesture.</li>
+ * <li>6. Tapping on the last touch explored location within given time and
+ * distance slop performs a click.</li>
+ * <li>7. Tapping and holding for a while on the last touch explored location within
+ * given time and distance slop performs a long press.</li>
+ * <ol>
+ *
+ * @hide
+ */
+public class TouchExplorer implements Explorer {
+ private static final boolean DEBUG = false;
+
+ // Tag for logging received events.
+ private static final String LOG_TAG_RECEIVED = "TouchExplorer-RECEIVED";
+ // Tag for logging injected events.
+ private static final String LOG_TAG_INJECTED = "TouchExplorer-INJECTED";
+ // Tag for logging the current state.
+ private static final String LOG_TAG_STATE = "TouchExplorer-STATE";
+
+ // States this explorer can be in.
+ private static final int STATE_TOUCH_EXPLORING = 0x00000001;
+ private static final int STATE_DRAGGING = 0x00000002;
+ private static final int STATE_DELEGATING = 0x00000004;
+
+ // Human readable symbolic names for the states of the explorer.
+ private static final SparseArray<String> sStateSymbolicNames = new SparseArray<String>();
+ static {
+ SparseArray<String> symbolicNames = sStateSymbolicNames;
+ symbolicNames.append(STATE_TOUCH_EXPLORING, "STATE_TOUCH_EXPLORING");
+ symbolicNames.append(STATE_DRAGGING, "STATE_DRAGING");
+ symbolicNames.append(STATE_DELEGATING, "STATE_DELEGATING");
+ }
+
+ // Invalid pointer ID.
+ private static final int INVALID_POINTER_ID = -1;
+
+ // The coefficient by which to multiply
+ // ViewConfiguration.#getScaledTouchExplorationTapSlop()
+ // to compute #mDraggingDistance.
+ private static final int COEFFICIENT_DRAGGING_DISTANCE = 2;
+
+ // The time slop in milliseconds for activating an item after it has
+ // been touch explored. Tapping on an item within this slop will perform
+ // a click and tapping and holding down a long press.
+ private static final long ACTIVATION_TIME_SLOP = 2000;
+
+ // This constant captures the current implementation detail that
+ // pointer IDs are between 0 and 31 inclusive (subject to change).
+ // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h)
+ private static final int MAX_POINTER_COUNT = 32;
+
+ // The minimum of the cosine between the vectors of two moving
+ // pointers so they can be considered moving in the same direction.
+ private static final float MIN_ANGLE_COS = 0.866025404f; // cos(pi/6)
+
+ // The delay for sending a hover enter event.
+ private static final long DELAY_SEND_HOVER_MOVE = 200;
+
+ // Temporary array for storing pointer IDs.
+ private final int[] mTempPointerIds = new int[MAX_POINTER_COUNT];
+
+ // Temporary array for mapping new to old pointer IDs while filtering inactive pointers.
+ private final int [] mTempNewToOldPointerIndexMap = new int[MAX_POINTER_COUNT];
+
+ // Temporary array for storing PointerCoords
+ private final PointerCoords[] mTempPointerCoords= new PointerCoords[MAX_POINTER_COUNT];
+
+ // The maximal distance between two pointers so they are
+ // considered to be performing a drag operation.
+ private final float mDraggingDistance;
+
+ // The distance from the last touch explored location tapping within
+ // which would perform a click and tapping and holding a long press.
+ private final int mTouchExplorationTapSlop;
+
+ // Context handle for accessing resources.
+ private final Context mContext;
+
+ // The InputFilter this tracker is associated with i.e. the filter
+ // which delegates event processing to this touch explorer.
+ private final InputFilter mInputFilter;
+
+ // Helper class for tracking pointers on the screen, for example which
+ // pointers are down, which are active, etc.
+ private final PointerTracker mPointerTracker;
+
+ // Handle to the accessibility manager for firing accessibility events
+ // announcing touch exploration gesture start and end.
+ private final AccessibilityManager mAccessibilityManager;
+
+ // The last event that was received while performing touch exploration.
+ private MotionEvent mLastTouchExploreEvent;
+
+ // The current state of the touch explorer.
+ private int mCurrentState = STATE_TOUCH_EXPLORING;
+
+ // Flag whether a touch exploration gesture is in progress.
+ private boolean mTouchExploreGestureInProgress;
+
+ // The ID of the pointer used for dragging.
+ private int mDraggingPointerId;
+
+ // Handler for performing asynchronous operations.
+ private final Handler mHandler;
+
+ // Command for delayed sending of a hover event.
+ private final SendHoverDelayed mSendHoverDelayed;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param inputFilter The input filter associated with this explorer.
+ * @param context A context handle for accessing resources.
+ */
+ public TouchExplorer(InputFilter inputFilter, Context context) {
+ mInputFilter = inputFilter;
+ mTouchExplorationTapSlop =
+ ViewConfiguration.get(context).getScaledTouchExplorationTapSlop();
+ mDraggingDistance = mTouchExplorationTapSlop * COEFFICIENT_DRAGGING_DISTANCE;
+ mPointerTracker = new PointerTracker(context);
+ mContext = context;
+ mHandler = new Handler(context.getMainLooper());
+ mSendHoverDelayed = new SendHoverDelayed();
+ mAccessibilityManager = AccessibilityManager.getInstance(context);
+
+ // Populate the temporary array with PointerCorrds to be reused.
+ for (int i = 0, count = mTempPointerCoords.length; i < count; i++) {
+ mTempPointerCoords[i] = new PointerCoords();
+ }
+ }
+
+ public void clear(MotionEvent event, int policyFlags) {
+ sendUpForInjectedDownPointers(event, policyFlags);
+ clear();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void onMotionEvent(MotionEvent event, int policyFlags) {
+ if (DEBUG) {
+ Slog.d(LOG_TAG_RECEIVED, "Received event: " + event + ", policyFlags=0x"
+ + Integer.toHexString(policyFlags));
+ Slog.d(LOG_TAG_STATE, sStateSymbolicNames.get(mCurrentState));
+ }
+
+ // Keep track of the pointers's state.
+ mPointerTracker.onReceivedMotionEvent(event);
+
+ switch(mCurrentState) {
+ case STATE_TOUCH_EXPLORING: {
+ handleMotionEventStateTouchExploring(event, policyFlags);
+ } break;
+ case STATE_DRAGGING: {
+ handleMotionEventStateDragging(event, policyFlags);
+ } break;
+ case STATE_DELEGATING: {
+ handleMotionEventStateDelegating(event, policyFlags);
+ } break;
+ default: {
+ throw new IllegalStateException("Illegal state: " + mCurrentState);
+ }
+ }
+ }
+
+ /**
+ * Handles a motion event in touch exploring state.
+ *
+ * @param event The event to be handled.
+ * @param policyFlags The policy flags associated with the event.
+ */
+ private void handleMotionEventStateTouchExploring(MotionEvent event, int policyFlags) {
+ PointerTracker pointerTracker = mPointerTracker;
+ final int activePointerCount = pointerTracker.getActivePointerCount();
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN: {
+ // Send a hover for every finger down so the user gets feedback
+ // where she is currently touching.
+ mSendHoverDelayed.forceSendAndRemove();
+ mSendHoverDelayed.post(event, MotionEvent.ACTION_HOVER_ENTER, 0, policyFlags,
+ DELAY_SEND_HOVER_MOVE);
+ } break;
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ switch (activePointerCount) {
+ case 0: {
+ throw new IllegalStateException("The must always be one active pointer in"
+ + "touch exploring state!");
+ }
+ case 1: {
+ // Schedule a hover event which will lead to firing an
+ // accessibility event from the hovered view.
+ mSendHoverDelayed.remove();
+ final int pointerId = pointerTracker.getPrimaryActivePointerId();
+ final int pointerIndex = event.findPointerIndex(pointerId);
+ final int lastAction = pointerTracker.getLastInjectedHoverAction();
+ // If a schedules hover enter for another pointer is delivered we send move.
+ final int action = (lastAction == MotionEvent.ACTION_HOVER_ENTER)
+ ? MotionEvent.ACTION_HOVER_MOVE
+ : MotionEvent.ACTION_HOVER_ENTER;
+ mSendHoverDelayed.post(event, action, pointerIndex, policyFlags,
+ DELAY_SEND_HOVER_MOVE);
+
+ if (mLastTouchExploreEvent == null) {
+ break;
+ }
+
+ // If more pointers down on the screen since the last touch
+ // exploration we discard the last cached touch explore event.
+ if (event.getPointerCount() != mLastTouchExploreEvent.getPointerCount()) {
+ mLastTouchExploreEvent = null;
+ }
+ } break;
+ default: {
+ /* do nothing - let the code for ACTION_MOVE decide what to do */
+ } break;
+ }
+ } break;
+ case MotionEvent.ACTION_MOVE: {
+ switch (activePointerCount) {
+ case 0: {
+ /* do nothing - no active pointers so we swallow the event */
+ } break;
+ case 1: {
+ final int pointerId = pointerTracker.getPrimaryActivePointerId();
+ final int pointerIndex = event.findPointerIndex(pointerId);
+
+ // Detect touch exploration gesture start by having one active pointer
+ // that moved more than a given distance.
+ if (!mTouchExploreGestureInProgress) {
+ final float deltaX = pointerTracker.getReceivedPointerDownX(pointerId)
+ - event.getX(pointerIndex);
+ final float deltaY = pointerTracker.getReceivedPointerDownY(pointerId)
+ - event.getY(pointerIndex);
+ final double moveDelta = Math.hypot(deltaX, deltaY);
+
+ if (moveDelta > mTouchExplorationTapSlop) {
+ mTouchExploreGestureInProgress = true;
+ sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_START);
+ // Make sure the scheduled down/move event is sent.
+ mSendHoverDelayed.forceSendAndRemove();
+ sendHoverEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIndex,
+ policyFlags);
+ }
+ } else {
+ // Touch exploration gesture in progress so send a hover event.
+ sendHoverEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIndex,
+ policyFlags);
+ }
+
+ // Detect long press on the last touch explored position.
+ if (!mTouchExploreGestureInProgress && mLastTouchExploreEvent != null) {
+
+ // If the down was not in the time slop => nothing else to do.
+ final long pointerDownTime =
+ pointerTracker.getReceivedPointerDownTime(pointerId);
+ final long lastExploreTime = mLastTouchExploreEvent.getEventTime();
+ final long deltaTimeExplore = pointerDownTime - lastExploreTime;
+ if (deltaTimeExplore > ACTIVATION_TIME_SLOP) {
+ mLastTouchExploreEvent = null;
+ break;
+ }
+
+ // If the pointer moved more than the tap slop => nothing else to do.
+ final float deltaX = mLastTouchExploreEvent.getX(pointerIndex)
+ - event.getX(pointerIndex);
+ final float deltaY = mLastTouchExploreEvent.getY(pointerIndex)
+ - event.getY(pointerIndex);
+ final float moveDelta = (float) Math.hypot(deltaX, deltaY);
+ if (moveDelta > mTouchExplorationTapSlop) {
+ mLastTouchExploreEvent = null;
+ break;
+ }
+
+ // If down for long enough we get a long press.
+ final long deltaTimeMove = event.getEventTime() - pointerDownTime;
+ if (deltaTimeMove > ViewConfiguration.getLongPressTimeout()) {
+ mCurrentState = STATE_DELEGATING;
+ // Make sure the scheduled hover exit is delivered.
+ mSendHoverDelayed.forceSendAndRemove();
+ sendDownForAllActiveNotInjectedPointers(event, policyFlags);
+ sendMotionEvent(event, policyFlags);
+ mTouchExploreGestureInProgress = false;
+ mLastTouchExploreEvent = null;
+ }
+ }
+ } break;
+ case 2: {
+ // Make sure the scheduled hover enter is delivered.
+ mSendHoverDelayed.forceSendAndRemove();
+ // We want to no longer hover over the location so subsequent
+ // touch at the same spot will generate a hover enter.
+ final int pointerId = pointerTracker.getPrimaryActivePointerId();
+ final int pointerIndex = event.findPointerIndex(pointerId);
+ sendHoverEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIndex,
+ policyFlags);
+
+ if (isDraggingGesture(event)) {
+ // Two pointers moving in the same direction within
+ // a given distance perform a drag.
+ mCurrentState = STATE_DRAGGING;
+ if (mTouchExploreGestureInProgress) {
+ sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END);
+ mTouchExploreGestureInProgress = false;
+ }
+ mDraggingPointerId = pointerTracker.getPrimaryActivePointerId();
+ sendDragEvent(event, MotionEvent.ACTION_DOWN, policyFlags);
+ } else {
+ // Two pointers moving arbitrary are delegated to the view hierarchy.
+ mCurrentState = STATE_DELEGATING;
+ if (mTouchExploreGestureInProgress) {
+ sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END);
+ mTouchExploreGestureInProgress = false;
+ }
+ sendDownForAllActiveNotInjectedPointers(event, policyFlags);
+ }
+ } break;
+ default: {
+ // Make sure the scheduled hover enter is delivered.
+ mSendHoverDelayed.forceSendAndRemove();
+ // We want to no longer hover over the location so subsequent
+ // touch at the same spot will generate a hover enter.
+ final int pointerId = pointerTracker.getPrimaryActivePointerId();
+ final int pointerIndex = event.findPointerIndex(pointerId);
+ sendHoverEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIndex,
+ policyFlags);
+
+ // More than two pointers are delegated to the view hierarchy.
+ mCurrentState = STATE_DELEGATING;
+ mSendHoverDelayed.remove();
+ if (mTouchExploreGestureInProgress) {
+ sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END);
+ mTouchExploreGestureInProgress = false;
+ }
+ sendDownForAllActiveNotInjectedPointers(event, policyFlags);
+ }
+ }
+ } break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_POINTER_UP: {
+ switch (activePointerCount) {
+ case 0: {
+ // If the pointer that went up was not active we have nothing to do.
+ if (!pointerTracker.wasLastReceivedUpPointerActive()) {
+ break;
+ }
+
+ // If touch exploring announce the end of the gesture.
+ if (mTouchExploreGestureInProgress) {
+ sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END);
+ mTouchExploreGestureInProgress = false;
+ }
+
+ // Detect whether to activate i.e. click on the last explored location.
+ if (mLastTouchExploreEvent != null) {
+ final int pointerId = pointerTracker.getLastReceivedUpPointerId();
+
+ // If the down was not in the time slop => nothing else to do.
+ final long eventTime =
+ pointerTracker.getLastReceivedUpPointerDownTime();
+ final long exploreTime = mLastTouchExploreEvent.getEventTime();
+ final long deltaTime = eventTime - exploreTime;
+ if (deltaTime > ACTIVATION_TIME_SLOP) {
+ mSendHoverDelayed.forceSendAndRemove();
+ scheduleHoverExit(event, policyFlags);
+ mLastTouchExploreEvent = MotionEvent.obtain(event);
+ break;
+ }
+
+ // If the pointer moved more than the tap slop => nothing else to do.
+ final int pointerIndex = event.findPointerIndex(pointerId);
+ final float deltaX = pointerTracker.getLastReceivedUpPointerDownX()
+ - event.getX(pointerIndex);
+ final float deltaY = pointerTracker.getLastReceivedUpPointerDownY()
+ - event.getY(pointerIndex);
+ final float deltaMove = (float) Math.hypot(deltaX, deltaY);
+ if (deltaMove > mTouchExplorationTapSlop) {
+ mSendHoverDelayed.forceSendAndRemove();
+ scheduleHoverExit(event, policyFlags);
+ mLastTouchExploreEvent = MotionEvent.obtain(event);
+ break;
+ }
+
+ // All preconditions are met, so click the last explored location.
+ mSendHoverDelayed.forceSendAndRemove();
+ sendActionDownAndUp(mLastTouchExploreEvent, policyFlags);
+ mLastTouchExploreEvent = null;
+ } else {
+ mSendHoverDelayed.forceSendAndRemove();
+ scheduleHoverExit(event, policyFlags);
+ mLastTouchExploreEvent = MotionEvent.obtain(event);
+ }
+ } break;
+ }
+ } break;
+ case MotionEvent.ACTION_CANCEL: {
+ final int lastAction = pointerTracker.getLastInjectedHoverAction();
+ if (lastAction != MotionEvent.ACTION_HOVER_EXIT) {
+ final int pointerId = pointerTracker.getPrimaryActivePointerId();
+ final int pointerIndex = event.findPointerIndex(pointerId);
+ sendHoverEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIndex,
+ policyFlags);
+ }
+ clear();
+ } break;
+ }
+ }
+
+ /**
+ * Handles a motion event in dragging state.
+ *
+ * @param event The event to be handled.
+ * @param policyFlags The policy flags associated with the event.
+ */
+ private void handleMotionEventStateDragging(MotionEvent event, int policyFlags) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN: {
+ throw new IllegalStateException("Dragging state can be reached only if two "
+ + "pointers are already down");
+ }
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ // We are in dragging state so we have two pointers and another one
+ // goes down => delegate the three pointers to the view hierarchy
+ mCurrentState = STATE_DELEGATING;
+ sendDragEvent(event, MotionEvent.ACTION_UP, policyFlags);
+ sendDownForAllActiveNotInjectedPointers(event, policyFlags);
+ } break;
+ case MotionEvent.ACTION_MOVE: {
+ final int activePointerCount = mPointerTracker.getActivePointerCount();
+ switch (activePointerCount) {
+ case 2: {
+ if (isDraggingGesture(event)) {
+ // If still dragging send a drag event.
+ sendDragEvent(event, MotionEvent.ACTION_MOVE, policyFlags);
+ } else {
+ // The two pointers are moving either in different directions or
+ // no close enough => delegate the gesture to the view hierarchy.
+ mCurrentState = STATE_DELEGATING;
+ // Send an event to the end of the drag gesture.
+ sendDragEvent(event, MotionEvent.ACTION_UP, policyFlags);
+ // Deliver all active pointers to the view hierarchy.
+ sendDownForAllActiveNotInjectedPointers(event, policyFlags);
+ }
+ } break;
+ default: {
+ mCurrentState = STATE_DELEGATING;
+ // Send an event to the end of the drag gesture.
+ sendDragEvent(event, MotionEvent.ACTION_UP, policyFlags);
+ // Deliver all active pointers to the view hierarchy.
+ sendDownForAllActiveNotInjectedPointers(event, policyFlags);
+ }
+ }
+ } break;
+ case MotionEvent.ACTION_POINTER_UP: {
+ mCurrentState = STATE_TOUCH_EXPLORING;
+ // Send an event to the end of the drag gesture.
+ sendDragEvent(event, MotionEvent.ACTION_UP, policyFlags);
+ } break;
+ case MotionEvent.ACTION_CANCEL: {
+ clear();
+ } break;
+ }
+ }
+
+ /**
+ * Handles a motion event in delegating state.
+ *
+ * @param event The event to be handled.
+ * @param policyFlags The policy flags associated with the event.
+ */
+ public void handleMotionEventStateDelegating(MotionEvent event, int policyFlags) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN: {
+ throw new IllegalStateException("Delegating state can only be reached if "
+ + "there is at least one pointer down!");
+ }
+ case MotionEvent.ACTION_UP: {
+ mCurrentState = STATE_TOUCH_EXPLORING;
+ } break;
+ case MotionEvent.ACTION_MOVE: {
+ // Check whether some other pointer became active because they have moved
+ // a given distance and if such exist send them to the view hierarchy
+ final int notInjectedCount = mPointerTracker.getNotInjectedActivePointerCount();
+ if (notInjectedCount > 0) {
+ sendDownForAllActiveNotInjectedPointers(event, policyFlags);
+ }
+ } break;
+ case MotionEvent.ACTION_POINTER_UP: {
+ // No active pointers => go to initial state.
+ if (mPointerTracker.getActivePointerCount() == 0) {
+ mCurrentState = STATE_TOUCH_EXPLORING;
+ }
+ } break;
+ case MotionEvent.ACTION_CANCEL: {
+ clear();
+ } break;
+ }
+ // Deliver the event striping out inactive pointers.
+ sendMotionEventStripInactivePointers(event, policyFlags);
+ }
+
+ /**
+ * Schedules a hover up event so subsequent poking on the same location after
+ * the scheduled delay will perform exploration.
+ *
+ * @param prototype The prototype from which to create the injected events.
+ * @param policyFlags The policy flags associated with the event.
+ */
+ private void scheduleHoverExit(MotionEvent prototype,
+ int policyFlags) {
+ final int pointerId = mPointerTracker.getLastReceivedUpPointerId();
+ final int pointerIndex = prototype.findPointerIndex(pointerId);
+ // We want to no longer hover over the location so subsequent
+ // touch at the same spot will generate a hover enter.
+ mSendHoverDelayed.post(prototype, MotionEvent.ACTION_HOVER_EXIT,
+ pointerIndex, policyFlags, ACTIVATION_TIME_SLOP);
+ }
+
+ /**
+ * Sends down events to the view hierarchy for all active pointers which are
+ * not already being delivered i.e. pointers that are not yet injected.
+ *
+ * @param prototype The prototype from which to create the injected events.
+ * @param policyFlags The policy flags associated with the event.
+ */
+ private void sendDownForAllActiveNotInjectedPointers(MotionEvent prototype, int policyFlags) {
+ PointerCoords[] pointerCoords = mTempPointerCoords;
+ PointerTracker pointerTracker = mPointerTracker;
+ int[] pointerIds = mTempPointerIds;
+ int pointerDataIndex = 0;
+
+ final int pinterCount = prototype.getPointerCount();
+ for (int i = 0; i < pinterCount; i++) {
+ final int pointerId = prototype.getPointerId(i);
+
+ // Skip inactive pointers.
+ if (!pointerTracker.isActivePointer(pointerId)) {
+ continue;
+ }
+ // Skip already delivered pointers.
+ if (pointerTracker.isInjectedPointerDown(pointerId)) {
+ continue;
+ }
+
+ // Populate and inject an event for the current pointer.
+ pointerIds[pointerDataIndex] = pointerId;
+ prototype.getPointerCoords(i, pointerCoords[pointerDataIndex]);
+
+ final long downTime = pointerTracker.getLastInjectedDownEventTime();
+ final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, pointerDataIndex);
+ final int pointerCount = pointerDataIndex + 1;
+ final long pointerDownTime = SystemClock.uptimeMillis();
+
+ MotionEvent event = MotionEvent.obtain(downTime, pointerDownTime,
+ action, pointerCount, pointerIds, pointerCoords, prototype.getMetaState(),
+ prototype.getXPrecision(), prototype.getYPrecision(), prototype.getDeviceId(),
+ prototype.getEdgeFlags(), prototype.getSource(), prototype.getFlags());
+ sendMotionEvent(event, policyFlags);
+ event.recycle();
+
+ pointerDataIndex++;
+ }
+ }
+
+ /**
+ * Sends up events to the view hierarchy for all active pointers which are
+ * already being delivered i.e. pointers that are injected.
+ *
+ * @param prototype The prototype from which to create the injected events.
+ * @param policyFlags The policy flags associated with the event.
+ */
+ private void sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags) {
+ PointerTracker pointerTracker = mPointerTracker;
+ PointerCoords[] pointerCoords = mTempPointerCoords;
+ int[] pointerIds = mTempPointerIds;
+ int pointerDataIndex = 0;
+
+ final int pointerCount = prototype.getPointerCount();
+ for (int i = 0; i < pointerCount; i++) {
+ final int pointerId = prototype.getPointerId(i);
+
+ // Skip non injected down pointers.
+ if (!pointerTracker.isInjectedPointerDown(pointerId)) {
+ continue;
+ }
+
+ // Populate and inject event.
+ pointerIds[pointerDataIndex] = pointerId;
+ prototype.getPointerCoords(i, pointerCoords[pointerDataIndex]);
+
+ final long downTime = pointerTracker.getLastInjectedDownEventTime();
+ final int action = computeInjectionAction(MotionEvent.ACTION_UP, pointerDataIndex);
+ final int newPointerCount = pointerDataIndex + 1;
+ final long eventTime = SystemClock.uptimeMillis();
+
+ MotionEvent event = MotionEvent.obtain(downTime, eventTime, action,
+ newPointerCount, pointerIds, pointerCoords, prototype.getMetaState(),
+ prototype.getXPrecision(), prototype.getYPrecision(), prototype.getDeviceId(),
+ prototype.getEdgeFlags(), prototype.getSource(), prototype.getFlags());
+
+ sendMotionEvent(event, policyFlags);
+ event.recycle();
+
+ pointerDataIndex++;
+ }
+ }
+
+ /**
+ * Sends a motion event by first stripping the inactive pointers.
+ *
+ * @param prototype The prototype from which to create the injected event.
+ * @param policyFlags The policy flags associated with the event.
+ */
+ private void sendMotionEventStripInactivePointers(MotionEvent prototype, int policyFlags) {
+ PointerTracker pointerTracker = mPointerTracker;
+
+ // All pointers active therefore we just inject the event as is.
+ if (prototype.getPointerCount() == pointerTracker.getActivePointerCount()) {
+ sendMotionEvent(prototype, policyFlags);
+ return;
+ }
+
+ // No active pointers and the one that just went up was not
+ // active, therefore we have nothing to do.
+ if (pointerTracker.getActivePointerCount() == 0
+ && !pointerTracker.wasLastReceivedUpPointerActive()) {
+ return;
+ }
+
+ // Filter out inactive pointers from the event and inject it.
+ PointerCoords[] pointerCoords = mTempPointerCoords;
+ int[] pointerIds = mTempPointerIds;
+ int [] newToOldPointerIndexMap = mTempNewToOldPointerIndexMap;
+ int newPointerIndex = 0;
+ int actionIndex = prototype.getActionIndex();
+
+ final int oldPointerCount = prototype.getPointerCount();
+ for (int oldPointerIndex = 0; oldPointerIndex < oldPointerCount; oldPointerIndex++) {
+ final int pointerId = prototype.getPointerId(oldPointerIndex);
+
+ // If the pointer is inactive or the pointer that just went up
+ // was inactive we strip the pointer data from the event.
+ if (!pointerTracker.isActiveOrWasLastActiveUpPointer(pointerId)) {
+ if (oldPointerIndex <= prototype.getActionIndex()) {
+ actionIndex--;
+ }
+ continue;
+ }
+
+ newToOldPointerIndexMap[newPointerIndex] = oldPointerIndex;
+ pointerIds[newPointerIndex] = pointerId;
+ prototype.getPointerCoords(oldPointerIndex, pointerCoords[newPointerIndex]);
+
+ newPointerIndex++;
+ }
+
+ // If we skipped all pointers => nothing to do.
+ if (newPointerIndex == 0) {
+ return;
+ }
+
+ // Populate and inject the event.
+ final long downTime = pointerTracker.getLastInjectedDownEventTime();
+ final int action = computeInjectionAction(prototype.getActionMasked(), actionIndex);
+ final int newPointerCount = newPointerIndex;
+ MotionEvent prunedEvent = MotionEvent.obtain(downTime, prototype.getEventTime(), action,
+ newPointerCount, pointerIds, pointerCoords, prototype.getMetaState(),
+ prototype.getXPrecision(), prototype.getYPrecision(), prototype.getDeviceId(),
+ prototype.getEdgeFlags(), prototype.getSource(),prototype.getFlags());
+
+ // Add the filtered history.
+ final int historySize = prototype.getHistorySize();
+ for (int historyIndex = 0; historyIndex < historySize; historyIndex++) {
+ for (int pointerIndex = 0; pointerIndex < newPointerCount; pointerIndex++) {
+ final int oldPointerIndex = newToOldPointerIndexMap[pointerIndex];
+ prototype.getPointerCoords(oldPointerIndex, pointerCoords[pointerIndex]);
+ }
+ final long historicalTime = prototype.getHistoricalEventTime(historyIndex);
+ prunedEvent.addBatch(historicalTime, pointerCoords, 0);
+ }
+
+ sendMotionEvent(prunedEvent, policyFlags);
+ prunedEvent.recycle();
+ }
+
+ /**
+ * Sends a dragging event from a two pointer event. The two pointers are
+ * merged into one and delivered to the view hierarchy. Through the entire
+ * drag gesture the pointer id delivered to the view hierarchy is the same.
+ *
+ * @param prototype The prototype from which to create the injected event.
+ * @param action The dragging action that is to be injected.
+ * @param policyFlags The policy flags associated with the event.
+ */
+ private void sendDragEvent(MotionEvent prototype, int action, int policyFlags) {
+ PointerCoords[] pointerCoords = mTempPointerCoords;
+ int[] pointerIds = mTempPointerIds;
+ final int pointerId = mDraggingPointerId;
+ final int pointerIndex = prototype.findPointerIndex(pointerId);
+
+ // Populate the event with the date of the dragging pointer and inject it.
+ pointerIds[0] = pointerId;
+ prototype.getPointerCoords(pointerIndex, pointerCoords[0]);
+
+ MotionEvent event = MotionEvent.obtain(prototype.getDownTime(),
+ prototype.getEventTime(), action, 1, pointerIds, pointerCoords,
+ prototype.getMetaState(), prototype.getXPrecision(), prototype.getYPrecision(),
+ prototype.getDeviceId(), prototype.getEdgeFlags(), prototype.getSource(),
+ prototype.getFlags());
+
+ sendMotionEvent(event, policyFlags);
+ event.recycle();
+ }
+
+ /**
+ * Sends an up and down events.
+ *
+ * @param prototype The prototype from which to create the injected events.
+ * @param policyFlags The policy flags associated with the event.
+ */
+ private void sendActionDownAndUp(MotionEvent prototype, int policyFlags) {
+ PointerCoords[] pointerCoords = mTempPointerCoords;
+ int[] pointerIds = mTempPointerIds;
+ final int pointerId = mPointerTracker.getLastReceivedUpPointerId();
+ final int pointerIndex = prototype.findPointerIndex(pointerId);
+
+ // Send down.
+ pointerIds[0] = pointerId;
+ prototype.getPointerCoords(pointerIndex, pointerCoords[0]);
+
+ final long downTime = SystemClock.uptimeMillis();
+
+ MotionEvent downEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN,
+ 1, mTempPointerIds, mTempPointerCoords, prototype.getMetaState(),
+ prototype.getXPrecision(), prototype.getYPrecision(), prototype.getDeviceId(),
+ prototype.getEdgeFlags(), prototype.getSource(), prototype.getFlags());
+
+ // Clone the down event before recycling it.
+ MotionEvent upEvent = MotionEvent.obtain(downEvent);
+
+ sendMotionEvent(downEvent, policyFlags);
+ downEvent.recycle();
+
+ // Send up.
+ upEvent.setAction(MotionEvent.ACTION_UP);
+ sendMotionEvent(upEvent, policyFlags);
+ upEvent.recycle();
+ }
+
+ /**
+ * Sends a hover event.
+ *
+ * @param prototype The prototype from which to create the injected event.
+ * @param action The hover action.
+ * @param pointerIndex The action pointer index.
+ * @param policyFlags The policy flags associated with the event.
+ */
+ private void sendHoverEvent(MotionEvent prototype, int action, int pointerIndex, int
+ policyFlags) {
+ PointerCoords[] pointerCoords = mTempPointerCoords;
+ int[] pointerIds = mTempPointerIds;
+
+ // Keep only data relevant to a hover event.
+ pointerIds[0] = prototype.getPointerId(pointerIndex);
+ pointerCoords[0].clear();
+ pointerCoords[0].x = prototype.getX(pointerIndex);
+ pointerCoords[0].y = prototype.getY(pointerIndex);
+
+ final long downTime = mPointerTracker.getLastInjectedDownEventTime();
+
+ // Populate and inject a hover event.
+ MotionEvent hoverEvent = MotionEvent.obtain(downTime, prototype.getEventTime(), action,
+ 1, pointerIds, pointerCoords, 0, 0, 0, prototype.getDeviceId(), 0,
+ prototype.getSource(), 0);
+
+ sendMotionEvent(hoverEvent, policyFlags);
+ hoverEvent.recycle();
+ }
+
+ /**
+ * Computes the action for an injected event based on a masked action
+ * and a pointer index.
+ *
+ * @param actionMasked The masked action.
+ * @param pointerIndex The index of the pointer which has changed.
+ * @return The action to be used for injection.
+ */
+ private int computeInjectionAction(int actionMasked, int pointerIndex) {
+ switch (actionMasked) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ PointerTracker pointerTracker = mPointerTracker;
+ // Compute the action based on how many down pointers are injected.
+ if (pointerTracker.getInjectedPointerDownCount() == 0) {
+ return MotionEvent.ACTION_DOWN;
+ } else {
+ return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
+ | MotionEvent.ACTION_POINTER_DOWN;
+ }
+ }
+ case MotionEvent.ACTION_POINTER_UP: {
+ PointerTracker pointerTracker = mPointerTracker;
+ // Compute the action based on how many down pointers are injected.
+ if (pointerTracker.getInjectedPointerDownCount() == 1) {
+ return MotionEvent.ACTION_UP;
+ } else {
+ return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
+ | MotionEvent.ACTION_POINTER_UP;
+ }
+ }
+ default:
+ return actionMasked;
+ }
+ }
+
+ /**
+ * Determines whether a two pointer gesture is a dragging one.
+ *
+ * @param event The event with the pointer data.
+ * @return True if the gesture is a dragging one.
+ */
+ private boolean isDraggingGesture(MotionEvent event) {
+ PointerTracker pointerTracker = mPointerTracker;
+ int[] pointerIds = mTempPointerIds;
+ pointerTracker.populateActivePointerIds(pointerIds);
+
+ final int firstPtrIndex = event.findPointerIndex(pointerIds[0]);
+ final int secondPtrIndex = event.findPointerIndex(pointerIds[1]);
+
+ final float firstPtrX = event.getX(firstPtrIndex);
+ final float firstPtrY = event.getY(firstPtrIndex);
+ final float secondPtrX = event.getX(secondPtrIndex);
+ final float secondPtrY = event.getY(secondPtrIndex);
+
+ // Check if the pointers are close enough.
+ final float deltaX = firstPtrX - secondPtrX;
+ final float deltaY = firstPtrY - secondPtrY;
+ final float deltaMove = (float) Math.hypot(deltaX, deltaY);
+ if (deltaMove > mDraggingDistance) {
+ return false;
+ }
+
+ // Check if the pointers are moving in the same direction.
+ final float firstDeltaX =
+ firstPtrX - pointerTracker.getReceivedPointerDownX(firstPtrIndex);
+ final float firstDeltaY =
+ firstPtrY - pointerTracker.getReceivedPointerDownY(firstPtrIndex);
+ final float firstMagnitude =
+ (float) Math.sqrt(firstDeltaX * firstDeltaX + firstDeltaY * firstDeltaY);
+ final float firstXNormalized =
+ (firstMagnitude > 0) ? firstDeltaX / firstMagnitude : firstDeltaX;
+ final float firstYNormalized =
+ (firstMagnitude > 0) ? firstDeltaY / firstMagnitude : firstDeltaY;
+
+ final float secondDeltaX =
+ secondPtrX - pointerTracker.getReceivedPointerDownX(secondPtrIndex);
+ final float secondDeltaY =
+ secondPtrY - pointerTracker.getReceivedPointerDownY(secondPtrIndex);
+ final float secondMagnitude =
+ (float) Math.sqrt(secondDeltaX * secondDeltaX + secondDeltaY * secondDeltaY);
+ final float secondXNormalized =
+ (secondMagnitude > 0) ? secondDeltaX / secondMagnitude : secondDeltaX;
+ final float secondYNormalized =
+ (secondMagnitude > 0) ? secondDeltaY / secondMagnitude : secondDeltaY;
+
+ final float angleCos =
+ firstXNormalized * secondXNormalized + firstYNormalized * secondYNormalized;
+
+ if (angleCos < MIN_ANGLE_COS) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Sends an event announcing the start/end of a touch exploration gesture.
+ *
+ * @param eventType The type of the event to send.
+ */
+ private void sendAccessibilityEvent(int eventType) {
+ AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
+ event.setPackageName(mContext.getPackageName());
+ event.setClassName(getClass().getName());
+ mAccessibilityManager.sendAccessibilityEvent(event);
+ }
+
+ /**
+ * Sends a motion event to the input filter for injection.
+ *
+ * @param event The event to send.
+ * @param policyFlags The policy flags associated with the event.
+ */
+ private void sendMotionEvent(MotionEvent event, int policyFlags) {
+ if (DEBUG) {
+ Slog.d(LOG_TAG_INJECTED, "Injecting event: " + event + ", policyFlags=0x"
+ + Integer.toHexString(policyFlags));
+ }
+ // Make sure that the user will see the event.
+ policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER;
+ mPointerTracker.onInjectedMotionEvent(event);
+ mInputFilter.sendInputEvent(event, policyFlags);
+ }
+
+ /**
+ * Clears the internal state of this explorer.
+ */
+ private void clear() {
+ mSendHoverDelayed.remove();
+ mPointerTracker.clear();
+ mLastTouchExploreEvent = null;
+ mCurrentState = STATE_TOUCH_EXPLORING;
+ mTouchExploreGestureInProgress = false;
+ mDraggingPointerId = INVALID_POINTER_ID;
+ }
+
+ /**
+ * Helper class for tracking pointers and more specifically which of
+ * them are currently down, which are active, and which are delivered
+ * to the view hierarchy. The enclosing {@link TouchExplorer} uses the
+ * pointer state reported by this class to perform touch exploration.
+ * <p>
+ * The main purpose of this class is to allow the touch explorer to
+ * disregard pointers put down by accident by the user and not being
+ * involved in the interaction. For example, a blind user grabs the
+ * device with her left hand such that she touches the screen and she
+ * uses her right hand's index finger to explore the screen content.
+ * In this scenario the touches generated by the left hand are to be
+ * ignored.
+ */
+ class PointerTracker {
+ private static final String LOG_TAG = "PointerTracker";
+
+ // The coefficient by which to multiply
+ // ViewConfiguration.#getScaledTouchSlop()
+ // to compute #mThresholdActivePointer.
+ private static final int COEFFICIENT_ACTIVE_POINTER = 2;
+
+ // Pointers that moved less than mThresholdActivePointer
+ // are considered active i.e. are ignored.
+ private final double mThresholdActivePointer;
+
+ // Keep track of where and when a pointer went down.
+ private final float[] mReceivedPointerDownX = new float[MAX_POINTER_COUNT];
+ private final float[] mReceivedPointerDownY = new float[MAX_POINTER_COUNT];
+ private final long[] mReceivedPointerDownTime = new long[MAX_POINTER_COUNT];
+
+ // Which pointers are down.
+ private int mReceivedPointersDown;
+
+ // Which down pointers are active.
+ private int mActivePointers;
+
+ // Primary active pointer which is either the first that went down
+ // or if it goes up the next active that most recently went down.
+ private int mPrimaryActivePointerId;
+
+ // Flag indicating that there is at least one active pointer moving.
+ private boolean mHasMovingActivePointer;
+
+ // Keep track of which pointers sent to the system are down.
+ private int mInjectedPointersDown;
+
+ // Keep track of the last up pointer data.
+ private float mLastReceivedUpPointerDownX;
+ private float mLastReveivedUpPointerDownY;
+ private long mLastReceivedUpPointerDownTime;
+ private int mLastReceivedUpPointerId;
+ private boolean mLastReceivedUpPointerActive;
+
+ // The time of the last injected down.
+ private long mLastInjectedDownEventTime;
+
+ // The action of the last injected hover event.
+ private int mLastInjectedHoverEventAction = MotionEvent.ACTION_HOVER_EXIT;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param context Context for looking up resources.
+ */
+ public PointerTracker(Context context) {
+ mThresholdActivePointer =
+ ViewConfiguration.get(context).getScaledTouchSlop() * COEFFICIENT_ACTIVE_POINTER;
+ }
+
+ /**
+ * Clears the internals state.
+ */
+ public void clear() {
+ Arrays.fill(mReceivedPointerDownX, 0);
+ Arrays.fill(mReceivedPointerDownY, 0);
+ Arrays.fill(mReceivedPointerDownTime, 0);
+ mReceivedPointersDown = 0;
+ mActivePointers = 0;
+ mPrimaryActivePointerId = 0;
+ mHasMovingActivePointer = false;
+ mInjectedPointersDown = 0;
+ mLastReceivedUpPointerDownX = 0;
+ mLastReveivedUpPointerDownY = 0;
+ mLastReceivedUpPointerDownTime = 0;
+ mLastReceivedUpPointerId = 0;
+ mLastReceivedUpPointerActive = false;
+ }
+
+ /**
+ * Processes a received {@link MotionEvent} event.
+ *
+ * @param event The event to process.
+ */
+ public void onReceivedMotionEvent(MotionEvent event) {
+ final int action = event.getActionMasked();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN: {
+ // New gesture so restart tracking injected down pointers.
+ mInjectedPointersDown = 0;
+ handleReceivedPointerDown(0, event);
+ } break;
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ handleReceivedPointerDown(event.getActionIndex(), event);
+ } break;
+ case MotionEvent.ACTION_MOVE: {
+ handleReceivedPointerMove(event);
+ } break;
+ case MotionEvent.ACTION_UP: {
+ handleReceivedPointerUp(0, event);
+ } break;
+ case MotionEvent.ACTION_POINTER_UP: {
+ handleReceivedPointerUp(event.getActionIndex(), event);
+ } break;
+ }
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Received pointer: " + toString());
+ }
+ }
+
+ /**
+ * Processes an injected {@link MotionEvent} event.
+ *
+ * @param event The event to process.
+ */
+ public void onInjectedMotionEvent(MotionEvent event) {
+ final int action = event.getActionMasked();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN: {
+ handleInjectedPointerDown(0, event);
+ } break;
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ handleInjectedPointerDown(event.getActionIndex(), event);
+ } break;
+ case MotionEvent.ACTION_UP: {
+ handleInjectedPointerUp(0, event);
+ } break;
+ case MotionEvent.ACTION_POINTER_UP: {
+ handleInjectedPointerUp(event.getActionIndex(), event);
+ } break;
+ case MotionEvent.ACTION_HOVER_ENTER:
+ case MotionEvent.ACTION_HOVER_MOVE:
+ case MotionEvent.ACTION_HOVER_EXIT: {
+ mLastInjectedHoverEventAction = event.getActionMasked();
+ } break;
+ }
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Injected pointer: " + toString());
+ }
+ }
+
+ /**
+ * @return The number of received pointers that are down.
+ */
+ public int getReceivedPointerDownCount() {
+ return Integer.bitCount(mReceivedPointersDown);
+ }
+
+ /**
+ * @return The number of down input pointers that are active.
+ */
+ public int getActivePointerCount() {
+ return Integer.bitCount(mActivePointers);
+ }
+
+ /**
+ * Whether an received pointer is down.
+ *
+ * @param pointerId The unique pointer id.
+ * @return True if the pointer is down.
+ */
+ public boolean isReceivedPointerDown(int pointerId) {
+ final int pointerFlag = (1 << pointerId);
+ return (mReceivedPointersDown & pointerFlag) != 0;
+ }
+
+ /**
+ * Whether an injected pointer is down.
+ *
+ * @param pointerId The unique pointer id.
+ * @return True if the pointer is down.
+ */
+ public boolean isInjectedPointerDown(int pointerId) {
+ final int pointerFlag = (1 << pointerId);
+ return (mInjectedPointersDown & pointerFlag) != 0;
+ }
+
+ /**
+ * @return The number of down pointers injected to the view hierarchy.
+ */
+ public int getInjectedPointerDownCount() {
+ return Integer.bitCount(mInjectedPointersDown);
+ }
+
+ /**
+ * Whether an input pointer is active.
+ *
+ * @param pointerId The unique pointer id.
+ * @return True if the pointer is active.
+ */
+ public boolean isActivePointer(int pointerId) {
+ final int pointerFlag = (1 << pointerId);
+ return (mActivePointers & pointerFlag) != 0;
+ }
+
+ /**
+ * @param pointerId The unique pointer id.
+ * @return The X coordinate where the pointer went down.
+ */
+ public float getReceivedPointerDownX(int pointerId) {
+ return mReceivedPointerDownX[pointerId];
+ }
+
+ /**
+ * @param pointerId The unique pointer id.
+ * @return The Y coordinate where the pointer went down.
+ */
+ public float getReceivedPointerDownY(int pointerId) {
+ return mReceivedPointerDownY[pointerId];
+ }
+
+ /**
+ * @param pointerId The unique pointer id.
+ * @return The time when the pointer went down.
+ */
+ public long getReceivedPointerDownTime(int pointerId) {
+ return mReceivedPointerDownTime[pointerId];
+ }
+
+ /**
+ * @return The id of the primary pointer.
+ */
+ public int getPrimaryActivePointerId() {
+ if (mPrimaryActivePointerId == INVALID_POINTER_ID) {
+ mPrimaryActivePointerId = findPrimaryActivePointer();
+ }
+ return mPrimaryActivePointerId;
+ }
+
+ /**
+ * @return The X coordinate where the last up received pointer went down.
+ */
+ public float getLastReceivedUpPointerDownX() {
+ return mLastReceivedUpPointerDownX;
+ }
+
+ /**
+ * @return The Y coordinate where the last up received pointer went down.
+ */
+ public float getLastReceivedUpPointerDownY() {
+ return mLastReveivedUpPointerDownY;
+ }
+
+ /**
+ * @return The time when the last up received pointer went down.
+ */
+ public long getLastReceivedUpPointerDownTime() {
+ return mLastReceivedUpPointerDownTime;
+ }
+
+ /**
+ * @return The id of the last received pointer that went up.
+ */
+ public int getLastReceivedUpPointerId() {
+ return mLastReceivedUpPointerId;
+ }
+
+ /**
+ * @return Whether the last received pointer that went up was active.
+ */
+ public boolean wasLastReceivedUpPointerActive() {
+ return mLastReceivedUpPointerActive;
+ }
+
+ /**
+ * @return The time of the last injected down event.
+ */
+ public long getLastInjectedDownEventTime() {
+ return mLastInjectedDownEventTime;
+ }
+
+ /**
+ * @return The action of the last injected hover event.
+ */
+ public int getLastInjectedHoverAction() {
+ return mLastInjectedHoverEventAction;
+ }
+
+ /**
+ * Populates the active pointer IDs to the given array.
+ * <p>
+ * Note: The client is responsible for providing large enough array.
+ *
+ * @param outPointerIds The array to which to write the active pointers.
+ */
+ public void populateActivePointerIds(int[] outPointerIds) {
+ int index = 0;
+ for (int idBits = mActivePointers; idBits != 0; ) {
+ final int id = Integer.numberOfTrailingZeros(idBits);
+ idBits &= ~(1 << id);
+ outPointerIds[index] = id;
+ index++;
+ }
+ }
+
+ /**
+ * @return The number of non injected active pointers.
+ */
+ public int getNotInjectedActivePointerCount() {
+ final int pointerState = mActivePointers & ~mInjectedPointersDown;
+ return Integer.bitCount(pointerState);
+ }
+
+ /**
+ * @param pointerId The unique pointer id.
+ * @return Whether the pointer is active or was the last active than went up.
+ */
+ private boolean isActiveOrWasLastActiveUpPointer(int pointerId) {
+ return (isActivePointer(pointerId)
+ || (mLastReceivedUpPointerId == pointerId
+ && mLastReceivedUpPointerActive));
+ }
+
+ /**
+ * Handles a received pointer down event.
+ *
+ * @param pointerIndex The index of the pointer that has changed.
+ * @param event The event to be handled.
+ */
+ private void handleReceivedPointerDown(int pointerIndex, MotionEvent event) {
+ final int pointerId = event.getPointerId(pointerIndex);
+ final int pointerFlag = (1 << pointerId);
+
+ mLastReceivedUpPointerId = 0;
+ mLastReceivedUpPointerDownX = 0;
+ mLastReveivedUpPointerDownY = 0;
+ mLastReceivedUpPointerDownTime = 0;
+ mLastReceivedUpPointerActive = false;
+
+ mReceivedPointersDown |= pointerFlag;
+ mReceivedPointerDownX[pointerId] = event.getX(pointerIndex);
+ mReceivedPointerDownY[pointerId] = event.getY(pointerIndex);
+ mReceivedPointerDownTime[pointerId] = event.getEventTime();
+
+ if (!mHasMovingActivePointer) {
+ // If still no moving active pointers every
+ // down pointer is the only active one.
+ mActivePointers = pointerFlag;
+ mPrimaryActivePointerId = pointerId;
+ } else {
+ // If at least one moving active pointer every
+ // subsequent down pointer is active.
+ mActivePointers |= pointerFlag;
+ }
+ }
+
+ /**
+ * Handles a received pointer move event.
+ *
+ * @param event The event to be handled.
+ */
+ private void handleReceivedPointerMove(MotionEvent event) {
+ detectActivePointers(event);
+ }
+
+ /**
+ * Handles a received pointer up event.
+ *
+ * @param pointerIndex The index of the pointer that has changed.
+ * @param event The event to be handled.
+ */
+ private void handleReceivedPointerUp(int pointerIndex, MotionEvent event) {
+ final int pointerId = event.getPointerId(pointerIndex);
+ final int pointerFlag = (1 << pointerId);
+
+ mLastReceivedUpPointerId = pointerId;
+ mLastReceivedUpPointerDownX = getReceivedPointerDownX(pointerId);
+ mLastReveivedUpPointerDownY = getReceivedPointerDownY(pointerId);
+ mLastReceivedUpPointerDownTime = getReceivedPointerDownTime(pointerId);
+ mLastReceivedUpPointerActive = isActivePointer(pointerId);
+
+ mReceivedPointersDown &= ~pointerFlag;
+ mActivePointers &= ~pointerFlag;
+ mReceivedPointerDownX[pointerId] = 0;
+ mReceivedPointerDownY[pointerId] = 0;
+ mReceivedPointerDownTime[pointerId] = 0;
+
+ if (mActivePointers == 0) {
+ mHasMovingActivePointer = false;
+ }
+ if (mPrimaryActivePointerId == pointerId) {
+ mPrimaryActivePointerId = INVALID_POINTER_ID;
+ }
+ }
+
+ /**
+ * Handles a injected pointer down event.
+ *
+ * @param pointerIndex The index of the pointer that has changed.
+ * @param event The event to be handled.
+ */
+ private void handleInjectedPointerDown(int pointerIndex, MotionEvent event) {
+ final int pointerId = event.getPointerId(pointerIndex);
+ final int pointerFlag = (1 << pointerId);
+ mInjectedPointersDown |= pointerFlag;
+ mLastInjectedDownEventTime = event.getEventTime();
+ }
+
+ /**
+ * Handles a injected pointer up event.
+ *
+ * @param pointerIndex The index of the pointer that has changed.
+ * @param event The event to be handled.
+ */
+ private void handleInjectedPointerUp(int pointerIndex, MotionEvent event) {
+ final int pointerId = event.getPointerId(pointerIndex);
+ final int pointerFlag = (1 << pointerId);
+ mInjectedPointersDown &= ~pointerFlag;
+ }
+
+ /**
+ * Detects the active pointers in an event.
+ *
+ * @param event The event to examine.
+ */
+ private void detectActivePointers(MotionEvent event) {
+ for (int i = 0, count = event.getPointerCount(); i < count; i++) {
+ final int pointerId = event.getPointerId(i);
+ if (mHasMovingActivePointer) {
+ // If already active => nothing to do.
+ if (isActivePointer(pointerId)) {
+ continue;
+ }
+ }
+ // Active pointers are ones that moved more than a given threshold.
+ final float pointerDeltaMove = computePointerDeltaMove(i, event);
+ if (pointerDeltaMove > mThresholdActivePointer) {
+ final int pointerFlag = (1 << pointerId);
+ mActivePointers |= pointerFlag;
+ mHasMovingActivePointer = true;
+ }
+ }
+ }
+
+ /**
+ * @return The primary active pointer.
+ */
+ private int findPrimaryActivePointer() {
+ int primaryActivePointerId = INVALID_POINTER_ID;
+ long minDownTime = Long.MAX_VALUE;
+ // Find the active pointer that went down first.
+ for (int i = 0, count = mReceivedPointerDownTime.length; i < count; i++) {
+ if (isActivePointer(i)) {
+ final long downPointerTime = mReceivedPointerDownTime[i];
+ if (downPointerTime < minDownTime) {
+ minDownTime = downPointerTime;
+ primaryActivePointerId = i;
+ }
+ }
+ }
+ return primaryActivePointerId;
+ }
+
+ /**
+ * Computes the move for a given action pointer index since the
+ * corresponding pointer went down.
+ *
+ * @param pointerIndex The action pointer index.
+ * @param event The event to examine.
+ * @return The distance the pointer has moved.
+ */
+ private float computePointerDeltaMove(int pointerIndex, MotionEvent event) {
+ final int pointerId = event.getPointerId(pointerIndex);
+ final float deltaX = event.getX(pointerIndex) - mReceivedPointerDownX[pointerId];
+ final float deltaY = event.getY(pointerIndex) - mReceivedPointerDownY[pointerId];
+ return (float) Math.hypot(deltaX, deltaY);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("=========================");
+ builder.append("\nDown pointers #");
+ builder.append(getReceivedPointerDownCount());
+ builder.append(" [ ");
+ for (int i = 0; i < MAX_POINTER_COUNT; i++) {
+ if (isReceivedPointerDown(i)) {
+ builder.append(i);
+ builder.append(" ");
+ }
+ }
+ builder.append("]");
+ builder.append("\nActive pointers #");
+ builder.append(getActivePointerCount());
+ builder.append(" [ ");
+ for (int i = 0; i < MAX_POINTER_COUNT; i++) {
+ if (isActivePointer(i)) {
+ builder.append(i);
+ builder.append(" ");
+ }
+ }
+ builder.append("]");
+ builder.append("\nPrimary active pointer id [ ");
+ builder.append(getPrimaryActivePointerId());
+ builder.append(" ]");
+ builder.append("\n=========================");
+ return builder.toString();
+ }
+ }
+
+ /**
+ * Class for delayed sending of hover events.
+ */
+ private final class SendHoverDelayed implements Runnable {
+ private static final String LOG_TAG = "SendHoverEnterOrExitDelayed";
+
+ private MotionEvent mEvent;
+ private int mAction;
+ private int mPointerIndex;
+ private int mPolicyFlags;
+
+ public void post(MotionEvent prototype, int action, int pointerIndex, int policyFlags,
+ long delay) {
+ remove();
+ mEvent = MotionEvent.obtain(prototype);
+ mAction = action;
+ mPointerIndex = pointerIndex;
+ mPolicyFlags = policyFlags;
+ mHandler.postDelayed(this, delay);
+ }
+
+ public void remove() {
+ mHandler.removeCallbacks(this);
+ clear();
+ }
+
+ private boolean isPenidng() {
+ return (mEvent != null);
+ }
+
+ private void clear() {
+ if (!isPenidng()) {
+ return;
+ }
+ mEvent.recycle();
+ mEvent = null;
+ mAction = 0;
+ mPointerIndex = -1;
+ mPolicyFlags = 0;
+ }
+
+ public void forceSendAndRemove() {
+ if (isPenidng()) {
+ run();
+ remove();
+ }
+ }
+
+ public void run() {
+ if (DEBUG) {
+ if (mAction == MotionEvent.ACTION_HOVER_ENTER) {
+ Slog.d(LOG_TAG, "Injecting: " + MotionEvent.ACTION_HOVER_ENTER);
+ } else if (mAction == MotionEvent.ACTION_HOVER_MOVE) {
+ Slog.d(LOG_TAG, "Injecting: MotionEvent.ACTION_HOVER_MOVE");
+ } else if (mAction == MotionEvent.ACTION_HOVER_EXIT) {
+ Slog.d(LOG_TAG, "Injecting: MotionEvent.ACTION_HOVER_EXIT");
+ }
+ }
+
+ sendHoverEvent(mEvent, mAction, mPointerIndex, mPolicyFlags);
+ clear();
+ }
+ }
+}
diff --git a/services/java/com/android/server/wm/InputFilter.java b/services/java/com/android/server/wm/InputFilter.java
index 7e1ab07..8f0001a 100644
--- a/services/java/com/android/server/wm/InputFilter.java
+++ b/services/java/com/android/server/wm/InputFilter.java
@@ -105,11 +105,13 @@ public abstract class InputFilter {
private final InputEventConsistencyVerifier mInboundInputEventConsistencyVerifier =
InputEventConsistencyVerifier.isInstrumentationEnabled() ?
new InputEventConsistencyVerifier(this,
- InputEventConsistencyVerifier.FLAG_RAW_DEVICE_INPUT) : null;
+ InputEventConsistencyVerifier.FLAG_RAW_DEVICE_INPUT,
+ "InputFilter#InboundInputEventConsistencyVerifier") : null;
private final InputEventConsistencyVerifier mOutboundInputEventConsistencyVerifier =
InputEventConsistencyVerifier.isInstrumentationEnabled() ?
new InputEventConsistencyVerifier(this,
- InputEventConsistencyVerifier.FLAG_RAW_DEVICE_INPUT) : null;
+ InputEventConsistencyVerifier.FLAG_RAW_DEVICE_INPUT,
+ "InputFilter#OutboundInputEventConsistencyVerifier") : null;
/**
* Creates the input filter.