Merge "Adding some more gestures and actions for accessibility."
diff --git a/api/current.txt b/api/current.txt
index fef1c83..d8387c1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1999,17 +1999,30 @@
     method protected void onGesture(int);
     method public abstract void onInterrupt();
     method protected void onServiceConnected();
+    method public final boolean performGlobalAction(int);
     method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo);
     field public static final int GESTURE_CLOCKWISE_CIRCLE = 9; // 0x9
     field public static final int GESTURE_COUNTER_CLOCKWISE_CIRCLE = 10; // 0xa
     field public static final int GESTURE_SWIPE_DOWN = 2; // 0x2
+    field public static final int GESTURE_SWIPE_DOWN_AND_LEFT = 17; // 0x11
+    field public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 18; // 0x12
     field public static final int GESTURE_SWIPE_DOWN_AND_UP = 8; // 0x8
     field public static final int GESTURE_SWIPE_LEFT = 3; // 0x3
+    field public static final int GESTURE_SWIPE_LEFT_AND_DOWN = 12; // 0xc
     field public static final int GESTURE_SWIPE_LEFT_AND_RIGHT = 5; // 0x5
+    field public static final int GESTURE_SWIPE_LEFT_AND_UP = 11; // 0xb
     field public static final int GESTURE_SWIPE_RIGHT = 4; // 0x4
+    field public static final int GESTURE_SWIPE_RIGHT_AND_DOWN = 14; // 0xe
     field public static final int GESTURE_SWIPE_RIGHT_AND_LEFT = 6; // 0x6
+    field public static final int GESTURE_SWIPE_RIGHT_AND_UP = 13; // 0xd
     field public static final int GESTURE_SWIPE_UP = 1; // 0x1
     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 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
+    field public static final int GLOBAL_ACTION_RECENTS = 3; // 0x3
     field public static final java.lang.String SERVICE_INTERFACE = "android.accessibilityservice.AccessibilityService";
     field public static final java.lang.String SERVICE_META_DATA = "android.accessibilityservice";
   }
@@ -25006,12 +25019,13 @@
     method public void setSource(android.view.View, int);
     method public void setText(java.lang.CharSequence);
     method public void writeToParcel(android.os.Parcel, int);
-    field public static final int ACTION_ACCESSIBILITY_FOCUS = 16; // 0x10
-    field public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 32; // 0x20
+    field public static final int ACTION_ACCESSIBILITY_FOCUS = 64; // 0x40
+    field public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 128; // 0x80
     field public static final int ACTION_CLEAR_FOCUS = 2; // 0x2
     field public static final int ACTION_CLEAR_SELECTION = 8; // 0x8
-    field public static final int ACTION_CLICK = 64; // 0x40
+    field public static final int ACTION_CLICK = 16; // 0x10
     field public static final int ACTION_FOCUS = 1; // 0x1
+    field public static final int ACTION_LONG_CLICK = 32; // 0x20
     field public static final int ACTION_SELECT = 4; // 0x4
     field public static final android.os.Parcelable.Creator CREATOR;
     field public static final int FOCUS_ACCESSIBILITY = 2; // 0x2
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 3da35d3..eed8aa6 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -259,13 +259,51 @@
     public static final int GESTURE_COUNTER_CLOCKWISE_CIRCLE = 10;
 
     /**
+     * The user has performed a left and up gesture on the touch screen.
+     */
+    public static final int GESTURE_SWIPE_LEFT_AND_UP = 11;
+
+    /**
+     * The user has performed a left and down gesture on the touch screen.
+     */
+    public static final int GESTURE_SWIPE_LEFT_AND_DOWN = 12;
+
+    /**
+     * The user has performed a right and up gesture on the touch screen.
+     */
+    public static final int GESTURE_SWIPE_RIGHT_AND_UP = 13;
+
+    /**
+     * The user has performed a right and down gesture on the touch screen.
+     */
+    public static final int GESTURE_SWIPE_RIGHT_AND_DOWN = 14;
+
+    /**
+     * The user has performed an up and left gesture on the touch screen.
+     */
+    public static final int GESTURE_SWIPE_UP_AND_LEFT = 15;
+
+    /**
+     * The user has performed an up and right gesture on the touch screen.
+     */
+    public static final int GESTURE_SWIPE_UP_AND_RIGHT = 16;
+
+    /**
+     * The user has performed an down and left gesture on the touch screen.
+     */
+    public static final int GESTURE_SWIPE_DOWN_AND_LEFT = 17;
+
+    /**
+     * The user has performed an down and right gesture on the touch screen.
+     */
+    public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 18;
+
+    /**
      * The {@link Intent} that must be declared as handled by the service.
      */
     public static final String SERVICE_INTERFACE =
         "android.accessibilityservice.AccessibilityService";
 
-    private static final int UNDEFINED = -1;
-
     /**
      * Name under which an AccessibilityService component publishes information
      * about itself. This meta-data must reference an XML resource containing an
@@ -284,6 +322,28 @@
      */
     public static final String SERVICE_META_DATA = "android.accessibilityservice";
 
+    /**
+     * Action to go back.
+     */
+    public static final int GLOBAL_ACTION_BACK = 1;
+
+    /**
+     * Action to go home.
+     */
+    public static final int GLOBAL_ACTION_HOME = 2;
+
+    /**
+     * Action to open the recents.
+     */
+    public static final int GLOBAL_ACTION_RECENTS = 3;
+
+    /**
+     * Action to open the notifications.
+     */
+    public static final int GLOBAL_ACTION_NOTIFICATIONS = 4;
+
+    private static final int UNDEFINED = -1;
+
     private static final String LOG_TAG = "AccessibilityService";
 
     interface Callbacks {
@@ -344,6 +404,22 @@
     protected void 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) {
@@ -357,10 +433,12 @@
         if (root == null) {
             return;
         }
-        AccessibilityNodeInfo current = root.findFocus(View.FOCUS_ACCESSIBILITY);
+        AccessibilityNodeInfo current = root.findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY);
         if (current == null) {
             current = root;
         }
+
+        // Local actions.
         AccessibilityNodeInfo next = null;
         switch (gestureId) {
             case GESTURE_SWIPE_UP: {
@@ -402,6 +480,33 @@
     }
 
     /**
+     * Performs a global action. Such an action can be performed
+     * at any moment regardless of the current application or user
+     * location in that application. For example going back, going
+     * home, opening recents, etc.
+     *
+     * @param action The action to perform.
+     * @return Whether the action was successfully performed.
+     *
+     * @see #GLOBAL_ACTION_BACK
+     * @see #GLOBAL_ACTION_HOME
+     * @see #GLOBAL_ACTION_NOTIFICATIONS
+     * @see #GLOBAL_ACTION_RECENTS
+     */
+    public final boolean performGlobalAction(int action) {
+        IAccessibilityServiceConnection connection =
+            AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
+        if (connection != null) {
+            try {
+                return connection.perfromGlobalAction(action);
+            } catch (RemoteException re) {
+                Log.w(LOG_TAG, "Error while calling performGlobalAction", re);
+            }
+        }
+        return false;
+    }
+
+    /**
      * Gets the an {@link AccessibilityServiceInfo} describing this
      * {@link AccessibilityService}. This method is useful if one wants
      * to change some of the dynamically configurable properties at
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 30da9db..1bd5387 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -160,4 +160,12 @@
      * @return The associated accessibility service info.
      */
     AccessibilityServiceInfo getServiceInfo();
+
+    /**
+     * Performs a global action, such as going home, going back, etc.
+     *
+     * @param action The action to perform.
+     * @return Whether the action was performed.
+     */
+    boolean perfromGlobalAction(int action);
 }
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 6c1a6bf..54c62ee 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -52,13 +52,16 @@
     private ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
         new ArrayList<AccessibilityNodeInfo>();
 
-    private final Handler mHandler = new PrivateHandler();
+    private final Handler mHandler;
 
     private final ViewRootImpl mViewRootImpl;
 
     private final AccessibilityNodePrefetcher mPrefetcher;
 
     public AccessibilityInteractionController(ViewRootImpl viewRootImpl) {
+        // mView is never null - the caller has already checked.
+        Looper looper = viewRootImpl.mView.mContext.getMainLooper();
+        mHandler = new PrivateHandler(looper);
         mViewRootImpl = viewRootImpl;
         mPrefetcher = new AccessibilityNodePrefetcher();
     }
@@ -846,8 +849,8 @@
         private final static int MSG_FIND_FOCUS = 5;
         private final static int MSG_FOCUS_SEARCH = 6;
 
-        public PrivateHandler() {
-            super(Looper.getMainLooper());
+        public PrivateHandler(Looper looper) {
+            super(looper);
         }
 
         @Override
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 0ded5f9..2fea8ec 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -6359,16 +6359,14 @@
     public boolean performAccessibilityAction(int action) {
         switch (action) {
             case AccessibilityNodeInfo.ACTION_CLICK: {
-                final long now = SystemClock.uptimeMillis();
-                // Send down.
-                MotionEvent event = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN,
-                        getWidth() / 2, getHeight() / 2, 0);
-                onTouchEvent(event);
-                // Send up.
-                event.setAction(MotionEvent.ACTION_UP);
-                onTouchEvent(event);
-                // Clean up.
-                event.recycle();
+                if (isClickable()) {
+                    performClick();
+                }
+            } break;
+            case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
+                if (isLongClickable()) {
+                    performLongClick();
+                }
             } break;
             case AccessibilityNodeInfo.ACTION_FOCUS: {
                 if (!hasFocus()) {
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 6371963..91e945b 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -5792,11 +5792,13 @@
                 throw new IllegalStateException("Instance already recycled.");
             }
             clear();
-            if (sPoolSize < MAX_POOL_SIZE) {
-                mNext = sPool;
-                mIsPooled = true;
-                sPool = this;
-                sPoolSize++;
+            synchronized (sPoolLock) {
+                if (sPoolSize < MAX_POOL_SIZE) {
+                    mNext = sPool;
+                    mIsPooled = true;
+                    sPool = this;
+                    sPoolSize++;
+                }
             }
         }
 
@@ -5889,11 +5891,13 @@
                 throw new IllegalStateException("Instance already recycled.");
             }
             clear();
-            if (sPoolSize < MAX_POOL_SIZE) {
-                mNext = sPool;
-                mIsPooled = true;
-                sPool = this;
-                sPoolSize++;
+            synchronized (sPoolLock) {
+                if (sPoolSize < MAX_POOL_SIZE) {
+                    mNext = sPool;
+                    mIsPooled = true;
+                    sPool = this;
+                    sPoolSize++;
+                }
             }
         }
 
@@ -5943,9 +5947,9 @@
             if (widthDiference != 0) {
                 return -widthDiference;
             }
-            // Return nondeterministically one of them since we do
-            // not want to ignore any views.
-            return 1;
+            // Just break the tie somehow. The accessibliity ids are unique
+            // and stable, hence this is deterministic tie breaking.
+            return mView.getAccessibilityViewId() - another.mView.getAccessibilityViewId();
         }
 
         private void init(ViewGroup root, View view) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1472993..3d40b2f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2665,6 +2665,7 @@
     private final static int MSG_PROCESS_INPUT_EVENTS = 19;
     private final static int MSG_DISPATCH_SCREEN_STATE = 20;
     private final static int MSG_INVALIDATE_DISPLAY_LIST = 21;
+    private final static int MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST = 22;
 
     final class ViewRootHandler extends Handler {
         @Override
@@ -2712,6 +2713,8 @@
                     return "MSG_DISPATCH_SCREEN_STATE";
                 case MSG_INVALIDATE_DISPLAY_LIST:
                     return "MSG_INVALIDATE_DISPLAY_LIST";
+                case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST:
+                    return "MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST";
             }
             return super.getMessageName(message);
         }
@@ -2921,6 +2924,9 @@
             case MSG_INVALIDATE_DISPLAY_LIST: {
                 invalidateDisplayLists();
             } break;
+            case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST: {
+                setAccessibilityFocusedHost(null);
+            } break;
             }
         }
     }
@@ -5066,7 +5072,7 @@
                 }
             } else {
                 ensureNoConnection();
-                setAccessibilityFocusedHost(null);
+                mHandler.obtainMessage(MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST).sendToTarget();
             }
         }
 
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 1071c65..c5f2062 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -84,7 +84,7 @@
     /**
      * Action that gives input focus to the node.
      */
-    public static final int ACTION_FOCUS = 0x00000001;
+    public static final int ACTION_FOCUS =  0x00000001;
 
     /**
      * Action that clears input focus of the node.
@@ -102,19 +102,24 @@
     public static final int ACTION_CLEAR_SELECTION = 0x00000008;
 
     /**
+     * Action that clicks on the node info.
+     */
+    public static final int ACTION_CLICK = 0x00000010;
+
+    /**
+     * Action that clicks on the node.
+     */
+    public static final int ACTION_LONG_CLICK = 0x00000020;
+
+    /**
      * Action that gives accessibility focus to the node.
      */
-    public static final int ACTION_ACCESSIBILITY_FOCUS = 0x00000010;
+    public static final int ACTION_ACCESSIBILITY_FOCUS = 0x00000040;
 
     /**
      * Action that clears accessibility focus of the node.
      */
-    public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 0x00000020;
-
-    /**
-     * Action that clicks on the node info./AccessibilityNodeInfoCache.java
-     */
-    public static final int ACTION_CLICK = 0x00000040;
+    public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 0x00000080;
 
     /**
      * The input focus.
@@ -278,9 +283,9 @@
             (root != null) ? root.getAccessibilityViewId() : UNDEFINED;
         mSourceNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
     }
-    
+
     /**
-     * Find the view that has the input focus. The search starts from
+     * Find the view that has the specified focus type. The search starts from
      * the view represented by this node info.
      *
      * @param focus The focus to find. One of {@link #FOCUS_INPUT} or
diff --git a/core/res/res/raw/accessibility_gestures.bin b/core/res/res/raw/accessibility_gestures.bin
index 1f95e56..f7e6615 100644
--- a/core/res/res/raw/accessibility_gestures.bin
+++ b/core/res/res/raw/accessibility_gestures.bin
Binary files differ
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index ed2a6c0..6675816 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -36,6 +36,7 @@
 import android.content.pm.ResolveInfo;
 import android.database.ContentObserver;
 import android.graphics.Rect;
+import android.hardware.input.InputManager;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -44,12 +45,16 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemClock;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.text.TextUtils.SimpleStringSplitter;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.IWindow;
+import android.view.InputDevice;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -1301,11 +1306,11 @@
                     }
                 }
             }
-            final int flags = (mIncludeNotImportantViews) ?
-                    AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0;
-            final int interrogatingPid = Binder.getCallingPid();
             final long identityToken = Binder.clearCallingIdentity();
             try {
+                final int flags = (mIncludeNotImportantViews) ?
+                        AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0;
+                final int interrogatingPid = Binder.getCallingPid();
                 connection.performAccessibilityAction(accessibilityNodeId, action, interactionId,
                         callback, flags, interrogatingPid, interrogatingTid);
             } catch (RemoteException re) {
@@ -1318,6 +1323,24 @@
             return true;
         }
 
+        public boolean perfromGlobalAction(int action) {
+            switch (action) {
+                case AccessibilityService.GLOBAL_ACTION_BACK: {
+                    sendDownAndUpKeyEvents(KeyEvent.KEYCODE_BACK);
+                } return true;
+                case AccessibilityService.GLOBAL_ACTION_HOME: {
+                    sendDownAndUpKeyEvents(KeyEvent.KEYCODE_HOME);
+                } return true;
+                case AccessibilityService.GLOBAL_ACTION_RECENTS: {
+                    sendDownAndUpKeyEvents(KeyEvent.KEYCODE_APP_SWITCH);
+                } return true;
+                case AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS: {
+                    // TODO: Implement when 6346026 is fixed.
+                } return true;
+            }
+            return false;
+        }
+
         public void onServiceDisconnected(ComponentName componentName) {
             /* do nothing - #binderDied takes care */
         }
@@ -1358,6 +1381,30 @@
             }
         }
 
+        private void sendDownAndUpKeyEvents(int keyCode) {
+            final long token = Binder.clearCallingIdentity();
+
+            // Inject down.
+            final long downTime = SystemClock.uptimeMillis();
+            KeyEvent down = KeyEvent.obtain(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, 0, 0,
+                    KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FROM_SYSTEM,
+                    InputDevice.SOURCE_KEYBOARD, null);
+            InputManager.getInstance().injectInputEvent(down,
+                    InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
+            down.recycle();
+
+            // Inject up.
+            final long upTime = SystemClock.uptimeMillis();
+            KeyEvent up = KeyEvent.obtain(downTime, upTime, KeyEvent.ACTION_UP, keyCode, 0, 0,
+                    KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FROM_SYSTEM,
+                    InputDevice.SOURCE_KEYBOARD, null);
+            InputManager.getInstance().injectInputEvent(up,
+                    InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
+            up.recycle();
+
+            Binder.restoreCallingIdentity(token);
+        }
+
         private IAccessibilityInteractionConnection getConnectionLocked(int windowId) {
             if (DEBUG) {
                 Slog.i(LOG_TAG, "Trying to get interaction connection to windowId: " + windowId);