Merge "Adding an opt-in mechanism for gesture detection in AccessibilityService."
diff --git a/Android.mk b/Android.mk
index 3b2d32d..a98b1c2 100644
--- a/Android.mk
+++ b/Android.mk
@@ -61,6 +61,7 @@
 LOCAL_SRC_FILES += \
 	core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl \
 	core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl \
+	core/java/android/accessibilityservice/IAccessibilityServiceClientCallback.aidl \
 	core/java/android/accounts/IAccountManager.aidl \
 	core/java/android/accounts/IAccountManagerResponse.aidl \
 	core/java/android/accounts/IAccountAuthenticator.aidl \
diff --git a/api/current.txt b/api/current.txt
index 56a34d6..0e8a926 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -294,6 +294,7 @@
     field public static final int cacheColorHint = 16843009; // 0x1010101
     field public static final int calendarViewShown = 16843596; // 0x101034c
     field public static final int calendarViewStyle = 16843613; // 0x101035d
+    field public static final int canHandleGestures = 16843699; // 0x10103b3
     field public static final int canRetrieveWindowContent = 16843653; // 0x1010385
     field public static final int candidatesTextStyleSpans = 16843312; // 0x1010230
     field public static final deprecated int capitalize = 16843113; // 0x1010169
@@ -1997,7 +1998,7 @@
     method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
     method public abstract void onAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
     method public final android.os.IBinder onBind(android.content.Intent);
-    method protected void onGesture(int);
+    method protected boolean onGesture(int);
     method public abstract void onInterrupt();
     method protected void onServiceConnected();
     method public final boolean performGlobalAction(int);
@@ -2020,6 +2021,8 @@
     field public static final int GESTURE_SWIPE_UP_AND_DOWN = 7; // 0x7
     field public static final int GESTURE_SWIPE_UP_AND_LEFT = 15; // 0xf
     field public static final int GESTURE_SWIPE_UP_AND_RIGHT = 16; // 0x10
+    field public static final int GESTURE_TWO_FINGER_LONG_PRESS = 20; // 0x14
+    field public static final int GESTURE_TWO_FINGER_TAP = 19; // 0x13
     field public static final int GLOBAL_ACTION_BACK = 1; // 0x1
     field public static final int GLOBAL_ACTION_HOME = 2; // 0x2
     field public static final int GLOBAL_ACTION_NOTIFICATIONS = 4; // 0x4
@@ -2033,6 +2036,7 @@
     method public int describeContents();
     method public static java.lang.String feedbackTypeToString(int);
     method public static java.lang.String flagToString(int);
+    method public boolean getCanHandleGestures();
     method public boolean getCanRetrieveWindowContent();
     method public deprecated java.lang.String getDescription();
     method public java.lang.String getId();
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 4e340c0..c858e3c 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -19,22 +19,17 @@
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
-import android.content.res.Configuration;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
-import android.util.LocaleUtil;
 import android.util.Log;
-import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityInteractionClient;
 import android.view.accessibility.AccessibilityNodeInfo;
 
 import com.android.internal.os.HandlerCaller;
 
-import java.util.Locale;
-
 /**
  * An accessibility service runs in the background and receives callbacks by the system
  * when {@link AccessibilityEvent}s are fired. Such events denote some state transition
@@ -299,6 +294,16 @@
     public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 18;
 
     /**
+     * The user has performed a two finger tap gesture on the touch screen.
+     */
+    public static final int GESTURE_TWO_FINGER_TAP = 19;
+
+    /**
+     * The user has performed a two finger long press gesture on the touch screen.
+     */
+    public static final int GESTURE_TWO_FINGER_LONG_PRESS = 20;
+
+    /**
      * The {@link Intent} that must be declared as handled by the service.
      */
     public static final String SERVICE_INTERFACE =
@@ -342,8 +347,6 @@
      */
     public static final int GLOBAL_ACTION_NOTIFICATIONS = 4;
 
-    private static final int UNDEFINED = -1;
-
     private static final String LOG_TAG = "AccessibilityService";
 
     interface Callbacks {
@@ -351,15 +354,13 @@
         public void onInterrupt();
         public void onServiceConnected();
         public void onSetConnectionId(int connectionId);
-        public void onGesture(int gestureId);
+        public boolean onGesture(int gestureId);
     }
 
     private int mConnectionId;
 
     private AccessibilityServiceInfo mInfo;
 
-    private int mLayoutDirection;
-
     /**
      * Callback for {@link android.view.accessibility.AccessibilityEvent}s.
      *
@@ -386,95 +387,43 @@
 
     /**
      * Called by the system when the user performs a specific gesture on the
-     * touch screen.
+     * touch screen. If the gesture is not handled in this callback the system
+     * may provide default handing. Therefore, one should return true from this
+     * function if overriding of default behavior is desired.
+     *
+     * <strong>Note:</strong> To receive gestures an accessibility service
+     *         must declare that it can handle such by specifying the
+     *         <code>&lt;{@link android.R.styleable#AccessibilityService_canHandleGestures
+     *         canHandleGestures}&gt;</code> attribute.
      *
      * @param gestureId The unique id of the performed gesture.
      *
+     * @return Whether the gesture was handled.
+     *
      * @see #GESTURE_SWIPE_UP
-     * @see #GESTURE_SWIPE_DOWN
-     * @see #GESTURE_SWIPE_LEFT
-     * @see #GESTURE_SWIPE_RIGHT
+     * @see #GESTURE_SWIPE_UP_AND_LEFT
      * @see #GESTURE_SWIPE_UP_AND_DOWN
+     * @see #GESTURE_SWIPE_UP_AND_RIGHT
+     * @see #GESTURE_SWIPE_DOWN
+     * @see #GESTURE_SWIPE_DOWN_AND_LEFT
      * @see #GESTURE_SWIPE_DOWN_AND_UP
+     * @see #GESTURE_SWIPE_DOWN_AND_RIGHT
+     * @see #GESTURE_SWIPE_LEFT
+     * @see #GESTURE_SWIPE_LEFT_AND_UP
      * @see #GESTURE_SWIPE_LEFT_AND_RIGHT
+     * @see #GESTURE_SWIPE_LEFT_AND_DOWN
+     * @see #GESTURE_SWIPE_RIGHT
+     * @see #GESTURE_SWIPE_RIGHT_AND_UP
      * @see #GESTURE_SWIPE_RIGHT_AND_LEFT
+     * @see #GESTURE_SWIPE_RIGHT_AND_DOWN
      * @see #GESTURE_CLOCKWISE_CIRCLE
      * @see #GESTURE_COUNTER_CLOCKWISE_CIRCLE
+     * @see #GESTURE_TWO_FINGER_TAP
+     * @see #GESTURE_TWO_FINGER_LONG_PRESS
      */
-    protected void onGesture(int gestureId) {
+    protected boolean onGesture(int gestureId) {
         // TODO: Describe the default gesture processing in the javaDoc once it is finalized.
-
-        // Global actions.
-        switch (gestureId) {
-            case GESTURE_SWIPE_DOWN_AND_LEFT: {
-                performGlobalAction(GLOBAL_ACTION_BACK);
-            } return;
-            case GESTURE_SWIPE_DOWN_AND_RIGHT: {
-                performGlobalAction(GLOBAL_ACTION_HOME);
-            } return;
-            case GESTURE_SWIPE_UP_AND_LEFT: {
-                performGlobalAction(GLOBAL_ACTION_RECENTS);
-            } return;
-            case GESTURE_SWIPE_UP_AND_RIGHT: {
-                performGlobalAction(GLOBAL_ACTION_NOTIFICATIONS);
-            } return;
-        }
-
-        // Cache the id to avoid locking
-        final int connectionId = mConnectionId;
-        if (connectionId == UNDEFINED) {
-            throw new IllegalStateException("AccessibilityService not connected."
-                    + " Did you receive a call of onServiceConnected()?");
-        }
-        AccessibilityNodeInfo root = getRootInActiveWindow();
-        if (root == null) {
-            return;
-        }
-
-        AccessibilityNodeInfo current = root.findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY);
-        if (current == null) {
-            current = root;
-        }
-
-        // Local actions.
-        AccessibilityNodeInfo next = null;
-        switch (gestureId) {
-            case GESTURE_SWIPE_UP: {
-                next = current.focusSearch(View.ACCESSIBILITY_FOCUS_OUT);
-            } break;
-            case GESTURE_SWIPE_DOWN: {
-                next = current.focusSearch(View.ACCESSIBILITY_FOCUS_IN);
-            } break;
-            case GESTURE_SWIPE_LEFT: {
-                if (mLayoutDirection == View.LAYOUT_DIRECTION_LTR) {
-                    next = current.focusSearch(View.ACCESSIBILITY_FOCUS_BACKWARD);
-                } else { // LAYOUT_DIRECTION_RTL
-                    next = current.focusSearch(View.ACCESSIBILITY_FOCUS_FORWARD);
-                }
-            } break;
-            case GESTURE_SWIPE_RIGHT: {
-                if (mLayoutDirection == View.LAYOUT_DIRECTION_LTR) {
-                    next = current.focusSearch(View.ACCESSIBILITY_FOCUS_FORWARD);
-                } else { // LAYOUT_DIRECTION_RTL
-                    next = current.focusSearch(View.ACCESSIBILITY_FOCUS_BACKWARD);
-                }
-            } break;
-            case GESTURE_SWIPE_UP_AND_DOWN: {
-                next = current.focusSearch(View.ACCESSIBILITY_FOCUS_UP);
-            } break;
-            case GESTURE_SWIPE_DOWN_AND_UP: {
-                next = current.focusSearch(View.ACCESSIBILITY_FOCUS_DOWN);
-            } break;
-            case GESTURE_SWIPE_LEFT_AND_RIGHT: {
-                next = current.focusSearch(View.ACCESSIBILITY_FOCUS_LEFT);
-            } break;
-            case GESTURE_SWIPE_RIGHT_AND_LEFT: {
-                next = current.focusSearch(View.ACCESSIBILITY_FOCUS_RIGHT);
-            } break;
-        }
-        if (next != null && !next.equals(current)) {
-            next.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
-        }
+        return false;
     }
 
     /**
@@ -484,10 +433,7 @@
      * @return The root node if this service can retrieve window content.
      */
     public AccessibilityNodeInfo getRootInActiveWindow() {
-        return AccessibilityInteractionClient.getInstance()
-            .findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
-                AccessibilityNodeInfo.ACTIVE_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID,
-                AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS);
+        return AccessibilityInteractionClient.getInstance().getRootInActiveWindow(mConnectionId);
     }
 
     /**
@@ -509,7 +455,7 @@
             AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
         if (connection != null) {
             try {
-                return connection.perfromGlobalAction(action);
+                return connection.performGlobalAction(action);
             } catch (RemoteException re) {
                 Log.w(LOG_TAG, "Error while calling performGlobalAction", re);
             }
@@ -572,18 +518,6 @@
         }
     }
 
-    @Override
-    public void onCreate() {
-        Locale locale = getResources().getConfiguration().locale;
-        mLayoutDirection = LocaleUtil.getLayoutDirectionFromLocale(locale);
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration configuration) {
-        super.onConfigurationChanged(configuration);
-        mLayoutDirection = LocaleUtil.getLayoutDirectionFromLocale(configuration.locale);
-    }
-
     /**
      * Implement to return the implementation of the internal accessibility
      * service interface.
@@ -612,8 +546,8 @@
             }
 
             @Override
-            public void onGesture(int gestureId) {
-                AccessibilityService.this.onGesture(gestureId);
+            public boolean onGesture(int gestureId) {
+                return AccessibilityService.this.onGesture(gestureId);
             }
         });
     }
@@ -658,8 +592,10 @@
             mCaller.sendMessage(message);
         }
 
-        public void onGesture(int gestureId) {
-            Message message = mCaller.obtainMessageI(DO_ON_GESTURE, gestureId);
+        public void onGesture(int gestureId, IAccessibilityServiceClientCallback callback,
+                int interactionId) {
+            Message message = mCaller.obtainMessageIIO(DO_ON_GESTURE, gestureId, interactionId,
+                    callback);
             mCaller.sendMessage(message);
         }
 
@@ -692,7 +628,15 @@
                     return;
                 case DO_ON_GESTURE :
                     final int gestureId = message.arg1;
-                    mCallback.onGesture(gestureId);
+                    final int interactionId = message.arg2;
+                    IAccessibilityServiceClientCallback callback =
+                        (IAccessibilityServiceClientCallback) message.obj;
+                    final boolean handled = mCallback.onGesture(gestureId);
+                    try {
+                        callback.setGestureResult(gestureId, handled, interactionId);
+                    } catch (RemoteException re) {
+                        Log.e(LOG_TAG, "Error calling back with the gesture resut.", re);
+                    }
                     return;
                 default :
                     Log.w(LOG_TAG, "Unknown message type " + message.what);
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index e77ed9a..7e6786b 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -224,6 +224,11 @@
     private boolean mCanRetrieveWindowContent;
 
     /**
+     * Flag whether this accessibility service can handle gestures.
+     */
+    private boolean mCanHandleGestures;
+
+    /**
      * Resource id of the description of the accessibility service.
      */
     private int mDescriptionResId;
@@ -303,6 +308,8 @@
             mCanRetrieveWindowContent = asAttributes.getBoolean(
                     com.android.internal.R.styleable.AccessibilityService_canRetrieveWindowContent,
                     false);
+            mCanHandleGestures = asAttributes.getBoolean(
+                    com.android.internal.R.styleable.AccessibilityService_canHandleGestures, false);
             TypedValue peekedValue = asAttributes.peekValue(
                     com.android.internal.R.styleable.AccessibilityService_description);
             if (peekedValue != null) {
@@ -378,13 +385,25 @@
      *    <strong>Statically set from
      *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
      * </p>
-     * @return True window content can be retrieved.
+     * @return True if window content can be retrieved.
      */
     public boolean getCanRetrieveWindowContent() {
         return mCanRetrieveWindowContent;
     }
 
     /**
+     * Whether this service can handle gestures.
+     * <p>
+     *    <strong>Statically set from
+     *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
+     * </p>
+     * @return True if the service can handle gestures.
+     */
+    public boolean getCanHandleGestures() {
+        return mCanHandleGestures;
+    }
+
+    /**
      * Gets the non-localized description of the accessibility service.
      * <p>
      *    <strong>Statically set from
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index 588728c..0257aa4 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -16,6 +16,7 @@
 
 package android.accessibilityservice;
 
+import android.accessibilityservice.IAccessibilityServiceClientCallback;
 import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.view.accessibility.AccessibilityEvent;
 
@@ -32,5 +33,5 @@
 
     void onInterrupt();
 
-    void onGesture(int gestureId);
+    void onGesture(int gesture, in IAccessibilityServiceClientCallback callback, int interactionId);
 }
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClientCallback.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClientCallback.aidl
new file mode 100644
index 0000000..9061398
--- /dev/null
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClientCallback.aidl
@@ -0,0 +1,30 @@
+/*
+** Copyright 2012, 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 android.accessibilityservice;
+
+import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.view.accessibility.AccessibilityEvent;
+
+/**
+ * Callback for IAccessibilityServiceClient.
+ *
+ * @hide
+ */
+ oneway interface IAccessibilityServiceClientCallback {
+
+    void setGestureResult(int gestureId, boolean handled, int interactionId);
+}
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 1bd5387..6e85665 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -167,5 +167,5 @@
      * @param action The action to perform.
      * @return Whether the action was performed.
      */
-    boolean perfromGlobalAction(int action);
+    boolean performGlobalAction(int action);
 }
diff --git a/core/java/android/accessibilityservice/UiTestAutomationBridge.java b/core/java/android/accessibilityservice/UiTestAutomationBridge.java
index c840bd6..1697df0 100644
--- a/core/java/android/accessibilityservice/UiTestAutomationBridge.java
+++ b/core/java/android/accessibilityservice/UiTestAutomationBridge.java
@@ -177,8 +177,8 @@
             }
 
             @Override
-            public void onGesture(int gestureId) {
-                /* do nothing */
+            public boolean onGesture(int gestureId) {
+                return false;
             }
         });
 
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 35f0d9d..f73faf3 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -84,7 +84,7 @@
 
     private final Object mInstanceLock = new Object();
 
-    private int mInteractionId = -1;
+    private volatile int mInteractionId = -1;
 
     private AccessibilityNodeInfo mFindAccessibilityNodeInfoResult;
 
@@ -150,6 +150,18 @@
     }
 
     /**
+     * Gets the root {@link AccessibilityNodeInfo} in the currently active window.
+     *
+     * @param connectionId The id of a connection for interacting with the system.
+     * @return The root {@link AccessibilityNodeInfo} if found, null otherwise.
+     */
+    public AccessibilityNodeInfo getRootInActiveWindow(int connectionId) {
+        return findAccessibilityNodeInfoByAccessibilityId(connectionId,
+                AccessibilityNodeInfo.ACTIVE_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID,
+                AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS);
+    }
+
+    /**
      * Finds an {@link AccessibilityNodeInfo} by accessibility id.
      *
      * @param connectionId The id of a connection for interacting with the system.
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 6f489d4..aa47993 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2470,6 +2470,8 @@
         <!-- Flag whether the accessibility service wants to be able to retrieve the
              active window content. This setting cannot be changed at runtime. -->
         <attr name="canRetrieveWindowContent" format="boolean" />
+        <!-- Flag whether the accessibility service can handle gesrures and wants such. -->
+        <attr name="canHandleGestures" format="boolean" />
         <!-- Short description of the accessibility serivce purpose or behavior.-->
         <attr name="description" />
     </declare-styleable>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 03ba08c..3f8036b 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3599,5 +3599,6 @@
   <public type="attr" name="parentActivityName" />
 
   <public type="attr" name="importantForAccessibility"/>
+  <public type="attr" name="canHandleGestures"/>
 
 </resources>
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index 6675816..7361062 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -23,6 +23,7 @@
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.IAccessibilityServiceClient;
+import android.accessibilityservice.IAccessibilityServiceClientCallback;
 import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -42,6 +43,7 @@
 import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -55,7 +57,9 @@
 import android.view.InputDevice;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
+import android.view.View;
 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;
@@ -64,6 +68,8 @@
 import android.view.accessibility.IAccessibilityManagerClient;
 
 import com.android.internal.content.PackageMonitor;
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.HandlerCaller.Callback;
 import com.android.server.accessibility.TouchExplorer.GestureListener;
 import com.android.server.wm.WindowManagerService;
 
@@ -78,6 +84,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * This class is instantiated by the system as a system level service and can be
@@ -97,10 +104,12 @@
     private static final String FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE =
         "registerUiTestAutomationService";
 
-    private static int sIdCounter = 0;
-
     private static final int OWN_PROCESS_ID = android.os.Process.myPid();
 
+    private static final int UNDEFINED = -1;
+
+    private static int sIdCounter = 0;
+
     private static int sNextWindowId;
 
     final Context mContext;
@@ -145,6 +154,10 @@
 
     private Service mUiAutomationService;
 
+    private GestureHandler mGestureHandler;
+
+    private int mDefaultGestureHandlingHelperServiceId = UNDEFINED;
+
     /**
      * Handler for delayed event dispatch.
      */
@@ -471,26 +484,60 @@
     }
 
     @Override
-    public void onGesture(int gestureId) {
+    public boolean onGesture(int gestureId) {
+        // Lazily instantiate the gesture handler.
+        if (mGestureHandler == null) {
+            mGestureHandler = new GestureHandler();
+        }
         synchronized (mLock) {
-            final boolean dispatched = notifyGestureLocked(gestureId, false);
-            if (!dispatched) {
-                notifyGestureLocked(gestureId, true);
+            boolean handled = notifyGestureLocked(gestureId, false);
+            if (!handled) {
+                handled = notifyGestureLocked(gestureId, true);
             }
+            if (!handled) {
+                mGestureHandler.scheduleHandleGestureDefault(gestureId);
+            }
+            return handled;
+        }
+    }
+
+    private Service getDefaultGestureHandlingHelperService() {
+        // Since querying of screen content is done through the
+        // AccessibilityInteractionClient which talks to an
+        // IAccessibilityServiceConnection implementation we create a proxy
+        // Service when necessary to enable interaction with the remote
+        // view tree. Note that this service is just a stateless proxy
+        // that does not get any events or interrupts.
+        if (mDefaultGestureHandlingHelperServiceId == UNDEFINED) {
+            ComponentName name = new ComponentName("android",
+                    "DefaultGestureHandlingHelperService");
+            AccessibilityServiceInfo info = new AccessibilityServiceInfo();
+            Service service = new Service(name, info, true);
+            mDefaultGestureHandlingHelperServiceId = service.mId;
+            AccessibilityInteractionClient.getInstance().addConnection(
+                    mDefaultGestureHandlingHelperServiceId, service);
+            return service;
+        } else {
+            return (Service) AccessibilityInteractionClient.getInstance()
+                .getConnection(mDefaultGestureHandlingHelperServiceId);
         }
     }
 
     private boolean notifyGestureLocked(int gestureId, boolean isDefault) {
-        final int serviceCount = mServices.size();
-        for (int i = 0; i < serviceCount; i++) {
+        // TODO: Now we are giving the gestures to the last enabled
+        //       service that can handle them which is the last one
+        //       in our list since we write the last enabled as the
+        //       last record in the enabled services setting. Ideally,
+        //       the user should make the call which service handles
+        //       gestures. However, only one service should handle
+        //       gestrues to avoid user frustration when different
+        //       bahiour is observed from different combinations of
+        //       enabled accessibility services.
+        for (int i = mServices.size() - 1; i >= 0; i--) {
             Service service = mServices.get(i);
-            if (service.mIsDefault == isDefault) {
-                try {
-                    service.mServiceInterface.onGesture(gestureId);
-                    return true;
-                } catch (RemoteException re) {
-                    Slog.e(LOG_TAG, "Error dispatching gesture.");
-                }
+            if (service.mCanHandleGestures && service.mIsDefault == isDefault) {
+                mGestureHandler.scheduleHandleGesture(gestureId, service.mServiceInterface);
+                return true;
             }
         }
         return false;
@@ -935,6 +982,210 @@
         }
     }
 
+    class GestureHandler extends IAccessibilityServiceClientCallback.Stub
+            implements Runnable, Callback {
+
+        private static final String THREAD_NAME = "AccessibilityGestureHandler";
+
+        private static final long TIMEOUT_INTERACTION_MILLIS = 5000;
+
+        private static final int MSG_HANDLE_GESTURE = 1;
+
+        private static final int MSG_HANDLE_GESTURE_DEFAULT = 2;
+
+        private final AtomicInteger mInteractionCounter = new AtomicInteger();
+
+        private final Object mGestureLock = new Object();
+
+        private HandlerCaller mHandlerCaller;
+
+        private volatile int mInteractionId = -1;
+
+        private volatile boolean mGestureResult;
+
+        public GestureHandler() {
+            synchronized (mGestureLock) {
+                Thread worker = new Thread(this, THREAD_NAME);
+                worker.start();
+                try {
+                    mGestureLock.wait();
+                } catch (InterruptedException ie) {
+                    /*  ignore */
+                }
+            }
+        }
+
+        @Override
+        public void run() {
+            Looper.prepare();
+            synchronized (mGestureLock) {
+                mHandlerCaller = new HandlerCaller(mContext, Looper.myLooper(), this);
+                mGestureLock.notifyAll();
+            }
+            Looper.loop();
+        }
+
+        @Override
+        public void setGestureResult(int gestureId, boolean handled, int interactionId) {
+            synchronized (mGestureLock) {
+                if (interactionId > mInteractionId) {
+                    mGestureResult = handled;
+                    mInteractionId = interactionId;
+                }
+                mGestureLock.notifyAll();
+            }
+        }
+
+        @Override
+        public void executeMessage(Message message) {
+            final int type = message.what;
+            switch (type) {
+                case MSG_HANDLE_GESTURE: {
+                    IAccessibilityServiceClient service =
+                        (IAccessibilityServiceClient) message.obj;
+                    final int gestureId = message.arg1;
+                    final int interactionId = message.arg1;
+
+                    try {
+                        service.onGesture(gestureId, this, interactionId);
+                    } catch (RemoteException re) {
+                        Slog.e(LOG_TAG, "Error dispatching a gesture to a client.", re);
+                        return;
+                    }
+
+                    long waitTimeMillis = 0;
+                    final long startTimeMillis = SystemClock.uptimeMillis();
+                    synchronized (mGestureLock) {
+                        while (true) {
+                            try {
+                                // Did we get the expected callback?
+                                if (mInteractionId == interactionId) {
+                                    break;
+                                }
+                                // Did we get an obsolete callback?
+                                if (mInteractionId > interactionId) {
+                                    break;
+                                }
+                                // Did we time out?
+                                final long elapsedTimeMillis =
+                                    SystemClock.uptimeMillis() - startTimeMillis;
+                                waitTimeMillis = TIMEOUT_INTERACTION_MILLIS - elapsedTimeMillis;
+                                if (waitTimeMillis <= 0) {
+                                    break;
+                                }
+                                mGestureLock.wait(waitTimeMillis);
+                            } catch (InterruptedException ie) {
+                                /* ignore */
+                            }
+                        }
+                        handleGestureIfNeededAndResetLocked(gestureId);
+                    }
+                } break;
+                case MSG_HANDLE_GESTURE_DEFAULT: {
+                    final int gestureId = message.arg1;
+                    handleGestureDefault(gestureId);
+                } break;
+                default: {
+                    throw new IllegalArgumentException("Unknown message type: " + type);
+                }
+            }
+        }
+
+        private void handleGestureIfNeededAndResetLocked(int gestureId) {
+            if (!mGestureResult) {
+                handleGestureDefault(gestureId);
+            }
+            mGestureResult = false;
+            mInteractionId = -1;
+        }
+
+        public void scheduleHandleGesture(int gestureId, IAccessibilityServiceClient service) {
+            final int interactionId = mInteractionCounter.incrementAndGet();
+            mHandlerCaller.obtainMessageIIO(MSG_HANDLE_GESTURE, gestureId, interactionId,
+                    service).sendToTarget();
+        }
+
+        public void scheduleHandleGestureDefault(int gestureId) {
+            final int interactionId = mInteractionCounter.incrementAndGet();
+            mHandlerCaller.obtainMessageI(MSG_HANDLE_GESTURE_DEFAULT, gestureId).sendToTarget();
+        }
+
+        private void handleGestureDefault(int gestureId) {
+            Service service = getDefaultGestureHandlingHelperService();
+
+            // Global actions.
+            switch (gestureId) {
+                case AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT: {
+                    service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
+                } return;
+                case AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT: {
+                    service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME);
+                } return;
+                case AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT: {
+                    service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_RECENTS);
+                } return;
+                case AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT: {
+                    service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS);
+                } return;
+            }
+
+            AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+
+            AccessibilityNodeInfo root = client.getRootInActiveWindow(service.mId);
+            if (root == null) {
+                return;
+            }
+
+            AccessibilityNodeInfo current = root.findFocus(
+                    AccessibilityNodeInfo.FOCUS_ACCESSIBILITY);
+            if (current == null) {
+                current = root;
+            }
+
+            // Local actions.
+            AccessibilityNodeInfo next = null;
+            switch (gestureId) {
+                case AccessibilityService.GESTURE_SWIPE_UP: {
+                    next = current.focusSearch(View.ACCESSIBILITY_FOCUS_OUT);
+                } break;
+                case AccessibilityService.GESTURE_SWIPE_DOWN: {
+                    next = current.focusSearch(View.ACCESSIBILITY_FOCUS_IN);
+                } break;
+                case AccessibilityService.GESTURE_SWIPE_LEFT: {
+                    // TODO: Implement the RTL support.
+//                     if (mLayoutDirection == View.LAYOUT_DIRECTION_LTR) {
+                        next = current.focusSearch(View.ACCESSIBILITY_FOCUS_BACKWARD);
+//                     } else { // LAYOUT_DIRECTION_RTL
+//                        next = current.focusSearch(View.ACCESSIBILITY_FOCUS_FORWARD);
+//                     }
+                } break;
+                case AccessibilityService.GESTURE_SWIPE_RIGHT: {
+                    // TODO: Implement the RTL support.
+//                    if (mLayoutDirection == View.LAYOUT_DIRECTION_LTR) {
+                        next = current.focusSearch(View.ACCESSIBILITY_FOCUS_FORWARD);
+//                    } else { // LAYOUT_DIRECTION_RTL
+//                        next = current.focusSearch(View.ACCESSIBILITY_FOCUS_BACKWARD);
+//                    }
+                } break;
+                case AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN: {
+                    next = current.focusSearch(View.ACCESSIBILITY_FOCUS_UP);
+                } break;
+                case AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP: {
+                    next = current.focusSearch(View.ACCESSIBILITY_FOCUS_DOWN);
+                } break;
+                case AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT: {
+                    next = current.focusSearch(View.ACCESSIBILITY_FOCUS_LEFT);
+                } break;
+                case AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT: {
+                    next = current.focusSearch(View.ACCESSIBILITY_FOCUS_RIGHT);
+                } break;
+            }
+            if (next != null && !next.equals(current)) {
+                next.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+            }
+        }
+    }
+
     /**
      * This class represents an accessibility service. It stores all per service
      * data required for the service management, provides API for starting/stopping the
@@ -971,6 +1222,8 @@
 
         boolean mCanRetrieveScreenContent;
 
+        boolean mCanHandleGestures;
+
         boolean mIsAutomation;
 
         final Rect mTempBounds = new Rect();
@@ -987,6 +1240,7 @@
             mIsAutomation = isAutomation;
             if (!isAutomation) {
                 mCanRetrieveScreenContent = accessibilityServiceInfo.getCanRetrieveWindowContent();
+                mCanHandleGestures = accessibilityServiceInfo.getCanHandleGestures();
                 mIntent = new Intent().setComponent(mComponentName);
                 mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
                         com.android.internal.R.string.accessibility_binding_label);
@@ -995,6 +1249,7 @@
             } else {
                 mCanRetrieveScreenContent = true;
                 mIncludeNotImportantViews = true;
+                mCanHandleGestures = true;
             }
             setDynamicallyConfigurableProperties(accessibilityServiceInfo);
         }
@@ -1323,7 +1578,7 @@
             return true;
         }
 
-        public boolean perfromGlobalAction(int action) {
+        public boolean performGlobalAction(int action) {
             switch (action) {
                 case AccessibilityService.GLOBAL_ACTION_BACK: {
                     sendDownAndUpKeyEvents(KeyEvent.KEYCODE_BACK);
diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java
index 5d01c77..39012e6 100644
--- a/services/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/java/com/android/server/accessibility/TouchExplorer.java
@@ -152,7 +152,7 @@
          *
          * @param gestureId The gesture id.
          */
-        public void onGesture(int gestureId);
+        public boolean onGesture(int gestureId);
     }
 
     /**