Set accessibility cursor in non-text views with content description.

We support text traversal at a granularity over non-next views with
content description, hence we should support setting the cursor position
in such views.

bug:8134469

Change-Id: I4dba225b0ade795b7a20c201fb906ae7146c065d
diff --git a/api/current.txt b/api/current.txt
index d1363db..0569b9d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -26305,12 +26305,15 @@
     method public java.lang.CharSequence getPackageName();
     method public android.view.accessibility.AccessibilityNodeInfo getParent();
     method public java.lang.CharSequence getText();
+    method public int getTextSelectionEnd();
+    method public int getTextSelectionStart();
     method public java.lang.CharSequence getViewIdResourceName();
     method public int getWindowId();
     method public boolean isAccessibilityFocused();
     method public boolean isCheckable();
     method public boolean isChecked();
     method public boolean isClickable();
+    method public boolean isEditable();
     method public boolean isEnabled();
     method public boolean isFocusable();
     method public boolean isFocused();
@@ -26335,6 +26338,7 @@
     method public void setClassName(java.lang.CharSequence);
     method public void setClickable(boolean);
     method public void setContentDescription(java.lang.CharSequence);
+    method public void setEditable(boolean);
     method public void setEnabled(boolean);
     method public void setFocusable(boolean);
     method public void setFocused(boolean);
@@ -26353,6 +26357,7 @@
     method public void setSource(android.view.View);
     method public void setSource(android.view.View, int);
     method public void setText(java.lang.CharSequence);
+    method public void setTextSelection(int, int);
     method public void setViewIdResourceName(java.lang.CharSequence);
     method public void setVisibleToUser(boolean);
     method public void writeToParcel(android.os.Parcel, int);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index cbd0668..ee3ca8c 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4859,13 +4859,25 @@
         event.setEnabled(isEnabled());
         event.setContentDescription(mContentDescription);
 
-        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED && mAttachInfo != null) {
-            ArrayList<View> focusablesTempList = mAttachInfo.mTempArrayList;
-            getRootView().addFocusables(focusablesTempList, View.FOCUS_FORWARD,
-                    FOCUSABLES_ALL);
-            event.setItemCount(focusablesTempList.size());
-            event.setCurrentItemIndex(focusablesTempList.indexOf(this));
-            focusablesTempList.clear();
+        switch (event.getEventType()) {
+            case AccessibilityEvent.TYPE_VIEW_FOCUSED: {
+                ArrayList<View> focusablesTempList = (mAttachInfo != null)
+                        ? mAttachInfo.mTempArrayList : new ArrayList<View>();
+                getRootView().addFocusables(focusablesTempList, View.FOCUS_FORWARD, FOCUSABLES_ALL);
+                event.setItemCount(focusablesTempList.size());
+                event.setCurrentItemIndex(focusablesTempList.indexOf(this));
+                if (mAttachInfo != null) {
+                    focusablesTempList.clear();
+                }
+            } break;
+            case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: {
+                CharSequence text = getIterableTextForAccessibility();
+                if (text != null && text.length() > 0) {
+                    event.setFromIndex(getAccessibilitySelectionStart());
+                    event.setToIndex(getAccessibilitySelectionEnd());
+                    event.setItemCount(text.length());
+                }
+            } break;
         }
     }
 
@@ -5081,7 +5093,10 @@
             info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
         }
 
-        if (mContentDescription != null && mContentDescription.length() > 0) {
+        CharSequence text = getIterableTextForAccessibility();
+        if (text != null && text.length() > 0) {
+            info.setTextSelection(getAccessibilitySelectionStart(), getAccessibilitySelectionEnd());
+
             info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
             info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
             info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
@@ -7153,11 +7168,15 @@
      * @hide
      */
     public void setAccessibilitySelection(int start, int end) {
+        if (start ==  end && end == mAccessibilityCursorPosition) {
+            return;
+        }
         if (start >= 0 && start == end && end <= getIterableTextForAccessibility().length()) {
             mAccessibilityCursorPosition = start;
         } else {
             mAccessibilityCursorPosition = ACCESSIBILITY_CURSOR_POSITION_UNDEFINED;
         }
+        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
     }
 
     private void sendViewTextTraversedAtGranularityEvent(int action, int granularity,
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index f61c879..ad87fcb 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -372,29 +372,31 @@
 
     // Boolean attributes.
 
-    private static final int PROPERTY_CHECKABLE = 0x00000001;
+    private static final int BOOLEAN_PROPERTY_CHECKABLE = 0x00000001;
 
-    private static final int PROPERTY_CHECKED = 0x00000002;
+    private static final int BOOLEAN_PROPERTY_CHECKED = 0x00000002;
 
-    private static final int PROPERTY_FOCUSABLE = 0x00000004;
+    private static final int BOOLEAN_PROPERTY_FOCUSABLE = 0x00000004;
 
-    private static final int PROPERTY_FOCUSED = 0x00000008;
+    private static final int BOOLEAN_PROPERTY_FOCUSED = 0x00000008;
 
-    private static final int PROPERTY_SELECTED = 0x00000010;
+    private static final int BOOLEAN_PROPERTY_SELECTED = 0x00000010;
 
-    private static final int PROPERTY_CLICKABLE = 0x00000020;
+    private static final int BOOLEAN_PROPERTY_CLICKABLE = 0x00000020;
 
-    private static final int PROPERTY_LONG_CLICKABLE = 0x00000040;
+    private static final int BOOLEAN_PROPERTY_LONG_CLICKABLE = 0x00000040;
 
-    private static final int PROPERTY_ENABLED = 0x00000080;
+    private static final int BOOLEAN_PROPERTY_ENABLED = 0x00000080;
 
-    private static final int PROPERTY_PASSWORD = 0x00000100;
+    private static final int BOOLEAN_PROPERTY_PASSWORD = 0x00000100;
 
-    private static final int PROPERTY_SCROLLABLE = 0x00000200;
+    private static final int BOOLEAN_PROPERTY_SCROLLABLE = 0x00000200;
 
-    private static final int PROPERTY_ACCESSIBILITY_FOCUSED = 0x00000400;
+    private static final int BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED = 0x00000400;
 
-    private static final int PROPERTY_VISIBLE_TO_USER = 0x00000800;
+    private static final int BOOLEAN_PROPERTY_VISIBLE_TO_USER = 0x00000800;
+
+    private static final int BOOLEAN_PROPERTY_EDITABLE = 0x00001000;
 
     /**
      * Bits that provide the id of a virtual descendant of a view.
@@ -478,6 +480,9 @@
 
     private int mMovementGranularities;
 
+    private int mTextSelectionStart = UNDEFINED;
+    private int mTextSelectionEnd = UNDEFINED;
+
     private int mConnectionId = UNDEFINED;
 
     /**
@@ -987,7 +992,7 @@
      * @return True if the node is checkable.
      */
     public boolean isCheckable() {
-        return getBooleanProperty(PROPERTY_CHECKABLE);
+        return getBooleanProperty(BOOLEAN_PROPERTY_CHECKABLE);
     }
 
     /**
@@ -1003,7 +1008,7 @@
      * @throws IllegalStateException If called from an AccessibilityService.
      */
     public void setCheckable(boolean checkable) {
-        setBooleanProperty(PROPERTY_CHECKABLE, checkable);
+        setBooleanProperty(BOOLEAN_PROPERTY_CHECKABLE, checkable);
     }
 
     /**
@@ -1012,7 +1017,7 @@
      * @return True if the node is checked.
      */
     public boolean isChecked() {
-        return getBooleanProperty(PROPERTY_CHECKED);
+        return getBooleanProperty(BOOLEAN_PROPERTY_CHECKED);
     }
 
     /**
@@ -1028,7 +1033,7 @@
      * @throws IllegalStateException If called from an AccessibilityService.
      */
     public void setChecked(boolean checked) {
-        setBooleanProperty(PROPERTY_CHECKED, checked);
+        setBooleanProperty(BOOLEAN_PROPERTY_CHECKED, checked);
     }
 
     /**
@@ -1037,7 +1042,7 @@
      * @return True if the node is focusable.
      */
     public boolean isFocusable() {
-        return getBooleanProperty(PROPERTY_FOCUSABLE);
+        return getBooleanProperty(BOOLEAN_PROPERTY_FOCUSABLE);
     }
 
     /**
@@ -1053,7 +1058,7 @@
      * @throws IllegalStateException If called from an AccessibilityService.
      */
     public void setFocusable(boolean focusable) {
-        setBooleanProperty(PROPERTY_FOCUSABLE, focusable);
+        setBooleanProperty(BOOLEAN_PROPERTY_FOCUSABLE, focusable);
     }
 
     /**
@@ -1062,7 +1067,7 @@
      * @return True if the node is focused.
      */
     public boolean isFocused() {
-        return getBooleanProperty(PROPERTY_FOCUSED);
+        return getBooleanProperty(BOOLEAN_PROPERTY_FOCUSED);
     }
 
     /**
@@ -1078,7 +1083,7 @@
      * @throws IllegalStateException If called from an AccessibilityService.
      */
     public void setFocused(boolean focused) {
-        setBooleanProperty(PROPERTY_FOCUSED, focused);
+        setBooleanProperty(BOOLEAN_PROPERTY_FOCUSED, focused);
     }
 
     /**
@@ -1087,7 +1092,7 @@
      * @return Whether the node is visible to the user.
      */
     public boolean isVisibleToUser() {
-        return getBooleanProperty(PROPERTY_VISIBLE_TO_USER);
+        return getBooleanProperty(BOOLEAN_PROPERTY_VISIBLE_TO_USER);
     }
 
     /**
@@ -1103,7 +1108,7 @@
      * @throws IllegalStateException If called from an AccessibilityService.
      */
     public void setVisibleToUser(boolean visibleToUser) {
-        setBooleanProperty(PROPERTY_VISIBLE_TO_USER, visibleToUser);
+        setBooleanProperty(BOOLEAN_PROPERTY_VISIBLE_TO_USER, visibleToUser);
     }
 
     /**
@@ -1112,7 +1117,7 @@
      * @return True if the node is accessibility focused.
      */
     public boolean isAccessibilityFocused() {
-        return getBooleanProperty(PROPERTY_ACCESSIBILITY_FOCUSED);
+        return getBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED);
     }
 
     /**
@@ -1128,7 +1133,7 @@
      * @throws IllegalStateException If called from an AccessibilityService.
      */
     public void setAccessibilityFocused(boolean focused) {
-        setBooleanProperty(PROPERTY_ACCESSIBILITY_FOCUSED, focused);
+        setBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED, focused);
     }
 
     /**
@@ -1137,7 +1142,7 @@
      * @return True if the node is selected.
      */
     public boolean isSelected() {
-        return getBooleanProperty(PROPERTY_SELECTED);
+        return getBooleanProperty(BOOLEAN_PROPERTY_SELECTED);
     }
 
     /**
@@ -1153,7 +1158,7 @@
      * @throws IllegalStateException If called from an AccessibilityService.
      */
     public void setSelected(boolean selected) {
-        setBooleanProperty(PROPERTY_SELECTED, selected);
+        setBooleanProperty(BOOLEAN_PROPERTY_SELECTED, selected);
     }
 
     /**
@@ -1162,7 +1167,7 @@
      * @return True if the node is clickable.
      */
     public boolean isClickable() {
-        return getBooleanProperty(PROPERTY_CLICKABLE);
+        return getBooleanProperty(BOOLEAN_PROPERTY_CLICKABLE);
     }
 
     /**
@@ -1178,7 +1183,7 @@
      * @throws IllegalStateException If called from an AccessibilityService.
      */
     public void setClickable(boolean clickable) {
-        setBooleanProperty(PROPERTY_CLICKABLE, clickable);
+        setBooleanProperty(BOOLEAN_PROPERTY_CLICKABLE, clickable);
     }
 
     /**
@@ -1187,7 +1192,7 @@
      * @return True if the node is long clickable.
      */
     public boolean isLongClickable() {
-        return getBooleanProperty(PROPERTY_LONG_CLICKABLE);
+        return getBooleanProperty(BOOLEAN_PROPERTY_LONG_CLICKABLE);
     }
 
     /**
@@ -1203,7 +1208,7 @@
      * @throws IllegalStateException If called from an AccessibilityService.
      */
     public void setLongClickable(boolean longClickable) {
-        setBooleanProperty(PROPERTY_LONG_CLICKABLE, longClickable);
+        setBooleanProperty(BOOLEAN_PROPERTY_LONG_CLICKABLE, longClickable);
     }
 
     /**
@@ -1212,7 +1217,7 @@
      * @return True if the node is enabled.
      */
     public boolean isEnabled() {
-        return getBooleanProperty(PROPERTY_ENABLED);
+        return getBooleanProperty(BOOLEAN_PROPERTY_ENABLED);
     }
 
     /**
@@ -1228,7 +1233,7 @@
      * @throws IllegalStateException If called from an AccessibilityService.
      */
     public void setEnabled(boolean enabled) {
-        setBooleanProperty(PROPERTY_ENABLED, enabled);
+        setBooleanProperty(BOOLEAN_PROPERTY_ENABLED, enabled);
     }
 
     /**
@@ -1237,7 +1242,7 @@
      * @return True if the node is a password.
      */
     public boolean isPassword() {
-        return getBooleanProperty(PROPERTY_PASSWORD);
+        return getBooleanProperty(BOOLEAN_PROPERTY_PASSWORD);
     }
 
     /**
@@ -1253,7 +1258,7 @@
      * @throws IllegalStateException If called from an AccessibilityService.
      */
     public void setPassword(boolean password) {
-        setBooleanProperty(PROPERTY_PASSWORD, password);
+        setBooleanProperty(BOOLEAN_PROPERTY_PASSWORD, password);
     }
 
     /**
@@ -1262,7 +1267,7 @@
      * @return True if the node is scrollable, false otherwise.
      */
     public boolean isScrollable() {
-        return getBooleanProperty(PROPERTY_SCROLLABLE);
+        return getBooleanProperty(BOOLEAN_PROPERTY_SCROLLABLE);
     }
 
     /**
@@ -1279,7 +1284,32 @@
      */
     public void setScrollable(boolean scrollable) {
         enforceNotSealed();
-        setBooleanProperty(PROPERTY_SCROLLABLE, scrollable);
+        setBooleanProperty(BOOLEAN_PROPERTY_SCROLLABLE, scrollable);
+    }
+
+    /**
+     * Gets if the node is editable.
+     *
+     * @return True if the node is editable, false otherwise.
+     */
+    public boolean isEditable() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_EDITABLE);
+    }
+
+    /**
+     * Sets whether this node is editable.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param editable True if the node is editable.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setEditable(boolean editable) {
+        setBooleanProperty(BOOLEAN_PROPERTY_EDITABLE, editable);
     }
 
     /**
@@ -1533,6 +1563,43 @@
     }
 
     /**
+     * Gets the text selection start.
+     *
+     * @return The text selection start if there is selection or -1.
+     */
+    public int getTextSelectionStart() {
+        return mTextSelectionStart;
+    }
+
+    /**
+     * Gets the text selection end.
+     *
+     * @return The text selection end if there is selection or -1.
+     */
+    public int getTextSelectionEnd() {
+        return mTextSelectionEnd;
+    }
+
+    /**
+     * Sets the text selection start and end.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param start The text selection start.
+     * @param end The text selection end.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setTextSelection(int start, int end) {
+        enforceNotSealed();
+        mTextSelectionStart = start;
+        mTextSelectionEnd = end;
+    }
+
+    /**
      * Gets the value of a boolean property.
      *
      * @param property The property.
@@ -1776,6 +1843,9 @@
         parcel.writeCharSequence(mContentDescription);
         parcel.writeCharSequence(mViewIdResourceName);
 
+        parcel.writeInt(mTextSelectionStart);
+        parcel.writeInt(mTextSelectionEnd);
+
         // Since instances of this class are fetched via synchronous i.e. blocking
         // calls in IPCs we always recycle as soon as the instance is marshaled.
         recycle();
@@ -1808,6 +1878,8 @@
         for (int i = 0; i < otherChildIdCount; i++) {
             mChildNodeIds.put(i, other.mChildNodeIds.valueAt(i));    
         }
+        mTextSelectionStart = other.mTextSelectionStart;
+        mTextSelectionEnd = other.mTextSelectionEnd;
     }
 
     /**
@@ -1852,6 +1924,9 @@
         mText = parcel.readCharSequence();
         mContentDescription = parcel.readCharSequence();
         mViewIdResourceName = parcel.readCharSequence();
+
+        mTextSelectionStart = parcel.readInt();
+        mTextSelectionEnd = parcel.readInt();
     }
 
     /**
@@ -1876,6 +1951,8 @@
         mContentDescription = null;
         mViewIdResourceName = null;
         mActions = 0;
+        mTextSelectionStart = UNDEFINED;
+        mTextSelectionEnd = UNDEFINED;
     }
 
     /**
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 1579e79..2145419 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -7165,6 +7165,7 @@
      */
     protected void onSelectionChanged(int selStart, int selEnd) {
         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
+        notifyAccessibilityStateChanged();
     }
 
     /**
@@ -7976,6 +7977,10 @@
             info.setText(getTextForAccessibility());
         }
 
+        if (mBufferType == BufferType.EDITABLE) {
+            info.setEditable(true);
+        }
+
         if (TextUtils.isEmpty(getContentDescription()) && !TextUtils.isEmpty(mText)) {
             info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
             info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);