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);