summaryrefslogtreecommitdiffstats
path: root/services/java/com/android/server
diff options
context:
space:
mode:
Diffstat (limited to 'services/java/com/android/server')
-rw-r--r--services/java/com/android/server/accessibility/AccessibilityInputFilter.java9
-rw-r--r--services/java/com/android/server/accessibility/AccessibilityManagerService.java464
-rw-r--r--services/java/com/android/server/accessibility/TouchExplorer.java911
-rwxr-xr-xservices/java/com/android/server/wm/WindowManagerService.java10
4 files changed, 947 insertions, 447 deletions
diff --git a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 3fbac38..3e35b20 100644
--- a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -16,7 +16,6 @@
package com.android.server.accessibility;
-import com.android.server.accessibility.TouchExplorer.GestureListener;
import com.android.server.input.InputFilter;
import android.content.Context;
@@ -40,7 +39,7 @@ public class AccessibilityInputFilter extends InputFilter {
private final PowerManager mPm;
- private final GestureListener mGestureListener;
+ private final AccessibilityManagerService mAms;
/**
* This is an interface for explorers that take a {@link MotionEvent}
@@ -73,10 +72,10 @@ public class AccessibilityInputFilter extends InputFilter {
private int mTouchscreenSourceDeviceId;
- public AccessibilityInputFilter(Context context, GestureListener gestureListener) {
+ public AccessibilityInputFilter(Context context, AccessibilityManagerService service) {
super(context.getMainLooper());
mContext = context;
- mGestureListener = gestureListener;
+ mAms = service;
mPm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
}
@@ -85,7 +84,7 @@ public class AccessibilityInputFilter extends InputFilter {
if (DEBUG) {
Slog.d(TAG, "Accessibility input filter installed.");
}
- mTouchExplorer = new TouchExplorer(this, mContext, mGestureListener);
+ mTouchExplorer = new TouchExplorer(this, mContext, mAms);
super.onInstalled();
}
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index f23b25e..ebc2074 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -24,12 +24,15 @@ import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.app.AlertDialog;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
@@ -58,7 +61,9 @@ import android.view.IWindow;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
+import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.IAccessibilityInteractionConnection;
@@ -66,8 +71,8 @@ import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
import android.view.accessibility.IAccessibilityManager;
import android.view.accessibility.IAccessibilityManagerClient;
+import com.android.internal.R;
import com.android.internal.content.PackageMonitor;
-import com.android.server.accessibility.TouchExplorer.GestureListener;
import com.android.server.wm.WindowManagerService;
import org.xmlpull.v1.XmlPullParserException;
@@ -90,8 +95,7 @@ import java.util.Set;
*
* @hide
*/
-public class AccessibilityManagerService extends IAccessibilityManager.Stub
- implements GestureListener {
+public class AccessibilityManagerService extends IAccessibilityManager.Stub {
private static final boolean DEBUG = false;
@@ -102,6 +106,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private static final int OWN_PROCESS_ID = android.os.Process.myPid();
+ private static final int MSG_SHOW_ENABLE_TOUCH_EXPLORATION_DIALOG = 1;
+
private static int sIdCounter = 0;
private static int sNextWindowId;
@@ -128,6 +134,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private final SimpleStringSplitter mStringColonSplitter = new SimpleStringSplitter(':');
+ private final Rect mTempRect = new Rect();
+
private PackageManager mPackageManager;
private int mHandledFeedbackTypes = 0;
@@ -146,23 +154,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private final SecurityPolicy mSecurityPolicy;
+ private final MainHanler mMainHandler;
+
private Service mUiAutomationService;
- /**
- * Handler for delayed event dispatch.
- */
- private Handler mHandler = new Handler() {
+ private Service mQueryBridge;
- @Override
- public void handleMessage(Message message) {
- Service service = (Service) message.obj;
- int eventType = message.arg1;
+ private boolean mTouchExplorationGestureEnded;
- synchronized (mLock) {
- notifyAccessibilityEventLocked(service, eventType);
- }
- }
- };
+ private boolean mTouchExplorationGestureStarted;
/**
* Creates a new instance.
@@ -175,7 +175,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mWindowManagerService = (WindowManagerService) ServiceManager.getService(
Context.WINDOW_SERVICE);
mSecurityPolicy = new SecurityPolicy();
-
+ mMainHandler = new MainHanler();
registerPackageChangeAndBootCompletedBroadcastReceiver();
registerSettingsContentObservers();
}
@@ -349,15 +349,37 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
public boolean sendAccessibilityEvent(AccessibilityEvent event) {
+ // The event for gesture start should be strictly before the
+ // first hover enter event for the gesture.
+ if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_HOVER_ENTER
+ && mTouchExplorationGestureStarted) {
+ mTouchExplorationGestureStarted = false;
+ AccessibilityEvent gestureStartEvent = AccessibilityEvent.obtain(
+ AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START);
+ sendAccessibilityEvent(gestureStartEvent);
+ }
+
synchronized (mLock) {
if (mSecurityPolicy.canDispatchAccessibilityEvent(event)) {
- mSecurityPolicy.updateRetrievalAllowingWindowAndEventSourceLocked(event);
+ mSecurityPolicy.updateActiveWindowAndEventSourceLocked(event);
notifyAccessibilityServicesDelayedLocked(event, false);
notifyAccessibilityServicesDelayedLocked(event, true);
}
}
+
+ // The event for gesture end should be strictly after the
+ // last hover exit event for the gesture.
+ if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT
+ && mTouchExplorationGestureEnded) {
+ mTouchExplorationGestureEnded = false;
+ AccessibilityEvent gestureEndEvent = AccessibilityEvent.obtain(
+ AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END);
+ sendAccessibilityEvent(gestureEndEvent);
+ }
+
event.recycle();
mHandledFeedbackTypes = 0;
+
return (OWN_PROCESS_ID != Binder.getCallingPid());
}
@@ -472,8 +494,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
- @Override
- public boolean onGesture(int gestureId) {
+ boolean onGesture(int gestureId) {
synchronized (mLock) {
boolean handled = notifyGestureLocked(gestureId, false);
if (!handled) {
@@ -483,6 +504,65 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
+ /**
+ * Gets the bounds of the accessibility focus if the provided,
+ * point coordinates are within the currently active window
+ * and accessibility focus is found within the latter.
+ *
+ * @param x X coordinate.
+ * @param y Y coordinate
+ * @param outBounds The output to which to write the focus bounds.
+ * @return The accessibility focus rectangle if the point is in the
+ * window and the window has accessibility focus.
+ */
+ boolean getAccessibilityFocusBounds(int x, int y, Rect outBounds) {
+ // Instead of keeping track of accessibility focus events per
+ // window to be able to find the focus in the active window,
+ // we take a stateless approach and look it up. This is fine
+ // since we do this only when the user clicks/long presses.
+ Service service = getQueryBridge();
+ final int connectionId = service.mId;
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ client.addConnection(connectionId, service);
+ try {
+ AccessibilityNodeInfo root = AccessibilityInteractionClient.getInstance()
+ .getRootInActiveWindow(connectionId);
+ if (root == null) {
+ return false;
+ }
+ Rect bounds = mTempRect;
+ root.getBoundsInScreen(bounds);
+ if (!bounds.contains(x, y)) {
+ return false;
+ }
+ AccessibilityNodeInfo focus = root.findFocus(
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY);
+ if (focus == null) {
+ return false;
+ }
+ focus.getBoundsInScreen(outBounds);
+ return true;
+ } finally {
+ client.removeConnection(connectionId);
+ }
+ }
+
+ private Service getQueryBridge() {
+ if (mQueryBridge == null) {
+ AccessibilityServiceInfo info = new AccessibilityServiceInfo();
+ mQueryBridge = new Service(null, info, true);
+ }
+ return mQueryBridge;
+ }
+
+ public void touchExplorationGestureEnded() {
+ mTouchExplorationGestureEnded = true;
+ }
+
+ public void touchExplorationGestureStarted() {
+ mTouchExplorationGestureStarted = true;
+ }
+
private boolean notifyGestureLocked(int gestureId, boolean isDefault) {
// TODO: Now we are giving the gestures to the last enabled
// service that can handle them which is the last one
@@ -496,12 +576,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
for (int i = mServices.size() - 1; i >= 0; i--) {
Service service = mServices.get(i);
if (service.mReqeustTouchExplorationMode && service.mIsDefault == isDefault) {
- try {
- service.mServiceInterface.onGesture(gestureId);
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error during sending gesture " + gestureId
- + " to " + service.mService, re);
- }
+ service.notifyGesture(gestureId);
return true;
}
}
@@ -573,7 +648,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (service.mIsDefault == isDefault) {
if (canDispathEventLocked(service, event, mHandledFeedbackTypes)) {
mHandledFeedbackTypes |= service.mFeedbackType;
- notifyAccessibilityServiceDelayedLocked(service, event);
+ service.notifyAccessibilityEvent(event);
}
}
}
@@ -586,90 +661,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
/**
- * Performs an {@link AccessibilityService} delayed notification. The delay is configurable
- * and denotes the period after the last event before notifying the service.
- *
- * @param service The service.
- * @param event The event.
- */
- private void notifyAccessibilityServiceDelayedLocked(Service service,
- AccessibilityEvent event) {
- synchronized (mLock) {
- final int eventType = event.getEventType();
- // Make a copy since during dispatch it is possible the event to
- // be modified to remove its source if the receiving service does
- // not have permission to access the window content.
- AccessibilityEvent newEvent = AccessibilityEvent.obtain(event);
- AccessibilityEvent oldEvent = service.mPendingEvents.get(eventType);
- service.mPendingEvents.put(eventType, newEvent);
-
- final int what = eventType | (service.mId << 16);
- if (oldEvent != null) {
- mHandler.removeMessages(what);
- oldEvent.recycle();
- }
-
- Message message = mHandler.obtainMessage(what, service);
- message.arg1 = eventType;
- mHandler.sendMessageDelayed(message, service.mNotificationTimeout);
- }
- }
-
- /**
- * Notifies an accessibility service client for a scheduled event given the event type.
- *
- * @param service The service client.
- * @param eventType The type of the event to dispatch.
- */
- private void notifyAccessibilityEventLocked(Service service, int eventType) {
- IAccessibilityServiceClient listener = service.mServiceInterface;
-
- // If the service died/was disabled while the message for dispatching
- // the accessibility event was propagating the listener may be null.
- if (listener == null) {
- return;
- }
-
- AccessibilityEvent event = service.mPendingEvents.get(eventType);
-
- // Check for null here because there is a concurrent scenario in which this
- // happens: 1) A binder thread calls notifyAccessibilityServiceDelayedLocked
- // which posts a message for dispatching an event. 2) The message is pulled
- // from the queue by the handler on the service thread and the latter is
- // just about to acquire the lock and call this method. 3) Now another binder
- // thread acquires the lock calling notifyAccessibilityServiceDelayedLocked
- // so the service thread waits for the lock; 4) The binder thread replaces
- // the event with a more recent one (assume the same event type) and posts a
- // dispatch request releasing the lock. 5) Now the main thread is unblocked and
- // dispatches the event which is removed from the pending ones. 6) And ... now
- // the service thread handles the last message posted by the last binder call
- // but the event is already dispatched and hence looking it up in the pending
- // ones yields null. This check is much simpler that keeping count for each
- // event type of each service to catch such a scenario since only one message
- // is processed at a time.
- if (event == null) {
- return;
- }
-
- service.mPendingEvents.remove(eventType);
- try {
- if (mSecurityPolicy.canRetrieveWindowContent(service)) {
- event.setConnectionId(service.mId);
- } else {
- event.setSource(null);
- }
- event.setSealed(true);
- listener.onAccessibilityEvent(event);
- event.recycle();
- if (DEBUG) {
- Slog.i(LOG_TAG, "Event " + event + " sent to " + listener);
- }
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error during sending " + event + " to " + service.mService, re);
- }
- }
-
- /**
* Adds a service.
*
* @param service The service to add.
@@ -683,6 +674,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mServices.add(service);
mComponentNameToServiceMap.put(service.mComponentName, service);
updateInputFilterLocked();
+ tryEnableTouchExploration(service);
} catch (RemoteException e) {
/* do nothing */
}
@@ -700,10 +692,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return false;
}
mComponentNameToServiceMap.remove(service.mComponentName);
- mHandler.removeMessages(service.mId);
service.unlinkToOwnDeath();
service.dispose();
updateInputFilterLocked();
+ tryDisableTouchExploration(service);
return removed;
}
@@ -932,6 +924,29 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0) == 1;
}
+ private void tryEnableTouchExploration(final Service service) {
+ if (!mIsTouchExplorationEnabled && service.mRequestTouchExplorationMode) {
+ mMainHandler.obtainMessage(MSG_SHOW_ENABLE_TOUCH_EXPLORATION_DIALOG,
+ service).sendToTarget();
+ }
+ }
+
+ private void tryDisableTouchExploration(Service service) {
+ if (mIsTouchExplorationEnabled && service.mReqeustTouchExplorationMode) {
+ synchronized (mLock) {
+ final int serviceCount = mServices.size();
+ for (int i = 0; i < serviceCount; i++) {
+ Service other = mServices.get(i);
+ if (other != service && other.mRequestTouchExplorationMode) {
+ return;
+ }
+ }
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0);
+ }
+ }
+ }
+
private class AccessibilityConnectionWrapper implements DeathRecipient {
private final int mWindowId;
private final IAccessibilityInteractionConnection mConnection;
@@ -959,6 +974,42 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
+ private class MainHanler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ final int type = msg.what;
+ switch (type) {
+ case MSG_SHOW_ENABLE_TOUCH_EXPLORATION_DIALOG: {
+ Service service = (Service) msg.obj;
+ String label = service.mResolveInfo.loadLabel(
+ mContext.getPackageManager()).toString();
+ final AlertDialog dialog = new AlertDialog.Builder(mContext)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setPositiveButton(android.R.string.ok, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.TOUCH_EXPLORATION_ENABLED, 1);
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ })
+ .setTitle(R.string.enable_explore_by_touch_warning_title)
+ .setMessage(mContext.getString(
+ R.string.enable_explore_by_touch_warning_message, label))
+ .create();
+ dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
+ dialog.setCanceledOnTouchOutside(true);
+ dialog.show();
+ }
+ }
+ }
+ }
+
/**
* This class represents an accessibility service. It stores all per service
* data required for the service management, provides API for starting/stopping the
@@ -969,6 +1020,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
*/
class Service extends IAccessibilityServiceConnection.Stub
implements ServiceConnection, DeathRecipient {
+
+ // We pick the MSB to avoid collision since accessibility event types are
+ // used as message types allowing us to remove messages per event type.
+ private static final int MSG_ON_GESTURE = 0x80000000;
+
int mId = 0;
AccessibilityServiceInfo mAccessibilityServiceInfo;
@@ -985,6 +1041,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
boolean mIsDefault;
+ boolean mRequestTouchExplorationMode;
+
boolean mIncludeNotImportantViews;
long mNotificationTimeout;
@@ -1001,12 +1059,35 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
final Rect mTempBounds = new Rect();
+ final ResolveInfo mResolveInfo;
+
// the events pending events to be dispatched to this service
final SparseArray<AccessibilityEvent> mPendingEvents =
new SparseArray<AccessibilityEvent>();
+ /**
+ * Handler for delayed event dispatch.
+ */
+ public Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ final int type = message.what;
+ switch (type) {
+ case MSG_ON_GESTURE: {
+ final int gestureId = message.arg1;
+ notifyGestureInternal(gestureId);
+ } break;
+ default: {
+ final int eventType = type;
+ notifyAccessibilityEventInternal(eventType);
+ } break;
+ }
+ }
+ };
+
public Service(ComponentName componentName,
AccessibilityServiceInfo accessibilityServiceInfo, boolean isAutomation) {
+ mResolveInfo = accessibilityServiceInfo.getResolveInfo();
mId = sIdCounter++;
mComponentName = componentName;
mAccessibilityServiceInfo = accessibilityServiceInfo;
@@ -1043,6 +1124,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
(info.flags & FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
}
+ mRequestTouchExplorationMode = (info.flags
+ & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
+
synchronized (mLock) {
tryAddServiceLocked(this);
}
@@ -1403,6 +1487,108 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
+ /**
+ * Performs a notification for an {@link AccessibilityEvent}.
+ *
+ * @param event The event.
+ */
+ public void notifyAccessibilityEvent(AccessibilityEvent event) {
+ synchronized (mLock) {
+ final int eventType = event.getEventType();
+ // Make a copy since during dispatch it is possible the event to
+ // be modified to remove its source if the receiving service does
+ // not have permission to access the window content.
+ AccessibilityEvent newEvent = AccessibilityEvent.obtain(event);
+ AccessibilityEvent oldEvent = mPendingEvents.get(eventType);
+ mPendingEvents.put(eventType, newEvent);
+
+ final int what = eventType;
+ if (oldEvent != null) {
+ mHandler.removeMessages(what);
+ oldEvent.recycle();
+ }
+
+ Message message = mHandler.obtainMessage(what);
+ mHandler.sendMessageDelayed(message, mNotificationTimeout);
+ }
+ }
+
+ /**
+ * Notifies an accessibility service client for a scheduled event given the event type.
+ *
+ * @param eventType The type of the event to dispatch.
+ */
+ private void notifyAccessibilityEventInternal(int eventType) {
+ IAccessibilityServiceClient listener;
+ AccessibilityEvent event;
+
+ synchronized (mLock) {
+ listener = mServiceInterface;
+
+ // If the service died/was disabled while the message for dispatching
+ // the accessibility event was propagating the listener may be null.
+ if (listener == null) {
+ return;
+ }
+
+ event = mPendingEvents.get(eventType);
+
+ // Check for null here because there is a concurrent scenario in which this
+ // happens: 1) A binder thread calls notifyAccessibilityServiceDelayedLocked
+ // which posts a message for dispatching an event. 2) The message is pulled
+ // from the queue by the handler on the service thread and the latter is
+ // just about to acquire the lock and call this method. 3) Now another binder
+ // thread acquires the lock calling notifyAccessibilityServiceDelayedLocked
+ // so the service thread waits for the lock; 4) The binder thread replaces
+ // the event with a more recent one (assume the same event type) and posts a
+ // dispatch request releasing the lock. 5) Now the main thread is unblocked and
+ // dispatches the event which is removed from the pending ones. 6) And ... now
+ // the service thread handles the last message posted by the last binder call
+ // but the event is already dispatched and hence looking it up in the pending
+ // ones yields null. This check is much simpler that keeping count for each
+ // event type of each service to catch such a scenario since only one message
+ // is processed at a time.
+ if (event == null) {
+ return;
+ }
+
+ mPendingEvents.remove(eventType);
+ if (mSecurityPolicy.canRetrieveWindowContent(this)) {
+ event.setConnectionId(mId);
+ } else {
+ event.setSource(null);
+ }
+ event.setSealed(true);
+ }
+
+ try {
+ listener.onAccessibilityEvent(event);
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Event " + event + " sent to " + listener);
+ }
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error during sending " + event + " to " + listener, re);
+ } finally {
+ event.recycle();
+ }
+ }
+
+ public void notifyGesture(int gestureId) {
+ mHandler.obtainMessage(MSG_ON_GESTURE, gestureId, 0).sendToTarget();
+ }
+
+ private void notifyGestureInternal(int gestureId) {
+ IAccessibilityServiceClient listener = mServiceInterface;
+ if (listener != null) {
+ try {
+ listener.onGesture(gestureId);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error during sending gesture " + gestureId
+ + " to " + mService, re);
+ }
+ }
+ }
+
private void sendDownAndUpKeyEvents(int keyCode) {
final long token = Binder.clearCallingIdentity();
@@ -1454,7 +1640,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private int resolveAccessibilityWindowId(int accessibilityWindowId) {
if (accessibilityWindowId == AccessibilityNodeInfo.ACTIVE_WINDOW_ID) {
- return mSecurityPolicy.mRetrievalAlowingWindowId;
+ return mSecurityPolicy.mActiveWindowId;
}
return accessibilityWindowId;
}
@@ -1497,24 +1683,35 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
| AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
| AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED;
- private static final int RETRIEVAL_ALLOWING_WINDOW_CHANGE_EVENT_TYPES =
- AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
- | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER
- | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT;
-
- private int mRetrievalAlowingWindowId;
+ private int mActiveWindowId;
private boolean canDispatchAccessibilityEvent(AccessibilityEvent event) {
// Send window changed event only for the retrieval allowing window.
return (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
- || event.getWindowId() == mRetrievalAlowingWindowId);
+ || event.getWindowId() == mActiveWindowId);
}
- public void updateRetrievalAllowingWindowAndEventSourceLocked(AccessibilityEvent event) {
+ public void updateActiveWindowAndEventSourceLocked(AccessibilityEvent event) {
+ // The active window is either the window that has input focus or
+ // the window that the user is currently touching. If the user is
+ // touching a window that does not have input focus as soon as the
+ // the user stops touching that window the focused window becomes
+ // the active one.
final int windowId = event.getWindowId();
final int eventType = event.getEventType();
- if ((eventType & RETRIEVAL_ALLOWING_WINDOW_CHANGE_EVENT_TYPES) != 0) {
- mRetrievalAlowingWindowId = windowId;
+ switch (eventType) {
+ case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: {
+ if (getFocusedWindowId() == windowId) {
+ mActiveWindowId = windowId;
+ }
+ } break;
+ case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER:
+ case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: {
+ mActiveWindowId = windowId;
+ } break;
+ case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END: {
+ mActiveWindowId = getFocusedWindowId();
+ } break;
}
if ((eventType & RETRIEVAL_ALLOWING_EVENT_TYPES) == 0) {
event.setSource(null);
@@ -1522,7 +1719,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
public int getRetrievalAllowingWindowLocked() {
- return mRetrievalAlowingWindowId;
+ return mActiveWindowId;
}
public boolean canGetAccessibilityNodeInfoLocked(Service service, int windowId) {
@@ -1550,7 +1747,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
private boolean isRetrievalAllowingWindow(int windowId) {
- return (mRetrievalAlowingWindowId == windowId);
+ return (mActiveWindowId == windowId);
}
private boolean isActionPermitted(int action) {
@@ -1567,5 +1764,22 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
+ " required to call " + function);
}
}
+
+ private int getFocusedWindowId() {
+ // We call this only on window focus change or after touch
+ // exploration gesture end and the shown windows are not that
+ // many, so the linear look up is just fine.
+ IBinder token = mWindowManagerService.getFocusedWindowClientToken();
+ if (token != null) {
+ SparseArray<IBinder> windows = mWindowIdToWindowTokenMap;
+ final int windowCount = windows.size();
+ for (int i = 0; i < windowCount; i++) {
+ if (windows.valueAt(i) == token) {
+ return windows.keyAt(i);
+ }
+ }
+ }
+ return -1;
+ }
}
}
diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java
index 39012e6..b0b2b8d 100644
--- a/services/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/java/com/android/server/accessibility/TouchExplorer.java
@@ -16,9 +16,6 @@
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 android.content.Context;
import android.gesture.Gesture;
import android.gesture.GestureLibraries;
@@ -26,17 +23,19 @@ import android.gesture.GestureLibrary;
import android.gesture.GesturePoint;
import android.gesture.GestureStroke;
import android.gesture.Prediction;
+import android.graphics.Rect;
import android.os.Handler;
+import android.os.SystemClock;
import android.util.Slog;
import android.view.MotionEvent;
+import android.view.MotionEvent.PointerCoords;
+import android.view.MotionEvent.PointerProperties;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import android.view.WindowManagerPolicy;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import com.android.server.input.InputFilter;
import com.android.internal.R;
+import com.android.server.input.InputFilter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -47,17 +46,18 @@ import java.util.Arrays;
* 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
+ * <li>1. One finger moving slow around performs touch exploration.</li>
+ * <li>2. One finger moving fast around performs gestures.</li>
+ * <li>3. Two close fingers moving in the same direction perform a drag.</li>
+ * <li>4. Multi-finger gestures are delivered to view hierarchy.</li>
+ * <li>5. 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>
+ * <li>6. Two fingers moving in different directions are considered a multi-finger gesture.</li>
+ * <li>7. Double tapping clicks on the on the last touch explored location of it was in
+ * a window that does not take focus, otherwise the click is within the accessibility
+ * focused rectangle.</li>
+ * <li>7. Tapping and holding for a while performs a long press in a similar fashion
+ * as the click above.</li>
* <ol>
*
* @hide
@@ -75,85 +75,116 @@ public class TouchExplorer {
private static final int STATE_DELEGATING = 0x00000004;
private static final int STATE_GESTURE_DETECTING = 0x00000005;
- // 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;
-
// 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 MAX_DRAGGING_ANGLE_COS = 0.525321989f; // cos(pi/4)
- // The delay for sending a hover enter event.
- private static final long DELAY_SEND_HOVER_ENTER = 200;
-
// Constant referring to the ids bits of all pointers.
private static final int ALL_POINTER_ID_BITS = 0xFFFFFFFF;
// 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)
- public static final int MAX_POINTER_COUNT = 32;
+ private static final int MAX_POINTER_COUNT = 32;
// Invalid pointer ID.
- public static final int INVALID_POINTER_ID = -1;
+ private static final int INVALID_POINTER_ID = -1;
+
+ // The velocity above which we detect gestures.
+ private static final int GESTURE_DETECTION_VELOCITY_DIP = 1000;
+
+ // The minimal distance before we take the middle of the distance between
+ // the two dragging pointers as opposed to use the location of the primary one.
+ private static final int MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP = 200;
// Temporary array for storing pointer IDs.
private final int[] mTempPointerIds = new int[MAX_POINTER_COUNT];
- // 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;
+ // Timeout within which we try to detect a tap.
+ private final int mTapTimeout;
+
+ // Timeout within which we try to detect a double tap.
+ private final int mDoubleTapTimeout;
+
+ // Slop between the down and up tap to be a tap.
+ private final int mTouchSlop;
+
+ // Slop between the first and second tap to be a double tap.
+ private final int mDoubleTapSlop;
// The InputFilter this tracker is associated with i.e. the filter
// which delegates event processing to this touch explorer.
private final InputFilter mInputFilter;
- // 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;
+ // Command for delayed sending of a hover enter event.
+ private final SendHoverDelayed mSendHoverEnterDelayed;
+
+ // Command for delayed sending of a hover exit event.
+ private final SendHoverDelayed mSendHoverExitDelayed;
// Command for delayed sending of a long press.
private final PerformLongPressDelayed mPerformLongPressDelayed;
+ // Helper to detect and react to double tap in touch explore mode.
+ private final DoubleTapDetector mDoubleTapDetector;
+
+ // The scaled minimal distance before we take the middle of the distance between
+ // the two dragging pointers as opposed to use the location of the primary one.
+ private final int mScaledMinPointerDistanceToUseMiddleLocation;
+
+ // The scaled velocity above which we detect gestures.
+ private final int mScaledGestureDetectionVelocity;
+
+ // Helper to track gesture velocity.
private VelocityTracker mVelocityTracker;
+ // Helper class to track received pointers.
private final ReceivedPointerTracker mReceivedPointerTracker;
+ // Helper class to track injected pointers.
private final InjectedPointerTracker mInjectedPointerTracker;
- private final GestureListener mGestureListener;
+ // Handle to the accessibility manager service.
+ private final AccessibilityManagerService mAms;
- /**
- * Callback for gesture detection.
- */
- public interface GestureListener {
+ // Temporary rectangle to avoid instantiation.
+ private final Rect mTempRect = new Rect();
- /**
- * Called when a given gesture was performed.
- *
- * @param gestureId The gesture id.
- */
- public boolean onGesture(int gestureId);
- }
+ // The X of the previous event.
+ private float mPreviousX;
+
+ // The Y of the previous event.
+ private float mPreviousY;
+
+ // Buffer for storing points for gesture detection.
+ private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
+
+ // The minimal delta between moves to add a gesture point.
+ private static final int TOUCH_TOLERANCE = 3;
+
+ // The minimal score for accepting a predicted gesture.
+ private static final float MIN_PREDICTION_SCORE = 2.0f;
+
+ // The library for gesture detection.
+ private GestureLibrary mGestureLibrary;
+
+ // The long pressing pointer id if coordinate remapping is needed.
+ private int mLongPressingPointerId;
+
+ // The long pressing pointer X if coordinate remapping is needed.
+ private int mLongPressingPointerDeltaX;
+
+ // The long pressing pointer Y if coordinate remapping is needed.
+ private int mLongPressingPointerDeltaY;
/**
* Creates a new instance.
@@ -162,25 +193,73 @@ public class TouchExplorer {
* @param context A context handle for accessing resources.
*/
public TouchExplorer(InputFilter inputFilter, Context context,
- GestureListener gestureListener) {
- mGestureListener = gestureListener;
+ AccessibilityManagerService service) {
+ mAms = service;
mReceivedPointerTracker = new ReceivedPointerTracker(context);
mInjectedPointerTracker = new InjectedPointerTracker();
mInputFilter = inputFilter;
- mTouchExplorationTapSlop =
- ViewConfiguration.get(context).getScaledTouchExploreTapSlop();
+ mTapTimeout = ViewConfiguration.getTapTimeout();
+ mDoubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
mHandler = new Handler(context.getMainLooper());
- mSendHoverDelayed = new SendHoverDelayed();
mPerformLongPressDelayed = new PerformLongPressDelayed();
- mAccessibilityManager = AccessibilityManager.getInstance(context);
mGestureLibrary = GestureLibraries.fromRawResource(context, R.raw.accessibility_gestures);
mGestureLibrary.setOrientationStyle(4);
mGestureLibrary.load();
+ mSendHoverEnterDelayed = new SendHoverDelayed(MotionEvent.ACTION_HOVER_ENTER, true);
+ mSendHoverExitDelayed = new SendHoverDelayed(MotionEvent.ACTION_HOVER_EXIT, false);
+ mDoubleTapDetector = new DoubleTapDetector();
+ final float density = context.getResources().getDisplayMetrics().density;
+ mScaledMinPointerDistanceToUseMiddleLocation =
+ (int) (MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP * density);
+ mScaledGestureDetectionVelocity = (int) (GESTURE_DETECTION_VELOCITY_DIP * density);
+ }
+
+ public void clear() {
+ // If we have not received an event then we are in initial
+ // state. Therefore, there is not need to clean anything.
+ MotionEvent event = mReceivedPointerTracker.getLastReceivedEvent();
+ if (event != null) {
+ clear(mReceivedPointerTracker.getLastReceivedEvent(), WindowManagerPolicy.FLAG_TRUSTED);
+ }
}
public void clear(MotionEvent event, int policyFlags) {
- sendUpForInjectedDownPointers(event, policyFlags);
- clear();
+ switch (mCurrentState) {
+ case STATE_TOUCH_EXPLORING: {
+ // If a touch exploration gesture is in progress send events for its end.
+ sendExitEventsIfNeeded(policyFlags);
+ } break;
+ case STATE_DRAGGING: {
+ mDraggingPointerId = INVALID_POINTER_ID;
+ // Send exit to all pointers that we have delivered.
+ sendUpForInjectedDownPointers(event, policyFlags);
+ } break;
+ case STATE_DELEGATING: {
+ // Send exit to all pointers that we have delivered.
+ sendUpForInjectedDownPointers(event, policyFlags);
+ } break;
+ case STATE_GESTURE_DETECTING: {
+ // Clear the current stroke.
+ mStrokeBuffer.clear();
+ } break;
+ }
+ // Remove all pending callbacks.
+ mSendHoverEnterDelayed.remove();
+ mSendHoverExitDelayed.remove();
+ mPerformLongPressDelayed.remove();
+ // Reset the pointer trackers.
+ mReceivedPointerTracker.clear();
+ mInjectedPointerTracker.clear();
+ // Clear the double tap detector
+ mDoubleTapDetector.clear();
+ // Go to initial state.
+ // Clear the long pressing pointer remap data.
+ mLongPressingPointerId = -1;
+ mLongPressingPointerDeltaX = 0;
+ mLongPressingPointerDeltaY = 0;
+ mCurrentState = STATE_TOUCH_EXPLORING;
}
public void onMotionEvent(MotionEvent event, int policyFlags) {
@@ -218,7 +297,6 @@ public class TouchExplorer {
*/
private void handleMotionEventStateTouchExploring(MotionEvent event, int policyFlags) {
ReceivedPointerTracker receivedTracker = mReceivedPointerTracker;
- InjectedPointerTracker injectedTracker = mInjectedPointerTracker;
final int activePointerCount = receivedTracker.getActivePointerCount();
if (mVelocityTracker == null) {
@@ -226,8 +304,16 @@ public class TouchExplorer {
}
mVelocityTracker.addMovement(event);
+ mDoubleTapDetector.onMotionEvent(event, policyFlags);
+
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
+ // Pre-feed the motion events to the gesture detector since we
+ // have a distance slop before getting into gesture detection
+ // mode and not using the points within this slop significantly
+ // decreases the quality of gesture recognition.
+ handleMotionEventGestureDetecting(event, policyFlags);
+ //$FALL-THROUGH$
case MotionEvent.ACTION_POINTER_DOWN: {
switch (activePointerCount) {
case 0: {
@@ -235,44 +321,31 @@ public class TouchExplorer {
+ "touch exploring state!");
}
case 1: {
- mSendHoverDelayed.remove();
- mPerformLongPressDelayed.remove();
- // Send a hover for every finger down so the user gets feedback.
- final int pointerId = receivedTracker.getPrimaryActivePointerId();
- final int pointerIdBits = (1 << pointerId);
- final int lastAction = injectedTracker.getLastInjectedHoverAction();
-
- // Deliver hover enter with a delay to have a change to detect
- // whether the user actually starts a scrolling gesture.
- if (lastAction == MotionEvent.ACTION_HOVER_EXIT) {
- mSendHoverDelayed.post(event, MotionEvent.ACTION_HOVER_ENTER,
- pointerIdBits, policyFlags, DELAY_SEND_HOVER_ENTER);
- } else {
- sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits,
- policyFlags);
- }
-
- if (mLastTouchExploreEvent == null) {
- break;
+ // If we still have not notified the user for the last
+ // touch, we figure out what to do. If were waiting
+ // we resent the delayed callback and wait again.
+ if (mSendHoverEnterDelayed.isPending()) {
+ mSendHoverEnterDelayed.remove();
+ mSendHoverExitDelayed.remove();
+ mPerformLongPressDelayed.remove();
}
- // 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;
- }
-
- // If the down is in the time slop => schedule a long press.
- final long pointerDownTime =
- receivedTracker.getReceivedPointerDownTime(pointerId);
- final long lastExploreTime = mLastTouchExploreEvent.getEventTime();
- final long deltaTimeExplore = pointerDownTime - lastExploreTime;
- if (deltaTimeExplore <= ACTIVATION_TIME_SLOP) {
- mPerformLongPressDelayed.post(event, policyFlags,
- ViewConfiguration.getLongPressTimeout());
+ // If we have the first tap schedule a long press and break
+ // since we do not want to schedule hover enter because
+ // the delayed callback will kick in before the long click.
+ // This would lead to a state transition resulting in long
+ // pressing the item below the double taped area which is
+ // not necessary where accessibility focus is.
+ if (mDoubleTapDetector.firstTapDetected()) {
+ // We got a tap now post a long press action.
+ mPerformLongPressDelayed.post(event, policyFlags);
break;
}
+ // Deliver hover enter with a delay to have a chance
+ // to detect what the user is trying to do.
+ final int pointerId = receivedTracker.getPrimaryActivePointerId();
+ final int pointerIdBits = (1 << pointerId);
+ mSendHoverEnterDelayed.post(event, pointerIdBits, policyFlags);
} break;
default: {
/* do nothing - let the code for ACTION_MOVE decide what to do */
@@ -288,119 +361,130 @@ public class TouchExplorer {
/* do nothing - no active pointers so we swallow the event */
} break;
case 1: {
- // Detect touch exploration gesture start by having one active pointer
- // that moved more than a given distance.
- if (!mTouchExploreGestureInProgress) {
+ // We have not started sending events since we try to
+ // figure out what the user is doing.
+ if (mSendHoverEnterDelayed.isPending()) {
+ // Pre-feed the motion events to the gesture detector since we
+ // have a distance slop before getting into gesture detection
+ // mode and not using the points within this slop significantly
+ // decreases the quality of gesture recognition.
+ handleMotionEventGestureDetecting(event, policyFlags);
+
final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId)
- event.getX(pointerIndex);
final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId)
- event.getY(pointerIndex);
final double moveDelta = Math.hypot(deltaX, deltaY);
-
- if (moveDelta > mTouchExplorationTapSlop) {
-
+ // The user has moved enough for us to decide.
+ if (moveDelta > mDoubleTapSlop) {
+ // Check whether the user is performing a gesture. We
+ // detect gestures if the pointer is moving above a
+ // given velocity.
mVelocityTracker.computeCurrentVelocity(1000);
final float maxAbsVelocity = Math.max(
Math.abs(mVelocityTracker.getXVelocity(pointerId)),
Math.abs(mVelocityTracker.getYVelocity(pointerId)));
- // TODO: Tune the velocity cut off and add a constant.
- if (maxAbsVelocity > 1000) {
- clear(event, policyFlags);
+ if (maxAbsVelocity > mScaledGestureDetectionVelocity) {
+ // We have to perform gesture detection, so
+ // clear the current state and try to detect.
mCurrentState = STATE_GESTURE_DETECTING;
- event.setAction(MotionEvent.ACTION_DOWN);
- handleMotionEventGestureDetecting(event, policyFlags);
- return;
- }
-
- mTouchExploreGestureInProgress = true;
- sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_START);
- // Make sure the scheduled down/move event is sent.
- mSendHoverDelayed.forceSendAndRemove();
- mPerformLongPressDelayed.remove();
- // If we have transitioned to exploring state from another one
- // we need to send a hover enter event here.
- final int lastAction = injectedTracker.getLastInjectedHoverAction();
- if (lastAction == MotionEvent.ACTION_HOVER_EXIT) {
- sendMotionEvent(event, MotionEvent.ACTION_HOVER_ENTER,
+ mSendHoverEnterDelayed.remove();
+ mSendHoverExitDelayed.remove();
+ mPerformLongPressDelayed.remove();
+ } else {
+ // We have just decided that the user is touch,
+ // exploring so start sending events.
+ mSendHoverEnterDelayed.forceSendAndRemove();
+ mSendHoverExitDelayed.remove();
+ sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE,
pointerIdBits, policyFlags);
}
- sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits,
- policyFlags);
+ break;
}
} else {
- // Touch exploration gesture in progress so send a hover event.
+ // The user is wither double tapping or performing long
+ // press so do not send move events yet.
+ if (mDoubleTapDetector.firstTapDetected()) {
+ break;
+ }
+ sendEnterEventsIfNeeded(policyFlags);
sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits,
policyFlags);
}
-
- // If the exploring pointer moved enough => cancel the long press.
- if (!mTouchExploreGestureInProgress && mLastTouchExploreEvent != null
- && mPerformLongPressDelayed.isPenidng()) {
-
- // If the pointer moved more than the tap slop => cancel long press.
- final float deltaX = mLastTouchExploreEvent.getX(pointerIndex)
+ } break;
+ case 2: {
+ // More than one pointer so the user is not touch exploring
+ // and now we have to decide whether to delegate or drag.
+ if (mSendHoverEnterDelayed.isPending()) {
+ // We have not started sending events so cancel
+ // scheduled sending events.
+ mSendHoverEnterDelayed.remove();
+ mSendHoverExitDelayed.remove();
+ mPerformLongPressDelayed.remove();
+ } else {
+ // If the user is touch exploring the second pointer may be
+ // performing a double tap to activate an item without need
+ // for the user to lift his exploring finger.
+ final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId)
- event.getX(pointerIndex);
- final float deltaY = mLastTouchExploreEvent.getY(pointerIndex)
+ final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId)
- event.getY(pointerIndex);
- final float moveDelta = (float) Math.hypot(deltaX, deltaY);
- if (moveDelta > mTouchExplorationTapSlop) {
- mLastTouchExploreEvent = null;
- mPerformLongPressDelayed.remove();
+ final double moveDelta = Math.hypot(deltaX, deltaY);
+ if (moveDelta < mDoubleTapSlop) {
break;
}
+ // We are sending events so send exit and gesture
+ // end since we transition to another state.
+ sendExitEventsIfNeeded(policyFlags);
}
- } break;
- case 2: {
- mSendHoverDelayed.remove();
- mPerformLongPressDelayed.remove();
- // We want to no longer hover over the location so subsequent
- // touch at the same spot will generate a hover enter.
- ensureHoverExitSent(event, pointerIdBits, policyFlags);
+
+ // We know that a new state transition is to happen and the
+ // new state will not be gesture recognition, so clear the
+ // stashed gesture strokes.
+ mStrokeBuffer.clear();
if (isDraggingGesture(event)) {
// Two pointers moving in the same direction within
// a given distance perform a drag.
+ mSendHoverEnterDelayed.remove();
+ mSendHoverExitDelayed.remove();
+ mPerformLongPressDelayed.remove();
mCurrentState = STATE_DRAGGING;
- if (mTouchExploreGestureInProgress) {
- sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END);
- mTouchExploreGestureInProgress = false;
- }
- mLastTouchExploreEvent = null;
mDraggingPointerId = pointerId;
sendMotionEvent(event, MotionEvent.ACTION_DOWN, pointerIdBits,
policyFlags);
} else {
// Two pointers moving arbitrary are delegated to the view hierarchy.
mCurrentState = STATE_DELEGATING;
- mSendHoverDelayed.remove();
- if (mTouchExploreGestureInProgress) {
- sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END);
- mTouchExploreGestureInProgress = false;
- }
- mLastTouchExploreEvent = null;
sendDownForAllActiveNotInjectedPointers(event, policyFlags);
}
} break;
default: {
- mSendHoverDelayed.remove();
- mPerformLongPressDelayed.remove();
- // We want to no longer hover over the location so subsequent
- // touch at the same spot will generate a hover enter.
- ensureHoverExitSent(event, pointerIdBits, policyFlags);
+ // More than one pointer so the user is not touch exploring
+ // and now we have to decide whether to delegate or drag.
+ if (mSendHoverEnterDelayed.isPending()) {
+ // We have not started sending events so cancel
+ // scheduled sending events.
+ mSendHoverEnterDelayed.remove();
+ mSendHoverExitDelayed.remove();
+ mPerformLongPressDelayed.remove();
+ } else {
+ // We are sending events so send exit and gesture
+ // end since we transition to another state.
+ sendExitEventsIfNeeded(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;
- }
- mLastTouchExploreEvent = null;
sendDownForAllActiveNotInjectedPointers(event, policyFlags);
}
}
} break;
case MotionEvent.ACTION_UP:
+ // We know that we do not need the pre-fed gesture points are not
+ // needed anymore since the last pointer just went up.
+ mStrokeBuffer.clear();
+ //$FALL-THROUGH$
case MotionEvent.ACTION_POINTER_UP: {
final int pointerId = receivedTracker.getLastReceivedUpPointerId();
final int pointerIdBits = (1 << pointerId);
@@ -413,59 +497,12 @@ public class TouchExplorer {
mPerformLongPressDelayed.remove();
- // If touch exploring announce the end of the gesture.
- // Also do not click on the last explored location.
- if (mTouchExploreGestureInProgress) {
- mTouchExploreGestureInProgress = false;
- mSendHoverDelayed.forceSendAndRemove();
- ensureHoverExitSent(event, pointerIdBits, policyFlags);
- mLastTouchExploreEvent = MotionEvent.obtain(event);
- sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END);
- break;
- }
-
- // Detect whether to activate i.e. click on the last explored location.
- if (mLastTouchExploreEvent != null) {
- // If the down was not in the time slop => nothing else to do.
- final long eventTime =
- receivedTracker.getLastReceivedUpPointerDownTime();
- final long exploreTime = mLastTouchExploreEvent.getEventTime();
- final long deltaTime = eventTime - exploreTime;
- if (deltaTime > ACTIVATION_TIME_SLOP) {
- mSendHoverDelayed.forceSendAndRemove();
- ensureHoverExitSent(event, pointerIdBits, policyFlags);
- mLastTouchExploreEvent = MotionEvent.obtain(event);
- break;
- }
-
- // If a tap is farther than the tap slop => nothing to do.
- final int pointerIndex = event.findPointerIndex(pointerId);
- final float deltaX = mLastTouchExploreEvent.getX(pointerIndex)
- - event.getX(pointerIndex);
- final float deltaY = mLastTouchExploreEvent.getY(pointerIndex)
- - event.getY(pointerIndex);
- final float deltaMove = (float) Math.hypot(deltaX, deltaY);
- if (deltaMove > mTouchExplorationTapSlop) {
- mSendHoverDelayed.forceSendAndRemove();
- ensureHoverExitSent(event, pointerIdBits, policyFlags);
- mLastTouchExploreEvent = MotionEvent.obtain(event);
- break;
- }
-
- // This is a tap so do not send hover events since
- // this events will result in firing the corresponding
- // accessibility events confusing the user about what
- // is actually clicked.
- mSendHoverDelayed.remove();
- ensureHoverExitSent(event, pointerIdBits, policyFlags);
-
- // All preconditions are met, so click the last explored location.
- sendActionDownAndUp(mLastTouchExploreEvent, policyFlags);
- mLastTouchExploreEvent = null;
+ // If we have not delivered the enter schedule exit.
+ if (mSendHoverEnterDelayed.isPending()) {
+ mSendHoverExitDelayed.post(event, pointerIdBits, policyFlags);
} else {
- mSendHoverDelayed.forceSendAndRemove();
- ensureHoverExitSent(event, pointerIdBits, policyFlags);
- mLastTouchExploreEvent = MotionEvent.obtain(event);
+ // The user is touch exploring so we send events for end.
+ sendExitEventsIfNeeded(policyFlags);
}
} break;
}
@@ -475,16 +512,7 @@ public class TouchExplorer {
}
} break;
case MotionEvent.ACTION_CANCEL: {
- mSendHoverDelayed.remove();
- mPerformLongPressDelayed.remove();
- final int pointerId = receivedTracker.getPrimaryActivePointerId();
- final int pointerIdBits = (1 << pointerId);
- ensureHoverExitSent(event, pointerIdBits, policyFlags);
- clear();
- if (mVelocityTracker != null) {
- mVelocityTracker.clear();
- mVelocityTracker = null;
- }
+ clear(event, policyFlags);
} break;
}
}
@@ -517,6 +545,28 @@ public class TouchExplorer {
} break;
case 2: {
if (isDraggingGesture(event)) {
+ // If the dragging pointer are closer that a given distance we
+ // use the location of the primary one. Otherwise, we take the
+ // middle between the pointers.
+ int[] pointerIds = mTempPointerIds;
+ mReceivedPointerTracker.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);
+
+ final float deltaX = firstPtrX - secondPtrX;
+ final float deltaY = firstPtrY - secondPtrY;
+ final double distance = Math.hypot(deltaX, deltaY);
+
+ if (distance > mScaledMinPointerDistanceToUseMiddleLocation) {
+ event.setLocation(deltaX / 2, deltaY / 2);
+ }
+
// If still dragging send a drag event.
sendMotionEvent(event, MotionEvent.ACTION_MOVE, pointerIdBits,
policyFlags);
@@ -557,7 +607,7 @@ public class TouchExplorer {
mCurrentState = STATE_TOUCH_EXPLORING;
} break;
case MotionEvent.ACTION_CANCEL: {
- clear();
+ clear(event, policyFlags);
} break;
}
}
@@ -574,9 +624,6 @@ public class TouchExplorer {
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
@@ -587,30 +634,24 @@ public class TouchExplorer {
sendDownForAllActiveNotInjectedPointers(prototype, policyFlags);
}
} break;
+ case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP: {
+ mLongPressingPointerId = -1;
+ mLongPressingPointerDeltaX = 0;
+ mLongPressingPointerDeltaY = 0;
// No active pointers => go to initial state.
if (mReceivedPointerTracker.getActivePointerCount() == 0) {
mCurrentState = STATE_TOUCH_EXPLORING;
}
} break;
case MotionEvent.ACTION_CANCEL: {
- clear();
+ clear(event, policyFlags);
} break;
}
// Deliver the event striping out inactive pointers.
sendMotionEventStripInactivePointers(event, policyFlags);
}
- private float mPreviousX;
- private float mPreviousY;
-
- private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
-
- private static final int TOUCH_TOLERANCE = 3;
- private static final float MIN_PREDICTION_SCORE = 2.0f;
-
- private GestureLibrary mGestureLibrary;
-
private void handleMotionEventGestureDetecting(MotionEvent event, int policyFlags) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
@@ -631,8 +672,7 @@ public class TouchExplorer {
mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
}
} break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_POINTER_UP: {
+ case MotionEvent.ACTION_UP: {
float x = event.getX();
float y = event.getY();
mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
@@ -650,7 +690,7 @@ public class TouchExplorer {
}
try {
final int gestureId = Integer.parseInt(bestPrediction.name);
- mGestureListener.onGesture(gestureId);
+ mAms.onGesture(gestureId);
} catch (NumberFormatException nfe) {
Slog.w(LOG_TAG, "Non numeric gesture id:" + bestPrediction.name);
}
@@ -661,8 +701,7 @@ public class TouchExplorer {
mCurrentState = STATE_TOUCH_EXPLORING;
} break;
case MotionEvent.ACTION_CANCEL: {
- mStrokeBuffer.clear();
- mCurrentState = STATE_TOUCH_EXPLORING;
+ clear(event, policyFlags);
} break;
}
}
@@ -706,17 +745,32 @@ public class TouchExplorer {
}
/**
- * Ensures that hover exit has been sent.
+ * Sends the exit events if needed. Such events are hover exit and touch explore
+ * gesture end.
*
- * @param prototype The prototype from which to create the injected events.
- * @param pointerIdBits The bits of the pointers to send.
* @param policyFlags The policy flags associated with the event.
*/
- private void ensureHoverExitSent(MotionEvent prototype, int pointerIdBits, int policyFlags) {
- final int lastAction = mInjectedPointerTracker.getLastInjectedHoverAction();
- if (lastAction != MotionEvent.ACTION_HOVER_EXIT) {
- sendMotionEvent(prototype, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits,
- policyFlags);
+ private void sendExitEventsIfNeeded(int policyFlags) {
+ MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent();
+ if (event != null && event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT) {
+ final int pointerIdBits = event.getPointerIdBits();
+ mAms.touchExplorationGestureEnded();
+ sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, policyFlags);
+ }
+ }
+
+ /**
+ * Sends the enter events if needed. Such events are hover enter and touch explore
+ * gesture start.
+ *
+ * @param policyFlags The policy flags associated with the event.
+ */
+ private void sendEnterEventsIfNeeded(int policyFlags) {
+ MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent();
+ if (event != null && event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) {
+ final int pointerIdBits = event.getPointerIdBits();
+ mAms.touchExplorationGestureStarted();
+ sendMotionEvent(event, MotionEvent.ACTION_HOVER_ENTER, pointerIdBits, policyFlags);
}
}
@@ -826,6 +880,36 @@ public class TouchExplorer {
event.setDownTime(mInjectedPointerTracker.getLastInjectedDownEventTime());
}
+ // If the user is long pressing but the long pressing pointer
+ // was not exactly over the accessibility focused item we need
+ // to remap the location of that pointer so the user does not
+ // have to explicitly touch explore something to be able to
+ // long press it, or even worse to avoid the user long pressing
+ // on the wrong item since click and long press behave differently.
+ if (mLongPressingPointerId >= 0) {
+ final int remappedIndex = event.findPointerIndex(mLongPressingPointerId);
+ final int pointerCount = event.getPointerCount();
+ PointerProperties[] props = PointerProperties.createArray(pointerCount);
+ PointerCoords[] coords = PointerCoords.createArray(pointerCount);
+ for (int i = 0; i < pointerCount; i++) {
+ event.getPointerProperties(i, props[i]);
+ event.getPointerCoords(i, coords[i]);
+ if (i == remappedIndex) {
+ coords[i].x -= mLongPressingPointerDeltaX;
+ coords[i].y -= mLongPressingPointerDeltaY;
+ }
+ }
+ MotionEvent remapped = MotionEvent.obtain(event.getDownTime(),
+ event.getEventTime(), event.getAction(), event.getPointerCount(),
+ props, coords, event.getMetaState(), event.getButtonState(),
+ 1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(),
+ event.getSource(), event.getFlags());
+ if (event != prototype) {
+ event.recycle();
+ }
+ event = remapped;
+ }
+
if (DEBUG) {
Slog.d(LOG_TAG, "Injecting event: " + event + ", policyFlags=0x"
+ Integer.toHexString(policyFlags));
@@ -878,6 +962,172 @@ public class TouchExplorer {
}
}
+ private class DoubleTapDetector {
+ private MotionEvent mDownEvent;
+ private MotionEvent mFirstTapEvent;
+
+ public void onMotionEvent(MotionEvent event, int policyFlags) {
+ final int action = event.getActionMasked();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ if (mFirstTapEvent != null && !isSamePointerContext(mFirstTapEvent, event)) {
+ clear();
+ }
+ mDownEvent = MotionEvent.obtain(event);
+ } break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_POINTER_UP: {
+ if (mDownEvent == null) {
+ return;
+ }
+ if (!isSamePointerContext(mDownEvent, event)) {
+ clear();
+ return;
+ }
+ if (isTap(mDownEvent, event)) {
+ if (mFirstTapEvent == null || isTimedOut(mFirstTapEvent, event,
+ mDoubleTapTimeout)) {
+ mFirstTapEvent = MotionEvent.obtain(event);
+ mDownEvent.recycle();
+ mDownEvent = null;
+ return;
+ }
+ if (isDoubleTap(mFirstTapEvent, event)) {
+ onDoubleTap(event, policyFlags);
+ mFirstTapEvent.recycle();
+ mFirstTapEvent = null;
+ mDownEvent.recycle();
+ mDownEvent = null;
+ return;
+ }
+ mFirstTapEvent.recycle();
+ mFirstTapEvent = null;
+ } else {
+ if (mFirstTapEvent != null) {
+ mFirstTapEvent.recycle();
+ mFirstTapEvent = null;
+ }
+ }
+ mDownEvent.recycle();
+ mDownEvent = null;
+ } break;
+ }
+ }
+
+ public void onDoubleTap(MotionEvent secondTapUp, int policyFlags) {
+ // This should never be called when more than two pointers are down.
+ if (secondTapUp.getPointerCount() > 2) {
+ return;
+ }
+
+ // Remove pending event deliveries.
+ mSendHoverEnterDelayed.remove();
+ mSendHoverExitDelayed.remove();
+ mPerformLongPressDelayed.remove();
+
+ // This is a tap so do not send hover events since
+ // this events will result in firing the corresponding
+ // accessibility events confusing the user about what
+ // is actually clicked.
+ sendExitEventsIfNeeded(policyFlags);
+
+ // If the last touched explored location is not within the focused
+ // window we will click at that exact spot, otherwise we find the
+ // accessibility focus and if the tap is within its bounds we click
+ // there, otherwise we pick the middle of the focus rectangle.
+ MotionEvent lastEvent = mInjectedPointerTracker.getLastInjectedHoverEvent();
+ if (lastEvent == null) {
+ return;
+ }
+
+ final int exploreLocationX = (int) lastEvent.getX(lastEvent.getActionIndex());
+ final int exploreLocationY = (int) lastEvent.getY(lastEvent.getActionIndex());
+
+ Rect bounds = mTempRect;
+ boolean useLastHoverLocation = false;
+
+ final int pointerId = secondTapUp.getPointerId(secondTapUp.getActionIndex());
+ final int pointerIndex = secondTapUp.findPointerIndex(pointerId);
+ if (mAms.getAccessibilityFocusBounds(exploreLocationX, exploreLocationY, bounds)) {
+ // If the user's last touch explored location is not
+ // within the accessibility focus bounds we use the center
+ // of the accessibility focused rectangle.
+ if (!bounds.contains((int) secondTapUp.getX(pointerIndex),
+ (int) secondTapUp.getY(pointerIndex))) {
+ useLastHoverLocation = true;
+ }
+ }
+
+ // Do the click.
+ PointerProperties[] properties = new PointerProperties[1];
+ properties[0] = new PointerProperties();
+ secondTapUp.getPointerProperties(pointerIndex, properties[0]);
+ PointerCoords[] coords = new PointerCoords[1];
+ coords[0] = new PointerCoords();
+ coords[0].x = (useLastHoverLocation) ? bounds.centerX() : exploreLocationX;
+ coords[0].y = (useLastHoverLocation) ? bounds.centerY() : exploreLocationY;
+ MotionEvent event = MotionEvent.obtain(secondTapUp.getDownTime(),
+ secondTapUp.getEventTime(), MotionEvent.ACTION_DOWN, 1, properties,
+ coords, 0, 0, 1.0f, 1.0f, secondTapUp.getDeviceId(), 0,
+ secondTapUp.getSource(), secondTapUp.getFlags());
+ sendActionDownAndUp(event, policyFlags);
+ event.recycle();
+ }
+
+ public void clear() {
+ if (mDownEvent != null) {
+ mDownEvent.recycle();
+ mDownEvent = null;
+ }
+ if (mFirstTapEvent != null) {
+ mFirstTapEvent.recycle();
+ mFirstTapEvent = null;
+ }
+ }
+
+ public boolean isTap(MotionEvent down, MotionEvent up) {
+ return eventsWithinTimeoutAndDistance(down, up, mTapTimeout, mTouchSlop);
+ }
+
+ private boolean isDoubleTap(MotionEvent firstUp, MotionEvent secondUp) {
+ return eventsWithinTimeoutAndDistance(firstUp, secondUp, mDoubleTapTimeout,
+ mDoubleTapSlop);
+ }
+
+ private boolean eventsWithinTimeoutAndDistance(MotionEvent first, MotionEvent second,
+ int timeout, int distance) {
+ if (isTimedOut(first, second, timeout)) {
+ return false;
+ }
+ final int downPtrIndex = first.getActionIndex();
+ final int upPtrIndex = second.getActionIndex();
+ final float deltaX = second.getX(upPtrIndex) - first.getX(downPtrIndex);
+ final float deltaY = second.getY(upPtrIndex) - first.getY(downPtrIndex);
+ final double deltaMove = Math.hypot(deltaX, deltaY);
+ if (deltaMove >= distance) {
+ return false;
+ }
+ return true;
+ }
+
+ private boolean isTimedOut(MotionEvent firstUp, MotionEvent secondUp, int timeout) {
+ final long deltaTime = secondUp.getEventTime() - firstUp.getEventTime();
+ return (deltaTime >= timeout);
+ }
+
+ private boolean isSamePointerContext(MotionEvent first, MotionEvent second) {
+ return (first.getPointerIdBits() == second.getPointerIdBits()
+ && first.getPointerId(first.getActionIndex())
+ == second.getPointerId(second.getActionIndex()));
+ }
+
+ public boolean firstTapDetected() {
+ return mFirstTapEvent != null
+ && SystemClock.uptimeMillis() - mFirstTapEvent.getEventTime() < mDoubleTapTimeout;
+ }
+ }
+
/**
* Determines whether a two pointer gesture is a dragging one.
*
@@ -940,30 +1190,6 @@ public class TouchExplorer {
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);
- mAccessibilityManager.sendAccessibilityEvent(event);
- }
-
- /**
- * Clears the internal state of this explorer.
- */
- public void clear() {
- mSendHoverDelayed.remove();
- mPerformLongPressDelayed.remove();
- mReceivedPointerTracker.clear();
- mInjectedPointerTracker.clear();
- mLastTouchExploreEvent = null;
- mCurrentState = STATE_TOUCH_EXPLORING;
- mTouchExploreGestureInProgress = false;
- mDraggingPointerId = INVALID_POINTER_ID;
- }
-
/**
* Gets the symbolic name of a state.
*
@@ -1002,10 +1228,10 @@ public class TouchExplorer {
private MotionEvent mEvent;
private int mPolicyFlags;
- public void post(MotionEvent prototype, int policyFlags, long delay) {
+ public void post(MotionEvent prototype, int policyFlags) {
mEvent = MotionEvent.obtain(prototype);
mPolicyFlags = policyFlags;
- mHandler.postDelayed(this, delay);
+ mHandler.postDelayed(this, ViewConfiguration.getLongPressTimeout());
}
public void remove() {
@@ -1021,16 +1247,29 @@ public class TouchExplorer {
@Override
public void run() {
- mCurrentState = STATE_DELEGATING;
- // Make sure the scheduled hover exit is delivered.
- mSendHoverDelayed.remove();
+ final int pointerIndex = mEvent.getActionIndex();
+ final int eventX = (int) mEvent.getX(pointerIndex);
+ final int eventY = (int) mEvent.getY(pointerIndex);
+ Rect bounds = mTempRect;
+ if (mAms.getAccessibilityFocusBounds(eventX, eventY, bounds)
+ && !bounds.contains(eventX, eventY)) {
+ mLongPressingPointerId = mEvent.getPointerId(pointerIndex);
+ mLongPressingPointerDeltaX = eventX - bounds.centerX();
+ mLongPressingPointerDeltaY = eventY - bounds.centerY();
+ } else {
+ mLongPressingPointerId = -1;
+ mLongPressingPointerDeltaX = 0;
+ mLongPressingPointerDeltaY = 0;
+ }
+ // We are sending events so send exit and gesture
+ // end since we transition to another state.
final int pointerId = mReceivedPointerTracker.getPrimaryActivePointerId();
final int pointerIdBits = (1 << pointerId);
- ensureHoverExitSent(mEvent, pointerIdBits, mPolicyFlags);
+ mAms.touchExplorationGestureEnded();
+ sendExitEventsIfNeeded(mPolicyFlags);
+ mCurrentState = STATE_DELEGATING;
sendDownForAllActiveNotInjectedPointers(mEvent, mPolicyFlags);
- mTouchExploreGestureInProgress = false;
- mLastTouchExploreEvent = null;
clear();
}
@@ -1047,20 +1286,41 @@ public class TouchExplorer {
/**
* Class for delayed sending of hover events.
*/
- private final class SendHoverDelayed implements Runnable {
- private MotionEvent mEvent;
- private int mAction;
+ class SendHoverDelayed implements Runnable {
+ private final String LOG_TAG_SEND_HOVER_DELAYED = SendHoverDelayed.class.getName();
+
+ private final int mHoverAction;
+ private final boolean mGestureStarted;
+
+ private MotionEvent mPrototype;
private int mPointerIdBits;
private int mPolicyFlags;
- public void post(MotionEvent prototype, int action, int pointerIdBits, int policyFlags,
- long delay) {
+ public SendHoverDelayed(int hoverAction, boolean gestureStarted) {
+ mHoverAction = hoverAction;
+ mGestureStarted = gestureStarted;
+ }
+
+ public void post(MotionEvent prototype, int pointerIdBits, int policyFlags) {
remove();
- mEvent = MotionEvent.obtain(prototype);
- mAction = action;
+ mPrototype = MotionEvent.obtain(prototype);
mPointerIdBits = pointerIdBits;
mPolicyFlags = policyFlags;
- mHandler.postDelayed(this, delay);
+ mHandler.postDelayed(this, mTapTimeout);
+ }
+
+ public float getX() {
+ if (isPending()) {
+ return mPrototype.getX();
+ }
+ return 0;
+ }
+
+ public float getY() {
+ if (isPending()) {
+ return mPrototype.getY();
+ }
+ return 0;
}
public void remove() {
@@ -1068,23 +1328,22 @@ public class TouchExplorer {
clear();
}
- private boolean isPenidng() {
- return (mEvent != null);
+ private boolean isPending() {
+ return (mPrototype != null);
}
private void clear() {
- if (!isPenidng()) {
+ if (!isPending()) {
return;
}
- mEvent.recycle();
- mEvent = null;
- mAction = 0;
+ mPrototype.recycle();
+ mPrototype = null;
mPointerIdBits = -1;
mPolicyFlags = 0;
}
public void forceSendAndRemove() {
- if (isPenidng()) {
+ if (isPending()) {
run();
remove();
}
@@ -1092,16 +1351,17 @@ public class TouchExplorer {
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");
- }
+ Slog.d(LOG_TAG_SEND_HOVER_DELAYED, "Injecting motion event: "
+ + MotionEvent.actionToString(mHoverAction));
+ Slog.d(LOG_TAG_SEND_HOVER_DELAYED, mGestureStarted ?
+ "touchExplorationGestureStarted" : "touchExplorationGestureEnded");
}
-
- sendMotionEvent(mEvent, mAction, mPointerIdBits, mPolicyFlags);
+ if (mGestureStarted) {
+ mAms.touchExplorationGestureStarted();
+ } else {
+ mAms.touchExplorationGestureEnded();
+ }
+ sendMotionEvent(mPrototype, mHoverAction, mPointerIdBits, mPolicyFlags);
clear();
}
}
@@ -1120,8 +1380,8 @@ public class TouchExplorer {
// 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;
+ // The last injected hover event.
+ private MotionEvent mLastInjectedHoverEvent;
/**
* Processes an injected {@link MotionEvent} event.
@@ -1150,11 +1410,14 @@ public class TouchExplorer {
case MotionEvent.ACTION_HOVER_ENTER:
case MotionEvent.ACTION_HOVER_MOVE:
case MotionEvent.ACTION_HOVER_EXIT: {
- mLastInjectedHoverEventAction = event.getActionMasked();
+ if (mLastInjectedHoverEvent != null) {
+ mLastInjectedHoverEvent.recycle();
+ }
+ mLastInjectedHoverEvent = MotionEvent.obtain(event);
} break;
}
if (DEBUG) {
- Slog.i(LOG_TAG_INJECTED_POINTER_TRACKER, "Injected pointer: " + toString());
+ Slog.i(LOG_TAG_INJECTED_POINTER_TRACKER, "Injected pointer:\n" + toString());
}
}
@@ -1198,10 +1461,10 @@ public class TouchExplorer {
}
/**
- * @return The action of the last injected hover event.
+ * @return The the last injected hover event.
*/
- public int getLastInjectedHoverAction() {
- return mLastInjectedHoverEventAction;
+ public MotionEvent getLastInjectedHoverEvent() {
+ return mLastInjectedHoverEvent;
}
@Override
@@ -1260,6 +1523,8 @@ public class TouchExplorer {
private float mLastReceivedUpPointerDownX;
private float mLastReceivedUpPointerDownY;
+ private MotionEvent mLastReceivedEvent;
+
/**
* Creates a new instance.
*
@@ -1294,6 +1559,11 @@ public class TouchExplorer {
* @param event The event to process.
*/
public void onMotionEvent(MotionEvent event) {
+ if (mLastReceivedEvent != null) {
+ mLastReceivedEvent.recycle();
+ }
+ mLastReceivedEvent = MotionEvent.obtain(event);
+
final int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN: {
@@ -1318,6 +1588,13 @@ public class TouchExplorer {
}
/**
+ * @return The last received event.
+ */
+ public MotionEvent getLastReceivedEvent() {
+ return mLastReceivedEvent;
+ }
+
+ /**
* @return The number of received pointers that are down.
*/
public int getReceivedPointerDownCount() {
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 076ba9a..b89e48f 100755
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -6561,6 +6561,16 @@ public class WindowManagerService extends IWindowManager.Stub
sendScreenStatusToClients();
}
+ public IBinder getFocusedWindowClientToken() {
+ synchronized (mWindowMap) {
+ WindowState windowState = getFocusedWindowLocked();
+ if (windowState != null) {
+ return windowState.mClient.asBinder();
+ }
+ return null;
+ }
+ }
+
private WindowState getFocusedWindow() {
synchronized (mWindowMap) {
return getFocusedWindowLocked();