/* * 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.os.Parcelable; import android.view.View; import java.util.ArrayList; import java.util.List; /** * Represents a record in an {@link AccessibilityEvent} and contains information * about state change of its source {@link android.view.View}. When a view fires * an accessibility event it requests from its parent to dispatch the * constructed event. The parent may optionally append a record for itself * for providing more context to * {@link android.accessibilityservice.AccessibilityService}s. Hence, * accessibility services can facilitate additional accessibility records * to enhance feedback. *

*

* Once the accessibility event containing a record is dispatched the record is * made immutable and calling a state mutation method generates an error. *

*

* Note: Not all properties are applicable to all accessibility * event types. For detailed information please refer to {@link AccessibilityEvent}. *

* *
*

Developer Guides

*

For more information about creating and processing AccessibilityRecords, read the * Accessibility * developer guide.

*
* * @see AccessibilityEvent * @see AccessibilityManager * @see android.accessibilityservice.AccessibilityService * @see AccessibilityNodeInfo */ public class AccessibilityRecord { private static final int UNDEFINED = -1; private static final int PROPERTY_CHECKED = 0x00000001; private static final int PROPERTY_ENABLED = 0x00000002; private static final int PROPERTY_PASSWORD = 0x00000004; private static final int PROPERTY_FULL_SCREEN = 0x00000080; private static final int PROPERTY_SCROLLABLE = 0x00000100; private static final int PROPERTY_IMPORTANT_FOR_ACCESSIBILITY = 0x00000200; private static final int GET_SOURCE_PREFETCH_FLAGS = AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS | AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS | AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS; // Housekeeping private static final int MAX_POOL_SIZE = 10; private static final Object sPoolLock = new Object(); private static AccessibilityRecord sPool; private static int sPoolSize; private AccessibilityRecord mNext; private boolean mIsInPool; boolean mSealed; int mBooleanProperties = 0; int mCurrentItemIndex = UNDEFINED; int mItemCount = UNDEFINED; int mFromIndex = UNDEFINED; int mToIndex = UNDEFINED; int mScrollX = UNDEFINED; int mScrollY = UNDEFINED; int mMaxScrollX = UNDEFINED; int mMaxScrollY = UNDEFINED; int mAddedCount= UNDEFINED; int mRemovedCount = UNDEFINED; AccessibilityNodeInfo mSourceNode; int mSourceWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; CharSequence mClassName; CharSequence mContentDescription; CharSequence mBeforeText; Parcelable mParcelableData; final List mText = new ArrayList(); int mConnectionId = UNDEFINED; /* * Hide constructor. */ AccessibilityRecord() { } /** * Sets the event source. * * @param source The source. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setSource(View source) { setSource(source, UNDEFINED); } /** * Sets the source to be a virtual descendant of the given root. * If virtualDescendantId equals to {@link View#NO_ID} the root * is set as the source. *

* A virtual descendant is an imaginary View that is reported as a part of the view * hierarchy for accessibility purposes. This enables custom views that draw complex * content to report them selves as a tree of virtual views, thus conveying their * logical structure. *

* * @param root The root of the virtual subtree. * @param virtualDescendantId The id of the virtual descendant. */ public void setSource(View root, int virtualDescendantId) { enforceNotSealed(); boolean important = true; mSourceWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; clearSourceNode(); if (root != null) { if (virtualDescendantId == View.NO_ID || virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID || virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) { important = root.isImportantForAccessibility(); mSourceNode = root.createAccessibilityNodeInfo(); } else { AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider(); if (provider != null) { mSourceNode = provider.createAccessibilityNodeInfo(virtualDescendantId); } } mSourceWindowId = root.getAccessibilityWindowId(); } setBooleanProperty(PROPERTY_IMPORTANT_FOR_ACCESSIBILITY, important); } /** * Set the source directly to an AccessibilityNodeInfo rather than indirectly via a View * * @param info The source * * @hide */ public void setSource(AccessibilityNodeInfo info) { enforceNotSealed(); clearSourceNode(); mSourceWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; if (info != null) { mSourceNode = AccessibilityNodeInfo.obtain(info); setBooleanProperty(PROPERTY_IMPORTANT_FOR_ACCESSIBILITY, mSourceNode.isImportantForAccessibility()); mSourceWindowId = info.getWindowId(); } } /** * Gets the {@link AccessibilityNodeInfo} of the event source. *

* Note: It is a client responsibility to recycle the received info * by calling {@link AccessibilityNodeInfo#recycle() AccessibilityNodeInfo#recycle()} * to avoid creating of multiple instances. *

* @return The info of the source. */ public AccessibilityNodeInfo getSource() { enforceSealed(); if (mSourceNode != null) { return AccessibilityNodeInfo.obtain(mSourceNode); } return null; } /** * Sets the window id. * * @param windowId The window id. * * @hide */ public void setWindowId(int windowId) { mSourceWindowId = windowId; } /** * Gets the id of the window from which the event comes from. * * @return The window id. */ public int getWindowId() { return mSourceWindowId; } /** * Gets if the source is checked. * * @return True if the view is checked, false otherwise. */ public boolean isChecked() { return getBooleanProperty(PROPERTY_CHECKED); } /** * Sets if the source is checked. * * @param isChecked True if the view is checked, false otherwise. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setChecked(boolean isChecked) { enforceNotSealed(); setBooleanProperty(PROPERTY_CHECKED, isChecked); } /** * Gets if the source is enabled. * * @return True if the view is enabled, false otherwise. */ public boolean isEnabled() { return getBooleanProperty(PROPERTY_ENABLED); } /** * Sets if the source is enabled. * * @param isEnabled True if the view is enabled, false otherwise. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setEnabled(boolean isEnabled) { enforceNotSealed(); setBooleanProperty(PROPERTY_ENABLED, isEnabled); } /** * Gets if the source is a password field. * * @return True if the view is a password field, false otherwise. */ public boolean isPassword() { return getBooleanProperty(PROPERTY_PASSWORD); } /** * Sets if the source is a password field. * * @param isPassword True if the view is a password field, false otherwise. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setPassword(boolean isPassword) { enforceNotSealed(); setBooleanProperty(PROPERTY_PASSWORD, isPassword); } /** * Gets if the source is taking the entire screen. * * @return True if the source is full screen, false otherwise. */ public boolean isFullScreen() { return getBooleanProperty(PROPERTY_FULL_SCREEN); } /** * Sets if the source is taking the entire screen. * * @param isFullScreen True if the source is full screen, false otherwise. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setFullScreen(boolean isFullScreen) { enforceNotSealed(); setBooleanProperty(PROPERTY_FULL_SCREEN, isFullScreen); } /** * Gets if the source is scrollable. * * @return True if the source is scrollable, false otherwise. */ public boolean isScrollable() { return getBooleanProperty(PROPERTY_SCROLLABLE); } /** * Sets if the source is scrollable. * * @param scrollable True if the source is scrollable, false otherwise. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setScrollable(boolean scrollable) { enforceNotSealed(); setBooleanProperty(PROPERTY_SCROLLABLE, scrollable); } /** * Gets if the source is important for accessibility. * * Note: Used only internally to determine whether * to deliver the event to a given accessibility service since some * services may want to regard all views for accessibility while others * may want to regard only the important views for accessibility. * * @return True if the source is important for accessibility, * false otherwise. * * @hide */ public boolean isImportantForAccessibility() { return getBooleanProperty(PROPERTY_IMPORTANT_FOR_ACCESSIBILITY); } /** * Gets the number of items that can be visited. * * @return The number of items. */ public int getItemCount() { return mItemCount; } /** * Sets the number of items that can be visited. * * @param itemCount The number of items. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setItemCount(int itemCount) { enforceNotSealed(); mItemCount = itemCount; } /** * Gets the index of the source in the list of items the can be visited. * * @return The current item index. */ public int getCurrentItemIndex() { return mCurrentItemIndex; } /** * Sets the index of the source in the list of items that can be visited. * * @param currentItemIndex The current item index. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setCurrentItemIndex(int currentItemIndex) { enforceNotSealed(); mCurrentItemIndex = currentItemIndex; } /** * Gets the index of the first character of the changed sequence, * or the beginning of a text selection or the index of the first * visible item when scrolling. * * @return The index of the first character or selection * start or the first visible item. */ public int getFromIndex() { return mFromIndex; } /** * Sets the index of the first character of the changed sequence * or the beginning of a text selection or the index of the first * visible item when scrolling. * * @param fromIndex The index of the first character or selection * start or the first visible item. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setFromIndex(int fromIndex) { enforceNotSealed(); mFromIndex = fromIndex; } /** * Gets the index of text selection end or the index of the last * visible item when scrolling. * * @return The index of selection end or last item index. */ public int getToIndex() { return mToIndex; } /** * Sets the index of text selection end or the index of the last * visible item when scrolling. * * @param toIndex The index of selection end or last item index. */ public void setToIndex(int toIndex) { enforceNotSealed(); mToIndex = toIndex; } /** * Gets the scroll offset of the source left edge in pixels. * * @return The scroll. */ public int getScrollX() { return mScrollX; } /** * Sets the scroll offset of the source left edge in pixels. * * @param scrollX The scroll. */ public void setScrollX(int scrollX) { enforceNotSealed(); mScrollX = scrollX; } /** * Gets the scroll offset of the source top edge in pixels. * * @return The scroll. */ public int getScrollY() { return mScrollY; } /** * Sets the scroll offset of the source top edge in pixels. * * @param scrollY The scroll. */ public void setScrollY(int scrollY) { enforceNotSealed(); mScrollY = scrollY; } /** * Gets the max scroll offset of the source left edge in pixels. * * @return The max scroll. */ public int getMaxScrollX() { return mMaxScrollX; } /** * Sets the max scroll offset of the source left edge in pixels. * * @param maxScrollX The max scroll. */ public void setMaxScrollX(int maxScrollX) { enforceNotSealed(); mMaxScrollX = maxScrollX; } /** * Gets the max scroll offset of the source top edge in pixels. * * @return The max scroll. */ public int getMaxScrollY() { return mMaxScrollY; } /** * Sets the max scroll offset of the source top edge in pixels. * * @param maxScrollY The max scroll. */ public void setMaxScrollY(int maxScrollY) { enforceNotSealed(); mMaxScrollY = maxScrollY; } /** * Gets the number of added characters. * * @return The number of added characters. */ public int getAddedCount() { return mAddedCount; } /** * Sets the number of added characters. * * @param addedCount The number of added characters. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setAddedCount(int addedCount) { enforceNotSealed(); mAddedCount = addedCount; } /** * Gets the number of removed characters. * * @return The number of removed characters. */ public int getRemovedCount() { return mRemovedCount; } /** * Sets the number of removed characters. * * @param removedCount The number of removed characters. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setRemovedCount(int removedCount) { enforceNotSealed(); mRemovedCount = removedCount; } /** * Gets the class name of the source. * * @return The class name. */ public CharSequence getClassName() { return mClassName; } /** * Sets the class name of the source. * * @param className The lass name. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setClassName(CharSequence className) { enforceNotSealed(); mClassName = className; } /** * Gets the text of the event. The index in the list represents the priority * of the text. Specifically, the lower the index the higher the priority. * * @return The text. */ public List getText() { return mText; } /** * Sets the text before a change. * * @return The text before the change. */ public CharSequence getBeforeText() { return mBeforeText; } /** * Sets the text before a change. * * @param beforeText The text before the change. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setBeforeText(CharSequence beforeText) { enforceNotSealed(); mBeforeText = (beforeText == null) ? null : beforeText.subSequence(0, beforeText.length()); } /** * Gets the description of the source. * * @return The description. */ public CharSequence getContentDescription() { return mContentDescription; } /** * Sets the description of the source. * * @param contentDescription The description. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setContentDescription(CharSequence contentDescription) { enforceNotSealed(); mContentDescription = (contentDescription == null) ? null : contentDescription.subSequence(0, contentDescription.length()); } /** * Gets the {@link Parcelable} data. * * @return The parcelable data. */ public Parcelable getParcelableData() { return mParcelableData; } /** * Sets the {@link Parcelable} data of the event. * * @param parcelableData The parcelable data. * * @throws IllegalStateException If called from an AccessibilityService. */ public void setParcelableData(Parcelable parcelableData) { enforceNotSealed(); mParcelableData = parcelableData; } /** * Gets the id of the source node. * * @return The id. * * @hide */ public long getSourceNodeId() { return mSourceNode != null ? mSourceNode.getSourceNodeId() : UNDEFINED; } /** * 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; if (mSourceNode != null) { mSourceNode.setConnectionId(mConnectionId); } } /** * Sets if this instance is sealed. * * @param sealed Whether is sealed. * * @hide */ public void setSealed(boolean sealed) { mSealed = sealed; if (mSourceNode != null) { mSourceNode.setSealed(sealed); } } /** * Gets if this instance is sealed. * * @return Whether is sealed. */ boolean isSealed() { return mSealed; } /** * Enforces that this instance is sealed. * * @throws IllegalStateException If this instance is not sealed. */ 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. */ void enforceNotSealed() { if (isSealed()) { throw new IllegalStateException("Cannot perform this " + "action on a sealed instance."); } } /** * Gets the value of a boolean property. * * @param property The property. * @return The value. */ private boolean getBooleanProperty(int property) { return (mBooleanProperties & property) == property; } /** * Sets a boolean property. * * @param property The property. * @param value The value. */ private void setBooleanProperty(int property, boolean value) { if (value) { mBooleanProperties |= property; } else { mBooleanProperties &= ~property; } } /** * Returns a cached instance if such is available or a new one is * instantiated. The instance is initialized with data from the * given record. * * @return An instance. */ public static AccessibilityRecord obtain(AccessibilityRecord record) { AccessibilityRecord clone = AccessibilityRecord.obtain(); clone.init(record); return clone; } /** * Returns a cached instance if such is available or a new one is * instantiated. * * @return An instance. */ public static AccessibilityRecord obtain() { synchronized (sPoolLock) { if (sPool != null) { AccessibilityRecord record = sPool; sPool = sPool.mNext; sPoolSize--; record.mNext = null; record.mIsInPool = false; return record; } return new AccessibilityRecord(); } } /** * Return an instance back to be reused. *

* Note: You must not touch the object after calling this function. * * @throws IllegalStateException If the record is already recycled. */ public void recycle() { if (mIsInPool) { throw new IllegalStateException("Record already recycled!"); } clear(); synchronized (sPoolLock) { if (sPoolSize <= MAX_POOL_SIZE) { mNext = sPool; sPool = this; mIsInPool = true; sPoolSize++; } } } /** * Initialize this record from another one. * * @param record The to initialize from. */ void init(AccessibilityRecord record) { mSealed = record.mSealed; mBooleanProperties = record.mBooleanProperties; mCurrentItemIndex = record.mCurrentItemIndex; mItemCount = record.mItemCount; mFromIndex = record.mFromIndex; mToIndex = record.mToIndex; mScrollX = record.mScrollX; mScrollY = record.mScrollY; mMaxScrollX = record.mMaxScrollX; mMaxScrollY = record.mMaxScrollY; mAddedCount = record.mAddedCount; mRemovedCount = record.mRemovedCount; mClassName = record.mClassName; mContentDescription = record.mContentDescription; mBeforeText = record.mBeforeText; mParcelableData = record.mParcelableData; mText.addAll(record.mText); mSourceWindowId = record.mSourceWindowId; if (record.mSourceNode != null) { mSourceNode = AccessibilityNodeInfo.obtain(record.mSourceNode); } mConnectionId = record.mConnectionId; } /** * Clears the state of this instance. */ void clear() { mSealed = false; mBooleanProperties = 0; mCurrentItemIndex = UNDEFINED; mItemCount = UNDEFINED; mFromIndex = UNDEFINED; mToIndex = UNDEFINED; mScrollX = UNDEFINED; mScrollY = UNDEFINED; mMaxScrollX = UNDEFINED; mMaxScrollY = UNDEFINED; mAddedCount = UNDEFINED; mRemovedCount = UNDEFINED; mClassName = null; mContentDescription = null; mBeforeText = null; mParcelableData = null; mText.clear(); clearSourceNode(); mSourceWindowId = UNDEFINED; mConnectionId = UNDEFINED; } private void clearSourceNode() { if (mSourceNode != null) { mSourceNode.recycle(); mSourceNode = null; } } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append(" [ ClassName: " + mClassName); builder.append("; Text: " + mText); builder.append("; ContentDescription: " + mContentDescription); builder.append("; ItemCount: " + mItemCount); builder.append("; CurrentItemIndex: " + mCurrentItemIndex); builder.append("; IsEnabled: " + getBooleanProperty(PROPERTY_ENABLED)); builder.append("; IsPassword: " + getBooleanProperty(PROPERTY_PASSWORD)); builder.append("; IsChecked: " + getBooleanProperty(PROPERTY_CHECKED)); builder.append("; IsFullScreen: " + getBooleanProperty(PROPERTY_FULL_SCREEN)); builder.append("; Scrollable: " + getBooleanProperty(PROPERTY_SCROLLABLE)); builder.append("; BeforeText: " + mBeforeText); builder.append("; FromIndex: " + mFromIndex); builder.append("; ToIndex: " + mToIndex); builder.append("; ScrollX: " + mScrollX); builder.append("; ScrollY: " + mScrollY); builder.append("; MaxScrollX: " + mMaxScrollX); builder.append("; MaxScrollY: " + mMaxScrollY); builder.append("; AddedCount: " + mAddedCount); builder.append("; RemovedCount: " + mRemovedCount); builder.append("; ParcelableData: " + mParcelableData); builder.append(" ]"); return builder.toString(); } }