/* * Copyright (C) 2011 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.view.accessibility; import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.util.SparseIntArray; import android.view.View; import java.util.Collections; import java.util.List; /** * This class represents a node of the window content as well as actions that * can be requested from its source. From the point of view of an * {@link android.accessibilityservice.AccessibilityService} a window content is * presented as tree of accessibility node info which may or may not map one-to-one * to the view hierarchy. In other words, a custom view is free to report itself as * a tree of accessibility node info. * </p> * <p> * Once an accessibility node info is delivered to an accessibility service it is * made immutable and calling a state mutation method generates an error. * </p> * <p> * Please refer to {@link android.accessibilityservice.AccessibilityService} for * details about how to obtain a handle to window content as a tree of accessibility * node info as well as familiarizing with the security model. * </p> * * @see android.accessibilityservice.AccessibilityService * @see AccessibilityEvent * @see AccessibilityManager */ public class AccessibilityNodeInfo implements Parcelable { private static final boolean DEBUG = false; private static final int UNDEFINED = -1; // Actions. /** * Action that focuses the node. */ public static final int ACTION_FOCUS = 0x00000001; /** * Action that unfocuses the node. */ public static final int ACTION_CLEAR_FOCUS = 0x00000002; /** * Action that selects the node. */ public static final int ACTION_SELECT = 0x00000004; /** * Action that unselects the node. */ public static final int ACTION_CLEAR_SELECTION = 0x00000008; // Boolean attributes. private static final int PROPERTY_CHECKABLE = 0x00000001; private static final int PROPERTY_CHECKED = 0x00000002; private static final int PROPERTY_FOCUSABLE = 0x00000004; private static final int PROPERTY_FOCUSED = 0x00000008; private static final int PROPERTY_SELECTED = 0x00000010; private static final int PROPERTY_CLICKABLE = 0x00000020; private static final int PROPERTY_LONG_CLICKABLE = 0x00000040; private static final int PROPERTY_ENABLED = 0x00000080; private static final int PROPERTY_PASSWORD = 0x00000100; private static final int PROPERTY_SCROLLABLE = 0x00000200; // Housekeeping. private static final int MAX_POOL_SIZE = 50; private static final Object sPoolLock = new Object(); private static AccessibilityNodeInfo sPool; private static int sPoolSize; private AccessibilityNodeInfo mNext; private boolean mIsInPool; private boolean mSealed; // Data. private int mAccessibilityViewId = UNDEFINED; private int mAccessibilityWindowId = UNDEFINED; private int mParentAccessibilityViewId = UNDEFINED; private int mBooleanProperties; private final Rect mBoundsInParent = new Rect(); private final Rect mBoundsInScreen = new Rect(); private CharSequence mPackageName; private CharSequence mClassName; private CharSequence mText; private CharSequence mContentDescription; private SparseIntArray mChildAccessibilityIds = new SparseIntArray(); private int mActions; private int mConnectionId = UNDEFINED; /** * Hide constructor from clients. */ private AccessibilityNodeInfo() { /* do nothing */ } /** * Sets the source. * * @param source The info source. */ public void setSource(View source) { enforceNotSealed(); mAccessibilityViewId = source.getAccessibilityViewId(); mAccessibilityWindowId = source.getAccessibilityWindowId(); } /** * Gets the id of the window from which the info comes from. * * @return The window id. */ public int getWindowId() { return mAccessibilityWindowId; } /** * Gets the number of children. * * @return The child count. */ public int getChildCount() { return mChildAccessibilityIds.size(); } /** * Get the child at given index. * <p> * <strong>Note:</strong> It is a client responsibility to recycle the * received info by calling {@link AccessibilityNodeInfo#recycle()} * to avoid creating of multiple instances. * </p> * * @param index The child index. * @return The child node. * * @throws IllegalStateException If called outside of an AccessibilityService. * */ public AccessibilityNodeInfo getChild(int index) { enforceSealed(); final int childAccessibilityViewId = mChildAccessibilityIds.get(index); if (!canPerformRequestOverConnection(childAccessibilityViewId)) { return null; } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mAccessibilityWindowId, childAccessibilityViewId); } /** * Adds a child. * <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. * * @throws IllegalStateException If called from an AccessibilityService. */ public void addChild(View child) { enforceNotSealed(); final int childAccessibilityViewId = child.getAccessibilityViewId(); final int index = mChildAccessibilityIds.size(); mChildAccessibilityIds.put(index, childAccessibilityViewId); } /** * Gets the actions that can be performed on the node. * * @return The bit mask of with actions. * * @see AccessibilityNodeInfo#ACTION_FOCUS * @see AccessibilityNodeInfo#ACTION_CLEAR_FOCUS * @see AccessibilityNodeInfo#ACTION_SELECT * @see AccessibilityNodeInfo#ACTION_CLEAR_SELECTION */ public int getActions() { return mActions; } /** * Adds an action that can be performed on the node. * <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 addAction(int action) { enforceNotSealed(); mActions |= action; } /** * Performs an action on the node. * <p> * <strong>Note:</strong> An action can be performed only if the request is made * from an {@link android.accessibilityservice.AccessibilityService}. * </p> * * @param action The action to perform. * @return True if the action was performed. * * @throws IllegalStateException If called outside of an AccessibilityService. */ public boolean performAction(int action) { enforceSealed(); if (!canPerformRequestOverConnection(mAccessibilityViewId)) { return false; } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); return client.performAccessibilityAction(mConnectionId, mAccessibilityWindowId, mAccessibilityViewId, action); } /** * Finds {@link AccessibilityNodeInfo}s by text. The match is case * insensitive containment. The search is relative to this info i.e. * this info is the root of the traversed tree. * * <p> * <strong>Note:</strong> It is a client responsibility to recycle the * received info by calling {@link AccessibilityNodeInfo#recycle()} * to avoid creating of multiple instances. * </p> * * @param text The searched text. * @return A list of node info. */ public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text) { enforceSealed(); if (!canPerformRequestOverConnection(mAccessibilityViewId)) { return Collections.emptyList(); } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); return client.findAccessibilityNodeInfosByViewText(mConnectionId, text, mAccessibilityWindowId, mAccessibilityViewId); } /** * Gets the parent. * <p> * <strong>Note:</strong> It is a client responsibility to recycle the * received info by calling {@link AccessibilityNodeInfo#recycle()} * to avoid creating of multiple instances. * </p> * * @return The parent. */ public AccessibilityNodeInfo getParent() { enforceSealed(); if (!canPerformRequestOverConnection(mParentAccessibilityViewId)) { return null; } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mAccessibilityWindowId, mParentAccessibilityViewId); } /** * Sets the parent. * <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 parent The parent. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setParent(View parent) { enforceNotSealed(); mParentAccessibilityViewId = parent.getAccessibilityViewId(); } /** * Gets the node bounds in parent coordinates. * * @param outBounds The output node bounds. */ public void getBoundsInParent(Rect outBounds) { outBounds.set(mBoundsInParent.left, mBoundsInParent.top, mBoundsInParent.right, mBoundsInParent.bottom); } /** * Sets the node bounds in parent coordinates. * <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 bounds The node bounds. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setBoundsInParent(Rect bounds) { enforceNotSealed(); mBoundsInParent.set(bounds.left, bounds.top, bounds.right, bounds.bottom); } /** * Gets the node bounds in screen coordinates. * * @param outBounds The output node bounds. */ public void getBoundsInScreen(Rect outBounds) { outBounds.set(mBoundsInScreen.left, mBoundsInScreen.top, mBoundsInScreen.right, mBoundsInScreen.bottom); } /** * Sets the node bounds in screen coordinates. * <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 bounds The node bounds. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setBoundsInScreen(Rect bounds) { enforceNotSealed(); mBoundsInScreen.set(bounds.left, bounds.top, bounds.right, bounds.bottom); } /** * Gets whether this node is checkable. * * @return True if the node is checkable. */ public boolean isCheckable() { return getBooleanProperty(PROPERTY_CHECKABLE); } /** * Sets whether this node is checkable. * <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 checkable True if the node is checkable. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setCheckable(boolean checkable) { setBooleanProperty(PROPERTY_CHECKABLE, checkable); } /** * Gets whether this node is checked. * * @return True if the node is checked. */ public boolean isChecked() { return getBooleanProperty(PROPERTY_CHECKED); } /** * Sets whether this node is checked. * <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 checked True if the node is checked. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setChecked(boolean checked) { setBooleanProperty(PROPERTY_CHECKED, checked); } /** * Gets whether this node is focusable. * * @return True if the node is focusable. */ public boolean isFocusable() { return getBooleanProperty(PROPERTY_FOCUSABLE); } /** * Sets whether this node is focusable. * <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 focusable True if the node is focusable. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setFocusable(boolean focusable) { setBooleanProperty(PROPERTY_FOCUSABLE, focusable); } /** * Gets whether this node is focused. * * @return True if the node is focused. */ public boolean isFocused() { return getBooleanProperty(PROPERTY_FOCUSED); } /** * Sets whether this node is focused. * <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 focused True if the node is focused. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setFocused(boolean focused) { setBooleanProperty(PROPERTY_FOCUSED, focused); } /** * Gets whether this node is selected. * * @return True if the node is selected. */ public boolean isSelected() { return getBooleanProperty(PROPERTY_SELECTED); } /** * Sets whether this node is selected. * <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 selected True if the node is selected. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setSelected(boolean selected) { setBooleanProperty(PROPERTY_SELECTED, selected); } /** * Gets whether this node is clickable. * * @return True if the node is clickable. */ public boolean isClickable() { return getBooleanProperty(PROPERTY_CLICKABLE); } /** * Sets whether this node is clickable. * <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 clickable True if the node is clickable. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setClickable(boolean clickable) { setBooleanProperty(PROPERTY_CLICKABLE, clickable); } /** * Gets whether this node is long clickable. * * @return True if the node is long clickable. */ public boolean isLongClickable() { return getBooleanProperty(PROPERTY_LONG_CLICKABLE); } /** * Sets whether this node is long clickable. * <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 longClickable True if the node is long clickable. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setLongClickable(boolean longClickable) { setBooleanProperty(PROPERTY_LONG_CLICKABLE, longClickable); } /** * Gets whether this node is enabled. * * @return True if the node is enabled. */ public boolean isEnabled() { return getBooleanProperty(PROPERTY_ENABLED); } /** * Sets whether this node is enabled. * <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 enabled True if the node is enabled. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setEnabled(boolean enabled) { setBooleanProperty(PROPERTY_ENABLED, enabled); } /** * Gets whether this node is a password. * * @return True if the node is a password. */ public boolean isPassword() { return getBooleanProperty(PROPERTY_PASSWORD); } /** * Sets whether this node is a password. * <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 password True if the node is a password. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setPassword(boolean password) { setBooleanProperty(PROPERTY_PASSWORD, password); } /** * Gets if the node is scrollable. * * @return True if the node is scrollable, false otherwise. */ public boolean isScrollable() { return getBooleanProperty(PROPERTY_SCROLLABLE); } /** * Sets if the node is scrollable. * <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 scrollable True if the node is scrollable, false otherwise. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setScrollable(boolean scrollable) { enforceNotSealed(); setBooleanProperty(PROPERTY_SCROLLABLE, scrollable); } /** * Gets the package this node comes from. * * @return The package name. */ public CharSequence getPackageName() { return mPackageName; } /** * Sets the package this node comes from. * <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 packageName The package name. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setPackageName(CharSequence packageName) { enforceNotSealed(); mPackageName = packageName; } /** * Gets the class this node comes from. * * @return The class name. */ public CharSequence getClassName() { return mClassName; } /** * Sets the class this node comes from. * <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 className The class name. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setClassName(CharSequence className) { enforceNotSealed(); mClassName = className; } /** * Gets the text of this node. * * @return The text. */ public CharSequence getText() { return mText; } /** * Sets the text of this node. * <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 text The text. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setText(CharSequence text) { enforceNotSealed(); mText = text; } /** * Gets the content description of this node. * * @return The content description. */ public CharSequence getContentDescription() { return mContentDescription; } /** * Sets the content description of this node. * <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 contentDescription The content description. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setContentDescription(CharSequence contentDescription) { enforceNotSealed(); mContentDescription = contentDescription; } /** * Gets the value of a boolean property. * * @param property The property. * @return The value. */ private boolean getBooleanProperty(int property) { return (mBooleanProperties & property) != 0; } /** * Sets a boolean property. * * @param property The property. * @param value The value. * * @throws IllegalStateException If called from an AccessibilityService. */ private void setBooleanProperty(int property, boolean value) { enforceNotSealed(); if (value) { mBooleanProperties |= property; } else { mBooleanProperties &= ~property; } } /** * Sets the unique id of the IAccessibilityServiceConnection over which * this instance can send requests to the system. * * @param connectionId The connection id. * * @hide */ public void setConnectionId(int connectionId) { enforceNotSealed(); mConnectionId = connectionId; } /** * {@inheritDoc} */ public int describeContents() { return 0; } /** * Sets if this instance is sealed. * * @param sealed Whether is sealed. * * @hide */ public void setSealed(boolean sealed) { mSealed = sealed; } /** * Gets if this instance is sealed. * * @return Whether is sealed. * * @hide */ public boolean isSealed() { return mSealed; } /** * Enforces that this instance is sealed. * * @throws IllegalStateException If this instance is not sealed. * * @hide */ protected void enforceSealed() { if (!isSealed()) { throw new IllegalStateException("Cannot perform this " + "action on a not sealed instance."); } } /** * Enforces that this instance is not sealed. * * @throws IllegalStateException If this instance is sealed. * * @hide */ protected void enforceNotSealed() { if (isSealed()) { throw new IllegalStateException("Cannot perform this " + "action on an sealed instance."); } } /** * Returns a cached instance if such is available otherwise a new one * and sets the source. * * @return An instance. * * @see #setSource(View) */ public static AccessibilityNodeInfo obtain(View source) { AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); info.setSource(source); return info; } /** * Returns a cached instance if such is available otherwise a new one. * * @return An instance. */ public static AccessibilityNodeInfo obtain() { synchronized (sPoolLock) { if (sPool != null) { AccessibilityNodeInfo info = sPool; sPool = sPool.mNext; sPoolSize--; info.mNext = null; info.mIsInPool = false; return info; } return new AccessibilityNodeInfo(); } } /** * Returns a cached instance if such is available or a new one is * create. The returned instance is initialized from the given * <code>info</code>. * * @param info The other info. * @return An instance. */ public static AccessibilityNodeInfo obtain(AccessibilityNodeInfo info) { AccessibilityNodeInfo infoClone = AccessibilityNodeInfo.obtain(); infoClone.init(info); return infoClone; } /** * Return an instance back to be reused. * <p> * <strong>Note:</strong> You must not touch the object after calling this function. * * @throws IllegalStateException If the info is already recycled. */ public void recycle() { if (mIsInPool) { throw new IllegalStateException("Info already recycled!"); } clear(); synchronized (sPoolLock) { if (sPoolSize <= MAX_POOL_SIZE) { mNext = sPool; sPool = this; mIsInPool = true; sPoolSize++; } } } /** * {@inheritDoc} * <p> * <strong>Note:</strong> After the instance is written to a parcel it * is recycled. You must not touch the object after calling this function. * </p> */ public void writeToParcel(Parcel parcel, int flags) { parcel.writeInt(isSealed() ? 1 : 0); parcel.writeInt(mAccessibilityViewId); parcel.writeInt(mAccessibilityWindowId); parcel.writeInt(mParentAccessibilityViewId); parcel.writeInt(mConnectionId); SparseIntArray childIds = mChildAccessibilityIds; final int childIdsSize = childIds.size(); parcel.writeInt(childIdsSize); for (int i = 0; i < childIdsSize; i++) { parcel.writeInt(childIds.valueAt(i)); } parcel.writeInt(mBoundsInParent.top); parcel.writeInt(mBoundsInParent.bottom); parcel.writeInt(mBoundsInParent.left); parcel.writeInt(mBoundsInParent.right); parcel.writeInt(mBoundsInScreen.top); parcel.writeInt(mBoundsInScreen.bottom); parcel.writeInt(mBoundsInScreen.left); parcel.writeInt(mBoundsInScreen.right); parcel.writeInt(mActions); parcel.writeInt(mBooleanProperties); TextUtils.writeToParcel(mPackageName, parcel, flags); TextUtils.writeToParcel(mClassName, parcel, flags); TextUtils.writeToParcel(mText, parcel, flags); TextUtils.writeToParcel(mContentDescription, parcel, flags); // 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(); } /** * Initializes this instance from another one. * * @param other The other instance. */ private void init(AccessibilityNodeInfo other) { mSealed = other.mSealed; mAccessibilityViewId = other.mAccessibilityViewId; mParentAccessibilityViewId = other.mParentAccessibilityViewId; mAccessibilityWindowId = other.mAccessibilityWindowId; mConnectionId = other.mConnectionId; mBoundsInParent.set(other.mBoundsInParent); mBoundsInScreen.set(other.mBoundsInScreen); mPackageName = other.mPackageName; mClassName = other.mClassName; mText = other.mText; mContentDescription = other.mContentDescription; mActions= other.mActions; mBooleanProperties = other.mBooleanProperties; mChildAccessibilityIds = other.mChildAccessibilityIds.clone(); } /** * Creates a new instance from a {@link Parcel}. * * @param parcel A parcel containing the state of a {@link AccessibilityNodeInfo}. */ private void initFromParcel(Parcel parcel) { mSealed = (parcel.readInt() == 1); mAccessibilityViewId = parcel.readInt(); mAccessibilityWindowId = parcel.readInt(); mParentAccessibilityViewId = parcel.readInt(); mConnectionId = parcel.readInt(); SparseIntArray childIds = mChildAccessibilityIds; final int childrenSize = parcel.readInt(); for (int i = 0; i < childrenSize; i++) { final int childId = parcel.readInt(); childIds.put(i, childId); } mBoundsInParent.top = parcel.readInt(); mBoundsInParent.bottom = parcel.readInt(); mBoundsInParent.left = parcel.readInt(); mBoundsInParent.right = parcel.readInt(); mBoundsInScreen.top = parcel.readInt(); mBoundsInScreen.bottom = parcel.readInt(); mBoundsInScreen.left = parcel.readInt(); mBoundsInScreen.right = parcel.readInt(); mActions = parcel.readInt(); mBooleanProperties = parcel.readInt(); mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); mClassName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); mText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); } /** * Clears the state of this instance. */ private void clear() { mSealed = false; mAccessibilityViewId = UNDEFINED; mParentAccessibilityViewId = UNDEFINED; mAccessibilityWindowId = UNDEFINED; mConnectionId = UNDEFINED; mChildAccessibilityIds.clear(); mBoundsInParent.set(0, 0, 0, 0); mBoundsInScreen.set(0, 0, 0, 0); mBooleanProperties = 0; mPackageName = null; mClassName = null; mText = null; mContentDescription = null; mActions = 0; } /** * Gets the human readable action symbolic name. * * @param action The action. * @return The symbolic name. */ private static String getActionSymbolicName(int action) { switch (action) { case ACTION_FOCUS: return "ACTION_FOCUS"; case ACTION_CLEAR_FOCUS: return "ACTION_CLEAR_FOCUS"; case ACTION_SELECT: return "ACTION_SELECT"; case ACTION_CLEAR_SELECTION: return "ACTION_CLEAR_SELECTION"; default: throw new IllegalArgumentException("Unknown action: " + action); } } private boolean canPerformRequestOverConnection(int accessibilityViewId) { return (mConnectionId != UNDEFINED && mAccessibilityWindowId != UNDEFINED && accessibilityViewId != UNDEFINED); } @Override public boolean equals(Object object) { if (this == object) { return true; } if (object == null) { return false; } if (getClass() != object.getClass()) { return false; } AccessibilityNodeInfo other = (AccessibilityNodeInfo) object; if (mAccessibilityViewId != other.mAccessibilityViewId) { return false; } if (mAccessibilityWindowId != other.mAccessibilityWindowId) { return false; } return true; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + mAccessibilityViewId; result = prime * result + mAccessibilityWindowId; return result; } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append(super.toString()); if (DEBUG) { builder.append("; accessibilityId: " + mAccessibilityViewId); builder.append("; parentAccessibilityId: " + mParentAccessibilityViewId); SparseIntArray childIds = mChildAccessibilityIds; builder.append("; childAccessibilityIds: ["); for (int i = 0, count = childIds.size(); i < count; i++) { builder.append(childIds.valueAt(i)); if (i < count - 1) { builder.append(", "); } } builder.append("]"); } builder.append("; boundsInParent: " + mBoundsInParent); builder.append("; boundsInScreen: " + mBoundsInScreen); builder.append("; packageName: ").append(mPackageName); builder.append("; className: ").append(mClassName); builder.append("; text: ").append(mText); builder.append("; contentDescription: ").append(mContentDescription); builder.append("; checkable: ").append(isCheckable()); builder.append("; checked: ").append(isChecked()); builder.append("; focusable: ").append(isFocusable()); builder.append("; focused: ").append(isFocused()); builder.append("; selected: ").append(isSelected()); builder.append("; clickable: ").append(isClickable()); builder.append("; longClickable: ").append(isLongClickable()); builder.append("; enabled: ").append(isEnabled()); builder.append("; password: ").append(isPassword()); builder.append("; scrollable: " + isScrollable()); builder.append("; ["); for (int actionBits = mActions; actionBits != 0;) { final int action = 1 << Integer.numberOfTrailingZeros(actionBits); actionBits &= ~action; builder.append(getActionSymbolicName(action)); if (actionBits != 0) { builder.append(", "); } } builder.append("]"); return builder.toString(); } /** * @see Parcelable.Creator */ public static final Parcelable.Creator<AccessibilityNodeInfo> CREATOR = new Parcelable.Creator<AccessibilityNodeInfo>() { public AccessibilityNodeInfo createFromParcel(Parcel parcel) { AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); info.initFromParcel(parcel); return info; } public AccessibilityNodeInfo[] newArray(int size) { return new AccessibilityNodeInfo[size]; } }; }