diff options
author | Alan Viverette <alanv@google.com> | 2013-11-07 23:34:08 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2013-11-07 23:34:08 +0000 |
commit | fe2737282fe3eac6b7e7b3863f82f6a6cc191af8 (patch) | |
tree | f68c951d045285d9f4854441ce2fb89ecee16208 | |
parent | f0696d992f4a18b12959ccf72e35d1ac1954cb6a (diff) | |
parent | f0aed09ed8153043e40b3ac99788d47ba0831306 (diff) | |
download | frameworks_base-fe2737282fe3eac6b7e7b3863f82f6a6cc191af8.zip frameworks_base-fe2737282fe3eac6b7e7b3863f82f6a6cc191af8.tar.gz frameworks_base-fe2737282fe3eac6b7e7b3863f82f6a6cc191af8.tar.bz2 |
Merge "Add methods for removing children and actions from A11y nodes"
-rw-r--r-- | api/current.txt | 3 | ||||
-rw-r--r-- | core/java/android/util/LongArray.java | 160 | ||||
-rw-r--r-- | core/java/android/view/AccessibilityInteractionController.java | 11 | ||||
-rw-r--r-- | core/java/android/view/ViewGroup.java | 6 | ||||
-rw-r--r-- | core/java/android/view/accessibility/AccessibilityInteractionClient.java | 5 | ||||
-rw-r--r-- | core/java/android/view/accessibility/AccessibilityNodeInfo.java | 178 | ||||
-rw-r--r-- | core/java/android/view/accessibility/AccessibilityNodeInfoCache.java | 23 |
7 files changed, 333 insertions, 53 deletions
diff --git a/api/current.txt b/api/current.txt index 8ec81b7..7e757b5 100644 --- a/api/current.txt +++ b/api/current.txt @@ -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); diff --git a/core/java/android/util/LongArray.java b/core/java/android/util/LongArray.java new file mode 100644 index 0000000..7d42063 --- /dev/null +++ b/core/java/android/util/LongArray.java @@ -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; + } +} diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index 41d3700..95a13d0 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -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) { diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 01eec98..dd2baf6 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -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(); } diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index 139df3e..d6e40aec 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -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) { diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 4f53c1e..61aabea 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -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 < 0 || index >= + * 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; } /** @@ -789,6 +871,24 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * 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> * <strong>Note:</strong> Cannot be called from an @@ -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]; } diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java index a9473a8..85aca61 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java @@ -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); } |