Merge "Add methods for removing children and actions from A11y nodes"

This commit is contained in:
Alan Viverette
2013-11-07 23:34:08 +00:00
committed by Android (Google) Code Review
7 changed files with 333 additions and 53 deletions

View File

@@ -29666,6 +29666,9 @@ package android.view.accessibility {
method public boolean performAction(int, android.os.Bundle);
method public void recycle();
method public boolean refresh();
method public void removeAction(int);
method public boolean removeChild(android.view.View);
method public boolean removeChild(android.view.View, int);
method public void setAccessibilityFocused(boolean);
method public void setBoundsInParent(android.graphics.Rect);
method public void setBoundsInScreen(android.graphics.Rect);

View File

@@ -0,0 +1,160 @@
/*
* Copyright (C) 2013 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.util;
import com.android.internal.util.ArrayUtils;
/**
* Implements a growing array of long primitives.
*
* @hide
*/
public class LongArray implements Cloneable {
private static final int MIN_CAPACITY_INCREMENT = 12;
private long[] mValues;
private int mSize;
/**
* Creates an empty LongArray with the default initial capacity.
*/
public LongArray() {
this(10);
}
/**
* Creates an empty LongArray with the specified initial capacity.
*/
public LongArray(int initialCapacity) {
if (initialCapacity == 0) {
mValues = ContainerHelpers.EMPTY_LONGS;
} else {
initialCapacity = ArrayUtils.idealLongArraySize(initialCapacity);
mValues = new long[initialCapacity];
}
mSize = 0;
}
/**
* Appends the specified value to the end of this array.
*/
public void add(long value) {
add(mSize, value);
}
/**
* Inserts a value at the specified position in this array.
*
* @throws IndexOutOfBoundsException when index < 0 || index > size()
*/
public void add(int index, long value) {
if (index < 0 || index > mSize) {
throw new IndexOutOfBoundsException();
}
ensureCapacity(1);
if (mSize - index != 0) {
System.arraycopy(mValues, index, mValues, index + 1, mSize - index);
}
mValues[index] = value;
mSize++;
}
/**
* Adds the values in the specified array to this array.
*/
public void addAll(LongArray values) {
final int count = values.mSize;
ensureCapacity(count);
System.arraycopy(mValues, mSize, values.mValues, 0, count);
mSize += count;
}
/**
* Ensures capacity to append at least <code>count</code> values.
*/
private void ensureCapacity(int count) {
final int currentSize = mSize;
final int minCapacity = currentSize + count;
if (minCapacity >= mValues.length) {
final int targetCap = currentSize + (currentSize < (MIN_CAPACITY_INCREMENT / 2) ?
MIN_CAPACITY_INCREMENT : currentSize >> 1);
final int newCapacity = targetCap > minCapacity ? targetCap : minCapacity;
final long[] newValues = new long[ArrayUtils.idealLongArraySize(newCapacity)];
System.arraycopy(mValues, 0, newValues, 0, currentSize);
mValues = newValues;
}
}
/**
* Removes all values from this array.
*/
public void clear() {
mSize = 0;
}
@Override
@SuppressWarnings("unchecked")
public LongArray clone() {
LongArray clone = null;
try {
clone = (LongArray) super.clone();
clone.mValues = mValues.clone();
} catch (CloneNotSupportedException cnse) {
/* ignore */
}
return clone;
}
/**
* Returns the value at the specified position in this array.
*/
public long get(int index) {
return mValues[index];
}
/**
* Returns the index of the first occurrence of the specified value in this
* array, or -1 if this array does not contain the value.
*/
public int indexOf(long value) {
final int n = mSize;
for (int i = 0; i < n; i++) {
if (mValues[i] == value) {
return i;
}
}
return -1;
}
/**
* Removes the value at the specified index from this array.
*/
public void remove(int index) {
System.arraycopy(mValues, index, mValues, index + 1, mSize - index);
}
/**
* Returns the number of values in this array.
*/
public int size() {
return mSize;
}
}

View File

@@ -24,6 +24,7 @@ import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.util.LongArray;
import android.util.SparseLongArray;
import android.view.View.AttachInfo;
import android.view.accessibility.AccessibilityInteractionClient;
@@ -881,13 +882,12 @@ final class AccessibilityInteractionController {
AccessibilityNodeInfo parent =
provider.createAccessibilityNodeInfo(parentVirtualDescendantId);
if (parent != null) {
SparseLongArray childNodeIds = parent.getChildNodeIds();
final int childCount = childNodeIds.size();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
return;
}
final long childNodeId = childNodeIds.get(i);
final long childNodeId = parent.getChildId(i);
if (childNodeId != current.getSourceNodeId()) {
final int childVirtualDescendantId =
AccessibilityNodeInfo.getVirtualDescendantId(childNodeId);
@@ -906,14 +906,13 @@ final class AccessibilityInteractionController {
private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root,
AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
SparseLongArray childNodeIds = root.getChildNodeIds();
final int initialOutInfosSize = outInfos.size();
final int childCount = childNodeIds.size();
final int childCount = root.getChildCount();
for (int i = 0; i < childCount; i++) {
if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
return;
}
final long childNodeId = childNodeIds.get(i);
final long childNodeId = root.getChildId(i);
AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
AccessibilityNodeInfo.getVirtualDescendantId(childNodeId));
if (child != null) {

View File

@@ -2511,13 +2511,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfoInternal(info);
if (mAttachInfo != null) {
ArrayList<View> childrenForAccessibility = mAttachInfo.mTempArrayList;
final ArrayList<View> childrenForAccessibility = mAttachInfo.mTempArrayList;
childrenForAccessibility.clear();
addChildrenForAccessibility(childrenForAccessibility);
final int childrenForAccessibilityCount = childrenForAccessibility.size();
for (int i = 0; i < childrenForAccessibilityCount; i++) {
View child = childrenForAccessibility.get(i);
info.addChild(child);
final View child = childrenForAccessibility.get(i);
info.addChildUnchecked(child);
}
childrenForAccessibility.clear();
}

View File

@@ -718,10 +718,9 @@ public final class AccessibilityInteractionClient
Log.e(LOG_TAG, "Duplicate node.");
return;
}
SparseLongArray childIds = current.getChildNodeIds();
final int childCount = childIds.size();
final int childCount = current.getChildCount();
for (int i = 0; i < childCount; i++) {
final long childId = childIds.valueAt(i);
final long childId = current.getChildId(i);
for (int j = 0; j < infoCount; j++) {
AccessibilityNodeInfo child = infos.get(j);
if (child.getSourceNodeId() == childId) {

View File

@@ -22,8 +22,8 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.InputType;
import android.util.LongArray;
import android.util.Pools.SynchronizedPool;
import android.util.SparseLongArray;
import android.view.View;
import java.util.Collections;
@@ -503,7 +503,7 @@ public class AccessibilityNodeInfo implements Parcelable {
private CharSequence mContentDescription;
private String mViewIdResourceName;
private final SparseLongArray mChildNodeIds = new SparseLongArray();
private LongArray mChildNodeIds;
private int mActions;
private int mMovementGranularities;
@@ -666,21 +666,35 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
* @return The ids of the children.
* Returns the array containing the IDs of this node's children.
*
* @hide
*/
public SparseLongArray getChildNodeIds() {
public LongArray getChildNodeIds() {
return mChildNodeIds;
}
/**
* Returns the id of the child at the specified index.
*
* @throws IndexOutOfBoundsException when index &lt; 0 || index &gt;=
* getChildCount()
* @hide
*/
public long getChildId(int index) {
if (mChildNodeIds == null) {
throw new IndexOutOfBoundsException();
}
return mChildNodeIds.get(index);
}
/**
* Gets the number of children.
*
* @return The child count.
*/
public int getChildCount() {
return mChildNodeIds.size();
return mChildNodeIds == null ? 0 : mChildNodeIds.size();
}
/**
@@ -699,6 +713,9 @@ public class AccessibilityNodeInfo implements Parcelable {
*/
public AccessibilityNodeInfo getChild(int index) {
enforceSealed();
if (mChildNodeIds == null) {
return null;
}
if (!canPerformRequestOverConnection(mSourceNodeId)) {
return null;
}
@@ -721,7 +738,35 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void addChild(View child) {
addChild(child, UNDEFINED);
addChildInternal(child, UNDEFINED, true);
}
/**
* Unchecked version of {@link #addChild(View)} that does not verify
* uniqueness. For framework use only.
*
* @hide
*/
public void addChildUnchecked(View child) {
addChildInternal(child, UNDEFINED, false);
}
/**
* Removes a child. If the child was not previously added to the node,
* calling this method has no effect.
* <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 child The child.
* @return true if the child was present
*
* @throws IllegalStateException If called from an AccessibilityService.
*/
public boolean removeChild(View child) {
return removeChild(child, UNDEFINED);
}
/**
@@ -739,12 +784,49 @@ public class AccessibilityNodeInfo implements Parcelable {
* @param virtualDescendantId The id of the virtual child.
*/
public void addChild(View root, int virtualDescendantId) {
addChildInternal(root, virtualDescendantId, true);
}
private void addChildInternal(View root, int virtualDescendantId, boolean checked) {
enforceNotSealed();
final int index = mChildNodeIds.size();
if (mChildNodeIds == null) {
mChildNodeIds = new LongArray();
}
final int rootAccessibilityViewId =
(root != null) ? root.getAccessibilityViewId() : UNDEFINED;
final long childNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
mChildNodeIds.put(index, childNodeId);
// If we're checking uniqueness and the ID already exists, abort.
if (checked && mChildNodeIds.indexOf(childNodeId) >= 0) {
return;
}
mChildNodeIds.add(childNodeId);
}
/**
* Removes a virtual child which is a descendant of the given
* <code>root</code>. If the child was not previously added to the node,
* calling this method has no effect.
*
* @param root The root of the virtual subtree.
* @param virtualDescendantId The id of the virtual child.
* @return true if the child was present
* @see #addChild(View, int)
*/
public boolean removeChild(View root, int virtualDescendantId) {
enforceNotSealed();
final LongArray childIds = mChildNodeIds;
if (childIds == null) {
return false;
}
final int rootAccessibilityViewId =
(root != null) ? root.getAccessibilityViewId() : UNDEFINED;
final long childNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
final int index = childIds.indexOf(childNodeId);
if (index < 0) {
return false;
}
childIds.remove(index);
return true;
}
/**
@@ -788,6 +870,24 @@ public class AccessibilityNodeInfo implements Parcelable {
mActions |= action;
}
/**
* Removes an action that can be performed on the node. If the action was
* not already added to the node, calling this method has no effect.
* <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 action The action.
*
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void removeAction(int action) {
enforceNotSealed();
mActions &= ~action;
}
/**
* Sets the movement granularities for traversing the text of this node.
* <p>
@@ -1408,8 +1508,6 @@ public class AccessibilityNodeInfo implements Parcelable {
* {@link android.accessibilityservice.AccessibilityService}.
* This class is made immutable before being delivered to an AccessibilityService.
* </p>
*
* @return collectionItem True if the node is an item.
*/
public void setCollectionItemInfo(CollectionItemInfo collectionItemInfo) {
enforceNotSealed();
@@ -1951,6 +2049,7 @@ public class AccessibilityNodeInfo implements Parcelable {
/**
* {@inheritDoc}
*/
@Override
public int describeContents() {
return 0;
}
@@ -2114,6 +2213,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* is recycled. You must not touch the object after calling this function.
* </p>
*/
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(isSealed() ? 1 : 0);
parcel.writeLong(mSourceNodeId);
@@ -2123,11 +2223,15 @@ public class AccessibilityNodeInfo implements Parcelable {
parcel.writeLong(mLabeledById);
parcel.writeInt(mConnectionId);
SparseLongArray childIds = mChildNodeIds;
final int childIdsSize = childIds.size();
parcel.writeInt(childIdsSize);
for (int i = 0; i < childIdsSize; i++) {
parcel.writeLong(childIds.valueAt(i));
final LongArray childIds = mChildNodeIds;
if (childIds == null) {
parcel.writeInt(0);
} else {
final int childIdsSize = childIds.size();
parcel.writeInt(childIdsSize);
for (int i = 0; i < childIdsSize; i++) {
parcel.writeLong(childIds.get(i));
}
}
parcel.writeInt(mBoundsInParent.top);
@@ -2222,10 +2326,16 @@ public class AccessibilityNodeInfo implements Parcelable {
mActions= other.mActions;
mBooleanProperties = other.mBooleanProperties;
mMovementGranularities = other.mMovementGranularities;
final int otherChildIdCount = other.mChildNodeIds.size();
for (int i = 0; i < otherChildIdCount; i++) {
mChildNodeIds.put(i, other.mChildNodeIds.valueAt(i));
final LongArray otherChildNodeIds = other.mChildNodeIds;
if (otherChildNodeIds != null && otherChildNodeIds.size() > 0) {
if (mChildNodeIds == null) {
mChildNodeIds = otherChildNodeIds.clone();
} else {
mChildNodeIds.addAll(otherChildNodeIds);
}
}
mTextSelectionStart = other.mTextSelectionStart;
mTextSelectionEnd = other.mTextSelectionEnd;
mInputType = other.mInputType;
@@ -2255,11 +2365,15 @@ public class AccessibilityNodeInfo implements Parcelable {
mLabeledById = parcel.readLong();
mConnectionId = parcel.readInt();
SparseLongArray childIds = mChildNodeIds;
final int childrenSize = parcel.readInt();
for (int i = 0; i < childrenSize; i++) {
final long childId = parcel.readLong();
childIds.put(i, childId);
if (childrenSize <= 0) {
mChildNodeIds = null;
} else {
mChildNodeIds = new LongArray(childrenSize);
for (int i = 0; i < childrenSize; i++) {
final long childId = parcel.readLong();
mChildNodeIds.add(childId);
}
}
mBoundsInParent.top = parcel.readInt();
@@ -2331,7 +2445,9 @@ public class AccessibilityNodeInfo implements Parcelable {
mWindowId = UNDEFINED;
mConnectionId = UNDEFINED;
mMovementGranularities = 0;
mChildNodeIds.clear();
if (mChildNodeIds != null) {
mChildNodeIds.clear();
}
mBoundsInParent.set(0, 0, 0, 0);
mBoundsInScreen.set(0, 0, 0, 0);
mBooleanProperties = 0;
@@ -2493,12 +2609,14 @@ public class AccessibilityNodeInfo implements Parcelable {
}
builder.append("]");
SparseLongArray childIds = mChildNodeIds;
builder.append("; childAccessibilityIds: [");
for (int i = 0, count = childIds.size(); i < count; i++) {
builder.append(childIds.valueAt(i));
if (i < count - 1) {
builder.append(", ");
final LongArray childIds = mChildNodeIds;
if (childIds != null) {
for (int i = 0, count = childIds.size(); i < count; i++) {
builder.append(childIds.get(i));
if (i < count - 1) {
builder.append(", ");
}
}
}
builder.append("]");
@@ -2893,16 +3011,18 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
* @see Parcelable.Creator
* @see android.os.Parcelable.Creator
*/
public static final Parcelable.Creator<AccessibilityNodeInfo> CREATOR =
new Parcelable.Creator<AccessibilityNodeInfo>() {
@Override
public AccessibilityNodeInfo createFromParcel(Parcel parcel) {
AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
info.initFromParcel(parcel);
return info;
}
@Override
public AccessibilityNodeInfo[] newArray(int size) {
return new AccessibilityNodeInfo[size];
}

View File

@@ -18,6 +18,7 @@ package android.view.accessibility;
import android.os.Build;
import android.util.Log;
import android.util.LongArray;
import android.util.LongSparseArray;
import android.util.SparseLongArray;
@@ -172,12 +173,12 @@ public class AccessibilityNodeInfoCache {
// the new one represents a source state where some of the
// children have been removed to avoid having disconnected
// subtrees in the cache.
SparseLongArray oldChildrenIds = oldInfo.getChildNodeIds();
SparseLongArray newChildrenIds = info.getChildNodeIds();
final int oldChildCount = oldChildrenIds.size();
// TODO: Runs in O(n^2), could optimize to O(n + n log n)
final LongArray newChildrenIds = info.getChildNodeIds();
final int oldChildCount = oldInfo.getChildCount();
for (int i = 0; i < oldChildCount; i++) {
final long oldChildId = oldChildrenIds.valueAt(i);
if (newChildrenIds.indexOfValue(oldChildId) < 0) {
final long oldChildId = oldInfo.getChildId(i);
if (newChildrenIds.indexOf(oldChildId) < 0) {
clearSubTreeLocked(oldChildId);
}
}
@@ -237,10 +238,9 @@ public class AccessibilityNodeInfoCache {
return;
}
mCacheImpl.remove(rootNodeId);
SparseLongArray childNodeIds = current.getChildNodeIds();
final int childCount = childNodeIds.size();
final int childCount = current.getChildCount();
for (int i = 0; i < childCount; i++) {
final long childNodeId = childNodeIds.valueAt(i);
final long childNodeId = current.getChildId(i);
clearSubTreeRecursiveLocked(childNodeId);
}
}
@@ -301,11 +301,10 @@ public class AccessibilityNodeInfoCache {
}
}
SparseLongArray childIds = current.getChildNodeIds();
final int childCount = childIds.size();
final int childCount = current.getChildCount();
for (int i = 0; i < childCount; i++) {
final long childId = childIds.valueAt(i);
AccessibilityNodeInfo child = mCacheImpl.get(childId);
final long childId = current.getChildId(i);
final AccessibilityNodeInfo child = mCacheImpl.get(childId);
if (child != null) {
fringe.add(child);
}