diff options
| author | 2013-06-07 00:04:57 +0000 | |
|---|---|---|
| committer | 2013-06-07 00:04:57 +0000 | |
| commit | 494cb689d09a82025b9d892241e62aa149ce3239 (patch) | |
| tree | 68640872a29eb46afb09aabba2dce498c5acc750 | |
| parent | fd6f77dc6accdc5d7ae4ffd5bb1d0d82fe448405 (diff) | |
| parent | 6254f4806dd3db53b7380e77fbb183065685573e (diff) | |
Merge "Optimizing AccessibilityNodeInfo caching."
| -rw-r--r-- | api/current.txt | 7 | ||||
| -rw-r--r-- | core/java/android/view/AccessibilityInteractionController.java | 4 | ||||
| -rw-r--r-- | core/java/android/view/View.java | 164 | ||||
| -rw-r--r-- | core/java/android/view/ViewGroup.java | 38 | ||||
| -rw-r--r-- | core/java/android/view/ViewParent.java | 9 | ||||
| -rw-r--r-- | core/java/android/view/ViewRootImpl.java | 41 | ||||
| -rw-r--r-- | core/java/android/view/accessibility/AccessibilityEvent.java | 46 | ||||
| -rw-r--r-- | core/java/android/view/accessibility/AccessibilityInteractionClient.java | 18 | ||||
| -rw-r--r-- | core/java/android/view/accessibility/AccessibilityNodeInfo.java | 100 | ||||
| -rw-r--r-- | core/java/android/view/accessibility/AccessibilityNodeInfoCache.java | 114 | ||||
| -rw-r--r-- | core/java/android/view/accessibility/AccessibilityRecord.java | 2 | ||||
| -rw-r--r-- | core/java/android/widget/AdapterView.java | 4 | ||||
| -rw-r--r-- | core/java/android/widget/CheckedTextView.java | 2 | ||||
| -rw-r--r-- | core/java/android/widget/CompoundButton.java | 2 | ||||
| -rw-r--r-- | core/java/android/widget/TextView.java | 12 |
15 files changed, 381 insertions, 182 deletions
diff --git a/api/current.txt b/api/current.txt index 27038011c81b..87891b51e984 100644 --- a/api/current.txt +++ b/api/current.txt @@ -27314,6 +27314,7 @@ package android.view.accessibility { method public int describeContents(); method public static java.lang.String eventTypeToString(int); method public int getAction(); + method public int getContentChangeType(); method public long getEventTime(); method public int getEventType(); method public int getMovementGranularity(); @@ -27325,11 +27326,14 @@ package android.view.accessibility { method public static android.view.accessibility.AccessibilityEvent obtain(android.view.accessibility.AccessibilityEvent); method public static android.view.accessibility.AccessibilityEvent obtain(); method public void setAction(int); + method public void setContentChangeType(int); method public void setEventTime(long); method public void setEventType(int); method public void setMovementGranularity(int); method public void setPackageName(java.lang.CharSequence); method public void writeToParcel(android.os.Parcel, int); + field public static final int CONTENT_CHANGE_TYPE_NODE = 1; // 0x1 + field public static final int CONTENT_CHANGE_TYPE_SUBTREE = 0; // 0x0 field public static final android.os.Parcelable.Creator CREATOR; field public static final int INVALID_POSITION = -1; // 0xffffffff field public static final deprecated int MAX_TEXT_LENGTH = 500; // 0x1f4 @@ -27391,10 +27395,12 @@ package android.view.accessibility { method public int getActions(); method public void getBoundsInParent(android.graphics.Rect); method public void getBoundsInScreen(android.graphics.Rect); + method public android.os.Bundle getBundle(); method public android.view.accessibility.AccessibilityNodeInfo getChild(int); method public int getChildCount(); method public java.lang.CharSequence getClassName(); method public java.lang.CharSequence getContentDescription(); + method public int getInputType(); method public android.view.accessibility.AccessibilityNodeInfo getLabelFor(); method public android.view.accessibility.AccessibilityNodeInfo getLabeledBy(); method public int getMovementGranularities(); @@ -27438,6 +27444,7 @@ package android.view.accessibility { method public void setEnabled(boolean); method public void setFocusable(boolean); method public void setFocused(boolean); + method public void setInputType(int); method public void setLabelFor(android.view.View); method public void setLabelFor(android.view.View, int); method public void setLabeledBy(android.view.View); diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index 2d6453e67b5d..e835a9708169 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -406,6 +406,10 @@ final class AccessibilityInteractionController { if (host == null || !ViewRootImpl.isViewDescendantOf(host, root)) { break; } + // The focused view not shown, we failed. + if (!isShown(host)) { + break; + } // If the host has a provider ask this provider to search for the // focus instead fetching all provider nodes to do the search here. AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider(); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 970d041fb324..55a8f747b5b2 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -1564,6 +1564,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private int mAccessibilityCursorPosition = ACCESSIBILITY_CURSOR_POSITION_UNDEFINED; + SendViewStateChangedAccessibilityEvent mSendViewStateChangedAccessibilityEvent; + /** * The view's tag. * {@hide} @@ -2136,13 +2138,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Flag indicating whether a view has accessibility focus. */ - static final int PFLAG2_ACCESSIBILITY_FOCUSED = 0x00000040 << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT; + static final int PFLAG2_ACCESSIBILITY_FOCUSED = 0x04000000; /** - * Flag indicating whether a view state for accessibility has changed. + * Flag whether the accessibility state of the subtree rooted at this view changed. */ - static final int PFLAG2_ACCESSIBILITY_STATE_CHANGED = 0x00000080 - << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT; + static final int PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED = 0x08000000; /** * Flag indicating whether a view failed the quickReject() check in draw(). This condition @@ -4454,10 +4455,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, onFocusChanged(true, direction, previouslyFocusedRect); refreshDrawableState(); - - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - notifyAccessibilityStateChanged(); - } } } @@ -4561,10 +4558,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (!rootViewRequestFocus()) { notifyGlobalFocusCleared(this); } - - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - notifyAccessibilityStateChanged(); - } } } @@ -4593,10 +4586,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, onFocusChanged(false, 0, null); refreshDrawableState(); - - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - notifyAccessibilityStateChanged(); - } } } @@ -4647,9 +4636,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { if (gainFocus) { - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); - } + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); + } else { + notifyViewAccessibilityStateChangedIfNeeded(); } InputMethodManager imm = InputMethodManager.peekInstance(); @@ -4706,6 +4695,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see AccessibilityDelegate */ public void sendAccessibilityEvent(int eventType) { + // Excluded views do not send accessibility events. + if (!includeForAccessibility()) { + return; + } if (mAccessibilityDelegate != null) { mAccessibilityDelegate.sendAccessibilityEvent(this, eventType); } else { @@ -5364,9 +5357,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mContentDescription = contentDescription; final boolean nonEmptyDesc = contentDescription != null && contentDescription.length() > 0; if (nonEmptyDesc && getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { - setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); + setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); + notifySubtreeAccessibilityStateChangedIfNeeded(); + } else { + notifyViewAccessibilityStateChangedIfNeeded(); } - notifyAccessibilityStateChanged(); } /** @@ -6646,7 +6641,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } invalidate(); sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED); - notifyAccessibilityStateChanged(); return true; } return false; @@ -6709,7 +6703,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_FOCUSED; invalidate(); sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED); - notifyAccessibilityStateChanged(); } } @@ -6883,11 +6876,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #IMPORTANT_FOR_ACCESSIBILITY_AUTO */ public void setImportantForAccessibility(int mode) { + final boolean oldIncludeForAccessibility = includeForAccessibility(); if (mode != getImportantForAccessibility()) { mPrivateFlags2 &= ~PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK; mPrivateFlags2 |= (mode << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT) & PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK; - notifyAccessibilityStateChanged(); + if (oldIncludeForAccessibility != includeForAccessibility()) { + notifySubtreeAccessibilityStateChangedIfNeeded(); + } else { + notifyViewAccessibilityStateChangedIfNeeded(); + } } } @@ -6994,25 +6992,44 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Notifies accessibility services that some view's important for - * accessibility state has changed. Note that such notifications - * are made at most once every + * Notifies that the accessibility state of this view changed. The change + * is local to this view and does not represent structural changes such + * as children and parent. For example, the view became focusable. The + * notification is at at most once every * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()} - * to avoid unnecessary load to the system. Also once a view has - * made a notifucation this method is a NOP until the notification has - * been sent to clients. + * to avoid unnecessary load to the system. Also once a view has a pending + * notifucation this method is a NOP until the notification has been sent. * * @hide + */ + public void notifyViewAccessibilityStateChangedIfNeeded() { + if (!AccessibilityManager.getInstance(mContext).isEnabled()) { + return; + } + if (mSendViewStateChangedAccessibilityEvent == null) { + mSendViewStateChangedAccessibilityEvent = + new SendViewStateChangedAccessibilityEvent(); + } + mSendViewStateChangedAccessibilityEvent.runOrPost(); + } + + /** + * Notifies that the accessibility state of this view changed. The change + * is *not* local to this view and does represent structural changes such + * as children and parent. For example, the view size changed. The + * notification is at at most once every + * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()} + * to avoid unnecessary load to the system. Also once a view has a pending + * notifucation this method is a NOP until the notification has been sent. * - * TODO: Makse sure this method is called for any view state change - * that is interesting for accessilility purposes. + * @hide */ - public void notifyAccessibilityStateChanged() { + private void notifySubtreeAccessibilityStateChangedIfNeeded() { if (!AccessibilityManager.getInstance(mContext).isEnabled()) { return; } - if ((mPrivateFlags2 & PFLAG2_ACCESSIBILITY_STATE_CHANGED) == 0) { - mPrivateFlags2 |= PFLAG2_ACCESSIBILITY_STATE_CHANGED; + if ((mPrivateFlags2 & PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED) == 0) { + mPrivateFlags2 |= PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED; if (mParent != null) { mParent.childAccessibilityStateChanged(this); } @@ -7020,13 +7037,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Reset the state indicating the this view has requested clients - * interested in its accessibility state to be notified. - * - * @hide + * Reset the flag indicating the accessibility state of the subtree rooted + * at this view changed. */ - public void resetAccessibilityStateChanged() { - mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_STATE_CHANGED; + void resetSubtreeAccessibilityStateChanged() { + mPrivateFlags2 &= ~PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED; } /** @@ -7139,7 +7154,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, || getAccessibilitySelectionEnd() != end) && (start == end)) { setAccessibilitySelection(start, end); - notifyAccessibilityStateChanged(); + notifyViewAccessibilityStateChangedIfNeeded(); return true; } } break; @@ -8560,6 +8575,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param mask Constant indicating the bit range that should be changed */ void setFlags(int flags, int mask) { + final boolean accessibilityEnabled = + AccessibilityManager.getInstance(mContext).isEnabled(); + final boolean oldIncludeForAccessibility = accessibilityEnabled + ? includeForAccessibility() : false; + int old = mViewFlags; mViewFlags = (mViewFlags & ~mask) | (flags & mask); @@ -8584,9 +8604,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ if (mParent != null) mParent.focusableViewAvailable(this); } - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - notifyAccessibilityStateChanged(); - } } final int newVisibility = flags & VISIBILITY_MASK; @@ -8707,10 +8724,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } - if (AccessibilityManager.getInstance(mContext).isEnabled() - && ((changed & FOCUSABLE) != 0 || (changed & CLICKABLE) != 0 - || (changed & LONG_CLICKABLE) != 0 || (changed & ENABLED) != 0)) { - notifyAccessibilityStateChanged(); + if (accessibilityEnabled) { + if ((changed & FOCUSABLE_MASK) != 0 || (changed & VISIBILITY_MASK) != 0 + || (changed & CLICKABLE) != 0 || (changed & LONG_CLICKABLE) != 0) { + if (oldIncludeForAccessibility != includeForAccessibility()) { + notifySubtreeAccessibilityStateChangedIfNeeded(); + } else { + notifyViewAccessibilityStateChangedIfNeeded(); + } + } + if ((changed & ENABLED_MASK) != 0) { + notifyViewAccessibilityStateChangedIfNeeded(); + } } } @@ -11757,6 +11782,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, jumpDrawablesToCurrentState(); clearAccessibilityFocus(); + resetSubtreeAccessibilityStateChanged(); + if (isFocused()) { InputMethodManager imm = InputMethodManager.peekInstance(); imm.focusIn(this); @@ -12052,8 +12079,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mCurrentAnimation = null; mCurrentScene = null; - - resetAccessibilityStateChanged(); } private void cleanupDraw() { @@ -14438,6 +14463,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags |= drawn; mBackgroundSizeChanged = true; + + notifySubtreeAccessibilityStateChangedIfNeeded(); } return changed; } @@ -15202,9 +15229,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidate(true); refreshDrawableState(); dispatchSetSelected(selected); - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - notifyAccessibilityStateChanged(); - } + notifyViewAccessibilityStateChangedIfNeeded(); } } @@ -18819,6 +18844,37 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + private class SendViewStateChangedAccessibilityEvent implements Runnable { + private boolean mPosted; + private long mLastEventTimeMillis; + + public void run() { + mPosted = false; + mLastEventTimeMillis = SystemClock.uptimeMillis(); + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + AccessibilityEvent event = AccessibilityEvent.obtain(); + event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + event.setContentChangeType(AccessibilityEvent.CONTENT_CHANGE_TYPE_NODE); + sendAccessibilityEventUnchecked(event); + } + } + + public void runOrPost() { + if (mPosted) { + return; + } + final long timeSinceLastMillis = SystemClock.uptimeMillis() - mLastEventTimeMillis; + final long minEventIntevalMillis = + ViewConfiguration.getSendRecurringAccessibilityEventsInterval(); + if (timeSinceLastMillis >= minEventIntevalMillis) { + run(); + } else { + postDelayed(this, minEventIntevalMillis - timeSinceLastMillis); + mPosted = true; + } + } + } + /** * Dump all private flags in readable format, useful for documentation and * sanity checking. diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 119ba729ad18..caed4a27d495 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -1698,16 +1698,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** - * @hide - */ - @Override - public void childAccessibilityStateChanged(View child) { - if (mParent != null) { - mParent.childAccessibilityStateChanged(child); - } - } - - /** * Implement this method to intercept hover events before they are handled * by child views. * <p> @@ -2534,13 +2524,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @hide */ @Override - public void resetAccessibilityStateChanged() { - super.resetAccessibilityStateChanged(); + public void childAccessibilityStateChanged(View root) { + if (mParent != null) { + mParent.childAccessibilityStateChanged(root); + } + } + + @Override + void resetSubtreeAccessibilityStateChanged() { + super.resetSubtreeAccessibilityStateChanged(); View[] children = mChildren; final int childCount = mChildrenCount; for (int i = 0; i < childCount; i++) { - View child = children[i]; - child.resetAccessibilityStateChanged(); + children[i].resetSubtreeAccessibilityStateChanged(); } } @@ -3466,7 +3462,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } private void clearCachedLayoutMode() { - if (!getBooleanFlag(FLAG_LAYOUT_MODE_WAS_EXPLICITLY_SET)) { + if (!hasBooleanFlag(FLAG_LAYOUT_MODE_WAS_EXPLICITLY_SET)) { mLayoutMode = LAYOUT_MODE_UNDEFINED; } } @@ -3597,6 +3593,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (child.hasTransientState()) { childHasTransientStateChanged(child, true); } + + if (child.isImportantForAccessibility() && child.getVisibility() != View.GONE) { + childAccessibilityStateChanged(child); + } } private void addInArray(View child, int index) { @@ -3836,6 +3836,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } onViewRemoved(view); + + if (view.isImportantForAccessibility() && view.getVisibility() != View.GONE) { + childAccessibilityStateChanged(view); + } } /** @@ -4786,7 +4790,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager setBooleanFlag(FLAG_USE_CHILD_DRAWING_ORDER, enabled); } - private boolean getBooleanFlag(int flag) { + private boolean hasBooleanFlag(int flag) { return (mGroupFlags & flag) == flag; } @@ -4854,7 +4858,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager void invalidateInheritedLayoutMode(int layoutModeOfRoot) { if (mLayoutMode == LAYOUT_MODE_UNDEFINED || mLayoutMode == layoutModeOfRoot || - getBooleanFlag(FLAG_LAYOUT_MODE_WAS_EXPLICITLY_SET)) { + hasBooleanFlag(FLAG_LAYOUT_MODE_WAS_EXPLICITLY_SET)) { return; } setLayoutMode(LAYOUT_MODE_UNDEFINED, false); diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java index d79aa7efad4d..2ebc1a19ed54 100644 --- a/core/java/android/view/ViewParent.java +++ b/core/java/android/view/ViewParent.java @@ -292,13 +292,14 @@ public interface ViewParent { public ViewParent getParentForAccessibility(); /** - * A child notifies its parent that its state for accessibility has changed. - * That is some of the child properties reported to accessibility services has - * changed, hence the interested services have to be notified for the new state. + * A child notifies its parent that the accessibility state of a subtree rooted + * at a given node changed. That is the structure of the subtree is different. + * + * @param The root of the changed subtree. * * @hide */ - public void childAccessibilityStateChanged(View child); + public void childAccessibilityStateChanged(View root); /** * Tells if this view parent can resolve the layout direction. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 62e0d3dada03..a5f92de09294 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1728,10 +1728,6 @@ public final class ViewRootImpl implements ViewParent, if (triggerGlobalLayoutListener) { attachInfo.mRecomputeGlobalAttributes = false; attachInfo.mTreeObserver.dispatchOnGlobalLayout(); - - if (AccessibilityManager.getInstance(host.mContext).isEnabled()) { - postSendWindowContentChangedCallback(mView); - } } if (computesInternalInsets) { @@ -5713,15 +5709,7 @@ public final class ViewRootImpl implements ViewParent, mSendWindowContentChangedAccessibilityEvent = new SendWindowContentChangedAccessibilityEvent(); } - View oldSource = mSendWindowContentChangedAccessibilityEvent.mSource; - if (oldSource == null) { - mSendWindowContentChangedAccessibilityEvent.mSource = source; - mHandler.postDelayed(mSendWindowContentChangedAccessibilityEvent, - ViewConfiguration.getSendRecurringAccessibilityEventsInterval()); - } else { - mSendWindowContentChangedAccessibilityEvent.mSource = - getCommonPredecessor(oldSource, source); - } + mSendWindowContentChangedAccessibilityEvent.runOrPost(source); } /** @@ -6405,12 +6393,33 @@ public final class ViewRootImpl implements ViewParent, private class SendWindowContentChangedAccessibilityEvent implements Runnable { public View mSource; + public long mLastEventTimeMillis; public void run() { + mLastEventTimeMillis = SystemClock.uptimeMillis(); + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + AccessibilityEvent event = AccessibilityEvent.obtain(); + event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + event.setContentChangeType(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); + mSource.sendAccessibilityEventUnchecked(event); + } + mSource.resetSubtreeAccessibilityStateChanged(); + mSource = null; + } + + public void runOrPost(View source) { if (mSource != null) { - mSource.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); - mSource.resetAccessibilityStateChanged(); - mSource = null; + mSource = getCommonPredecessor(mSource, source); + return; + } + mSource = source; + final long timeSinceLastMillis = SystemClock.uptimeMillis() - mLastEventTimeMillis; + final long minEventIntevalMillis = + ViewConfiguration.getSendRecurringAccessibilityEventsInterval(); + if (timeSinceLastMillis >= minEventIntevalMillis) { + run(); + } else { + mSource.postDelayed(this, minEventIntevalMillis - timeSinceLastMillis); } } } diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index dbeca1f9bab1..82c8163b3923 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -326,6 +326,7 @@ import java.util.List; * <em>Properties:</em></br> * <ul> * <li>{@link #getEventType()} - The type of the event.</li> + * <li>{@link #getContentChangeType()} - The type of content change.</li> * <li>{@link #getSource()} - The source info (for registered clients).</li> * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> @@ -661,6 +662,18 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par public static final int TYPE_TOUCH_INTERACTION_END = 0x00200000; /** + * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event: + * The subtree rooted at the source node changed. + */ + public static final int CONTENT_CHANGE_TYPE_SUBTREE = 0; + + /** + * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event: + * Only the source node changed. + */ + public static final int CONTENT_CHANGE_TYPE_NODE = 1; + + /** * Mask for {@link AccessibilityEvent} all types. * * @see #TYPE_VIEW_CLICKED @@ -695,6 +708,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par private long mEventTime; int mMovementGranularity; int mAction; + int mContentChangeType; private final ArrayList<AccessibilityRecord> mRecords = new ArrayList<AccessibilityRecord>(); @@ -714,6 +728,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par mEventType = event.mEventType; mMovementGranularity = event.mMovementGranularity; mAction = event.mAction; + mContentChangeType = event.mContentChangeType; mEventTime = event.mEventTime; mPackageName = event.mPackageName; } @@ -777,6 +792,33 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par } /** + * Gets the type of node tree change signaled by an + * {@link #TYPE_WINDOW_CONTENT_CHANGED} event. + * + * @see #CONTENT_CHANGE_TYPE_NODE + * @see #CONTENT_CHANGE_TYPE_SUBTREE + * + * @return The change type. + */ + public int getContentChangeType() { + return mContentChangeType; + } + + /** + * Sets the type of node tree change signaled by an + * {@link #TYPE_WINDOW_CONTENT_CHANGED} event. + * + * @see #CONTENT_CHANGE_TYPE_NODE + * @see #CONTENT_CHANGE_TYPE_SUBTREE + * + * @param changeType The change type. + */ + public void setContentChangeType(int changeType) { + enforceNotSealed(); + mContentChangeType = changeType; + } + + /** * Sets the event type. * * @param eventType The event type. @@ -943,6 +985,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par mEventType = 0; mMovementGranularity = 0; mAction = 0; + mContentChangeType = 0; mPackageName = null; mEventTime = 0; while (!mRecords.isEmpty()) { @@ -961,6 +1004,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par mEventType = parcel.readInt(); mMovementGranularity = parcel.readInt(); mAction = parcel.readInt(); + mContentChangeType = parcel.readInt(); mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); mEventTime = parcel.readLong(); mConnectionId = parcel.readInt(); @@ -1013,6 +1057,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par parcel.writeInt(mEventType); parcel.writeInt(mMovementGranularity); parcel.writeInt(mAction); + parcel.writeInt(mContentChangeType); TextUtils.writeToParcel(mPackageName, parcel, 0); parcel.writeLong(mEventTime); parcel.writeInt(mConnectionId); @@ -1074,6 +1119,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par builder.append(super.toString()); if (DEBUG) { builder.append("\n"); + builder.append("; ContentChangeType: ").append(mContentChangeType); builder.append("; sourceWindowId: ").append(mSourceWindowId); builder.append("; mSourceNodeId: ").append(mSourceNodeId); for (int i = 0; i < mRecords.size(); i++) { diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index 84d7e720b31f..139df3e57af0 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -163,7 +163,7 @@ public final class AccessibilityInteractionClient public AccessibilityNodeInfo getRootInActiveWindow(int connectionId) { return findAccessibilityNodeInfoByAccessibilityId(connectionId, AccessibilityNodeInfo.ACTIVE_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID, - AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS); + false, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS); } /** @@ -177,18 +177,22 @@ public final class AccessibilityInteractionClient * where to start the search. Use * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} * to start from the root. + * @param bypassCache Whether to bypass the cache while looking for the node. * @param prefetchFlags flags to guide prefetching. * @return An {@link AccessibilityNodeInfo} if found, null otherwise. */ public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(int connectionId, - int accessibilityWindowId, long accessibilityNodeId, int prefetchFlags) { + int accessibilityWindowId, long accessibilityNodeId, boolean bypassCache, + int prefetchFlags) { try { IAccessibilityServiceConnection connection = getConnection(connectionId); if (connection != null) { - AccessibilityNodeInfo cachedInfo = sAccessibilityNodeInfoCache.get( - accessibilityNodeId); - if (cachedInfo != null) { - return cachedInfo; + if (!bypassCache) { + AccessibilityNodeInfo cachedInfo = sAccessibilityNodeInfoCache.get( + accessibilityNodeId); + if (cachedInfo != null) { + return cachedInfo; + } } final int interactionId = mInteractionIdCounter.getAndIncrement(); final boolean success = connection.findAccessibilityNodeInfoByAccessibilityId( @@ -350,7 +354,7 @@ public final class AccessibilityInteractionClient } } catch (RemoteException re) { if (DEBUG) { - Log.w(LOG_TAG, "Error while calling remote findAccessibilityFocus", re); + Log.w(LOG_TAG, "Error while calling remote findFocus", re); } } return null; diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index d9c9b69bfced..750e0226f2fe 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -21,6 +21,7 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.text.InputType; import android.util.Pools.SynchronizedPool; import android.util.SparseLongArray; import android.view.View; @@ -482,6 +483,9 @@ public class AccessibilityNodeInfo implements Parcelable { private int mTextSelectionStart = UNDEFINED; private int mTextSelectionEnd = UNDEFINED; + private int mInputType = InputType.TYPE_NULL; + + private Bundle mBundle; private int mConnectionId = UNDEFINED; @@ -594,16 +598,20 @@ public class AccessibilityNodeInfo implements Parcelable { * since it represents a view that is no longer in the view tree and should * be recycled. * </p> + * + * @param bypassCache Whether to bypass the cache. * @return Whether the refresh succeeded. + * + * @hide */ - public boolean refresh() { + public boolean refresh(boolean bypassCache) { enforceSealed(); if (!canPerformRequestOverConnection(mSourceNodeId)) { return false; } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); AccessibilityNodeInfo refreshedInfo = client.findAccessibilityNodeInfoByAccessibilityId( - mConnectionId, mWindowId, mSourceNodeId, 0); + mConnectionId, mWindowId, mSourceNodeId, bypassCache, 0); if (refreshedInfo == null) { return false; } @@ -613,6 +621,19 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Refreshes this info with the latest state of the view it represents. + * <p> + * <strong>Note:</strong> If this method returns false this info is obsolete + * since it represents a view that is no longer in the view tree and should + * be recycled. + * </p> + * @return Whether the refresh succeeded. + */ + public boolean refresh() { + return refresh(false); + } + + /** * @return The ids of the children. * * @hide @@ -652,7 +673,7 @@ public class AccessibilityNodeInfo implements Parcelable { final long childId = mChildNodeIds.get(index); AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mWindowId, - childId, FLAG_PREFETCH_DESCENDANTS); + childId, false, FLAG_PREFETCH_DESCENDANTS); } /** @@ -878,7 +899,7 @@ public class AccessibilityNodeInfo implements Parcelable { } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, - mWindowId, mParentNodeId, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); + mWindowId, mParentNodeId, false, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); } /** @@ -1470,7 +1491,7 @@ public class AccessibilityNodeInfo implements Parcelable { } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, - mWindowId, mLabelForId, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); + mWindowId, mLabelForId, false, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); } /** @@ -1527,7 +1548,7 @@ public class AccessibilityNodeInfo implements Parcelable { } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, - mWindowId, mLabeledById, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); + mWindowId, mLabeledById, false, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); } /** @@ -1600,6 +1621,52 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Gets the input type of the source as defined by {@link InputType}. + * + * @return The input type. + */ + public int getInputType() { + return mInputType; + } + + /** + * Sets the input type of the source as defined by {@link InputType}. + * <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 inputType The input type. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setInputType(int inputType) { + mInputType = inputType; + } + + /** + * Gets an optional bundle with additional data. The bundle + * is lazily created and never <code>null</code>. + * <p> + * <strong>Note:</strong> It is recommended to use the package + * name of your application as a prefix for the keys to avoid + * collisions which may confuse an accessibility service if the + * same key has different meaning when emitted from different + * applications. + * </p> + * + * @return The bundle. + */ + public Bundle getBundle() { + if (mBundle == null) { + mBundle = new Bundle(); + } + return mBundle; + } + + /** * Gets the value of a boolean property. * * @param property The property. @@ -1845,6 +1912,14 @@ public class AccessibilityNodeInfo implements Parcelable { parcel.writeInt(mTextSelectionStart); parcel.writeInt(mTextSelectionEnd); + parcel.writeInt(mInputType); + + if (mBundle != null) { + parcel.writeInt(1); + parcel.writeBundle(mBundle); + } else { + parcel.writeInt(0); + } // 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. @@ -1880,6 +1955,10 @@ public class AccessibilityNodeInfo implements Parcelable { } mTextSelectionStart = other.mTextSelectionStart; mTextSelectionEnd = other.mTextSelectionEnd; + mInputType = other.mInputType; + if (other.mBundle != null && !other.mBundle.isEmpty()) { + getBundle().putAll(other.mBundle); + } } /** @@ -1927,6 +2006,11 @@ public class AccessibilityNodeInfo implements Parcelable { mTextSelectionStart = parcel.readInt(); mTextSelectionEnd = parcel.readInt(); + mInputType = parcel.readInt(); + + if (parcel.readInt() == 1) { + getBundle().putAll(parcel.readBundle()); + } } /** @@ -1953,6 +2037,10 @@ public class AccessibilityNodeInfo implements Parcelable { mActions = 0; mTextSelectionStart = UNDEFINED; mTextSelectionEnd = UNDEFINED; + mInputType = InputType.TYPE_NULL; + if (mBundle != null) { + mBundle.clear(); + } } /** diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java index 28518aab8ab9..dded74cd9918 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java @@ -39,9 +39,9 @@ public class AccessibilityNodeInfoCache { private static final boolean ENABLED = true; - private static final boolean DEBUG = false; + private static final boolean DEBUG = true; - private static final boolean CHECK_INTEGRITY = true; + private static final boolean CHECK_INTEGRITY_IF_DEBUGGABLE_BUILD = true; private final Object mLock = new Object(); @@ -67,16 +67,12 @@ public class AccessibilityNodeInfoCache { if (ENABLED) { final int eventType = event.getEventType(); switch (eventType) { - case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: { - // New window so we clear the cache. - mWindowId = event.getWindowId(); - clear(); - } break; + case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER: case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: { final int windowId = event.getWindowId(); + // If a new window, we clear the cache. if (mWindowId != windowId) { - // New window so we clear the cache. mWindowId = windowId; clear(); } @@ -87,34 +83,48 @@ public class AccessibilityNodeInfoCache { case AccessibilityEvent.TYPE_VIEW_SELECTED: case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED: case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: { - // Since we prefetch the descendants of a node we - // just remove the entire subtree since when the node - // is fetched we will gets its descendant anyway. - synchronized (mLock) { - final long sourceId = event.getSourceNodeId(); - clearSubTreeLocked(sourceId); - if (eventType == AccessibilityEvent.TYPE_VIEW_FOCUSED) { - clearSubtreeWithOldInputFocusLocked(sourceId); - } - if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) { - clearSubtreeWithOldAccessibilityFocusLocked(sourceId); - } - } + refreshCachedNode(event.getSourceNodeId()); } break; - case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: case AccessibilityEvent.TYPE_VIEW_SCROLLED: { + clearSubTreeLocked(event.getSourceNodeId()); + } break; + case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: { synchronized (mLock) { - final long accessibilityNodeId = event.getSourceNodeId(); - clearSubTreeLocked(accessibilityNodeId); + final long sourceId = event.getSourceNodeId(); + if (event.getContentChangeType() + == AccessibilityEvent.CONTENT_CHANGE_TYPE_NODE) { + refreshCachedNode(sourceId); + } else { + clearSubTreeLocked(sourceId); + } } } break; } - if (Build.IS_DEBUGGABLE && CHECK_INTEGRITY) { + if (Build.IS_DEBUGGABLE && CHECK_INTEGRITY_IF_DEBUGGABLE_BUILD) { checkIntegrity(); } } } + private void refreshCachedNode(long sourceId) { + if (DEBUG) { + Log.i(LOG_TAG, "Refresing cached node."); + } + synchronized (mLock) { + AccessibilityNodeInfo cachedInfo = mCacheImpl.get(sourceId); + // If the source is not in the cache - nothing to do. + if (cachedInfo == null) { + return; + } + // The node changed so we will just refresh it right now. + if (cachedInfo.refresh(false)) { + return; + } + // Weird, we could not refresh. Just evict the entire sub-tree. + clearSubTreeLocked(sourceId); + } + } + /** * Gets a cached {@link AccessibilityNodeInfo} given its accessibility node id. * @@ -131,7 +141,7 @@ public class AccessibilityNodeInfoCache { info = AccessibilityNodeInfo.obtain(info); } if (DEBUG) { - Log.i(LOG_TAG, "get(" + accessibilityNodeId + ") = " + info); +// Log.i(LOG_TAG, "get(" + accessibilityNodeId + ") = " + info); } return info; } @@ -149,7 +159,7 @@ public class AccessibilityNodeInfoCache { if (ENABLED) { synchronized(mLock) { if (DEBUG) { - Log.i(LOG_TAG, "add(" + info + ")"); +// Log.i(LOG_TAG, "add(" + info + ")"); } final long sourceId = info.getSourceNodeId(); @@ -212,6 +222,13 @@ public class AccessibilityNodeInfoCache { * @param rootNodeId The root id. */ private void clearSubTreeLocked(long rootNodeId) { + if (DEBUG) { + Log.i(LOG_TAG, "Clearing cached subtree."); + } + clearSubTreeRecursiveLocked(rootNodeId); + } + + private void clearSubTreeRecursiveLocked(long rootNodeId) { AccessibilityNodeInfo current = mCacheImpl.get(rootNodeId); if (current == null) { return; @@ -221,41 +238,7 @@ public class AccessibilityNodeInfoCache { final int childCount = childNodeIds.size(); for (int i = 0; i < childCount; i++) { final long childNodeId = childNodeIds.valueAt(i); - clearSubTreeLocked(childNodeId); - } - } - - /** - * We are enforcing the invariant for a single input focus. - * - * @param currentInputFocusId The current input focused node. - */ - private void clearSubtreeWithOldInputFocusLocked(long currentInputFocusId) { - final int cacheSize = mCacheImpl.size(); - for (int i = 0; i < cacheSize; i++) { - AccessibilityNodeInfo info = mCacheImpl.valueAt(i); - final long infoSourceId = info.getSourceNodeId(); - if (infoSourceId != currentInputFocusId && info.isFocused()) { - clearSubTreeLocked(infoSourceId); - return; - } - } - } - - /** - * We are enforcing the invariant for a single accessibility focus. - * - * @param currentAccessibilityFocusId The current input focused node. - */ - private void clearSubtreeWithOldAccessibilityFocusLocked(long currentAccessibilityFocusId) { - final int cacheSize = mCacheImpl.size(); - for (int i = 0; i < cacheSize; i++) { - AccessibilityNodeInfo info = mCacheImpl.valueAt(i); - final long infoSourceId = info.getSourceNodeId(); - if (infoSourceId != currentAccessibilityFocusId && info.isAccessibilityFocused()) { - clearSubTreeLocked(infoSourceId); - return; - } + clearSubTreeRecursiveLocked(childNodeId); } } @@ -327,16 +310,17 @@ public class AccessibilityNodeInfoCache { } // Check for disconnected nodes or ones from another window. - final int cacheSize = mCacheImpl.size(); - for (int i = 0; i < cacheSize; i++) { + for (int i = 0; i < mCacheImpl.size(); i++) { AccessibilityNodeInfo info = mCacheImpl.valueAt(i); if (!seen.contains(info)) { if (info.getWindowId() == windowId) { - Log.e(LOG_TAG, "Disconneced node: "); + Log.e(LOG_TAG, "Disconneced node: " + info); } else { Log.e(LOG_TAG, "Node from: " + info.getWindowId() + " not from:" + windowId + " " + info); } + mCacheImpl.removeAt(i); + i--; } } } diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java index 7147c57c8b0d..3fcd2189066f 100644 --- a/core/java/android/view/accessibility/AccessibilityRecord.java +++ b/core/java/android/view/accessibility/AccessibilityRecord.java @@ -164,7 +164,7 @@ public class AccessibilityRecord { } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mSourceWindowId, - mSourceNodeId, GET_SOURCE_PREFETCH_FLAGS); + mSourceNodeId, false, GET_SOURCE_PREFETCH_FLAGS); } /** diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java index 502de314051b..4a2df58c5415 100644 --- a/core/java/android/widget/AdapterView.java +++ b/core/java/android/widget/AdapterView.java @@ -31,7 +31,6 @@ import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; -import android.view.accessibility.AccessibilityNodeProvider; /** * An AdapterView is a view whose children are determined by an {@link Adapter}. @@ -1034,8 +1033,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { checkSelectionChanged(); } - //TODO: Hmm, we do not know the old state so this is sub-optimal - notifyAccessibilityStateChanged(); + childAccessibilityStateChanged(this); } void checkSelectionChanged() { diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java index de8b80d45ec6..f1c3139f094b 100644 --- a/core/java/android/widget/CheckedTextView.java +++ b/core/java/android/widget/CheckedTextView.java @@ -93,7 +93,7 @@ public class CheckedTextView extends TextView implements Checkable { if (mChecked != checked) { mChecked = checked; refreshDrawableState(); - notifyAccessibilityStateChanged(); + notifyViewAccessibilityStateChangedIfNeeded(); } } diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java index 452ad1bd3e6f..c4406ac66d69 100644 --- a/core/java/android/widget/CompoundButton.java +++ b/core/java/android/widget/CompoundButton.java @@ -114,7 +114,7 @@ public abstract class CompoundButton extends Button implements Checkable { if (mChecked != checked) { mChecked = checked; refreshDrawableState(); - notifyAccessibilityStateChanged(); + notifyViewAccessibilityStateChangedIfNeeded(); // Avoid infinite recursions if setChecked() is called from a listener if (mBroadcasting) { diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 12d0c49aca3b..71baa9004680 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -1721,7 +1721,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener setText(mText); if (hasPasswordTransformationMethod()) { - notifyAccessibilityStateChanged(); + notifyViewAccessibilityStateChangedIfNeeded(); } } @@ -7318,7 +7318,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ protected void onSelectionChanged(int selStart, int selEnd) { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED); - notifyAccessibilityStateChanged(); } /** @@ -8131,6 +8130,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener info.setEditable(true); } + if (mEditor != null) { + info.setInputType(mEditor.mInputType); + } + if (!TextUtils.isEmpty(mText)) { info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); @@ -8163,7 +8166,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case AccessibilityNodeInfo.ACTION_COPY: { if (isFocused() && canCopy()) { if (onTextContextMenuItem(ID_COPY)) { - notifyAccessibilityStateChanged(); return true; } } @@ -8171,7 +8173,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case AccessibilityNodeInfo.ACTION_PASTE: { if (isFocused() && canPaste()) { if (onTextContextMenuItem(ID_PASTE)) { - notifyAccessibilityStateChanged(); return true; } } @@ -8179,7 +8180,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case AccessibilityNodeInfo.ACTION_CUT: { if (isFocused() && canCut()) { if (onTextContextMenuItem(ID_CUT)) { - notifyAccessibilityStateChanged(); return true; } } @@ -8198,7 +8198,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // No arguments clears the selection. if (start == end && end == -1) { Selection.removeSelection((Spannable) text); - notifyAccessibilityStateChanged(); return true; } if (start >= 0 && start <= end && end <= text.length()) { @@ -8207,7 +8206,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mEditor != null) { mEditor.startSelectionActionMode(); } - notifyAccessibilityStateChanged(); return true; } } |