| /* |
| * 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.accessibilityservice.IAccessibilityServiceConnection; |
| 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. |
| * </p> |
| * <p> |
| * Once the accessibility event containing a record is dispatched the record is |
| * made immutable and calling a state mutation method generates an error. |
| * </p> |
| * <p> |
| * <strong>Note:</strong> Not all properties are applicable to all accessibility |
| * event types. For detailed information please refer to {@link AccessibilityEvent}. |
| * </p> |
| * |
| * @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; |
| |
| // 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; |
| 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; |
| long mSourceNodeId = AccessibilityNodeInfo.makeNodeId(View.NO_ID, View.NO_ID); |
| int mSourceWindowId = View.NO_ID; |
| |
| CharSequence mClassName; |
| CharSequence mContentDescription; |
| CharSequence mBeforeText; |
| Parcelable mParcelableData; |
| |
| final List<CharSequence> mText = new ArrayList<CharSequence>(); |
| IAccessibilityServiceConnection mConnection; |
| |
| /* |
| * 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, View.NO_ID); |
| } |
| |
| /** |
| * Sets the source to be a virtual descendant of the given <code>root</code>. |
| * If <code>virtualDescendantId</code> equals to {@link View#NO_ID} the root |
| * is set as the source. |
| * <p> |
| * 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. |
| * </p> |
| * |
| * @param root The root of the virtual subtree. |
| * @param virtualDescendantId The id of the virtual descendant. |
| */ |
| public void setSource(View root, int virtualDescendantId) { |
| enforceNotSealed(); |
| mSourceWindowId = (root != null) ? root.getAccessibilityWindowId() : View.NO_ID; |
| final int rootViewId = (root != null) ? root.getAccessibilityViewId() : View.NO_ID; |
| mSourceNodeId = AccessibilityNodeInfo.makeNodeId(rootViewId, virtualDescendantId); |
| } |
| |
| /** |
| * Gets the {@link AccessibilityNodeInfo} of the event source. |
| * <p> |
| * <strong>Note:</strong> It is a client responsibility to recycle the received info |
| * by calling {@link AccessibilityNodeInfo#recycle() AccessibilityNodeInfo#recycle()} |
| * to avoid creating of multiple instances. |
| * |
| * </p> |
| * @return The info of the source. |
| */ |
| public AccessibilityNodeInfo getSource() { |
| enforceSealed(); |
| if (mConnection == null || mSourceWindowId == View.NO_ID |
| || AccessibilityNodeInfo.getAccessibilityViewId(mSourceNodeId) == View.NO_ID) { |
| return null; |
| } |
| AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); |
| return client.findAccessibilityNodeInfoByAccessibilityId(mConnection, mSourceWindowId, |
| mSourceNodeId); |
| } |
| |
| /** |
| * Sets the connection for interacting with the AccessibilityManagerService. |
| * |
| * @param connection The connection. |
| * |
| * @hide |
| */ |
| public void setConnection(IAccessibilityServiceConnection connection) { |
| enforceNotSealed(); |
| mConnection = connection; |
| } |
| |
| /** |
| * 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 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<CharSequence> 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; |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * 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. |
| */ |
| 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 an 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. |
| * <p> |
| * <strong>Note:</strong> 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; |
| mSourceNodeId = record.mSourceNodeId; |
| mConnection = record.mConnection; |
| } |
| |
| /** |
| * 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(); |
| mSourceNodeId = AccessibilityNodeInfo.makeNodeId(View.NO_ID, View.NO_ID); |
| mSourceWindowId = View.NO_ID; |
| } |
| |
| @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(); |
| } |
| } |