From e4d31b3c103045d5b2b141a05084dced595cc64f Mon Sep 17 00:00:00 2001 From: Eugene Susla Date: Thu, 1 Jun 2017 11:16:42 -0700 Subject: [PATCH] Move A11y events throttling away from View(RootImpl) ..and also extract common code into a common superclass This also preserves the order of the throttled events (TYPE_VIEW_SCROLLED & TYPE_WINDOW_CONTENT_CHANGED) with regards to the rest of events by flushing any pending throttled events immediately if another event is requested to be sent. Test: ensure no new a11y CTS failures Change-Id: I948a16716521974393aaa1cf822d0a0324e9ce3a --- core/java/android/app/UiAutomation.java | 15 +- core/java/android/view/View.java | 184 +++++---------- core/java/android/view/ViewGroup.java | 42 ++-- core/java/android/view/ViewRootImpl.java | 158 +++---------- .../AccessibilityViewHierarchyState.java | 61 +++++ .../SendViewScrolledAccessibilityEvent.java | 58 +++++ ...SendWindowContentChangedAccessibilityEvent.java | 111 +++++++++ .../ThrottlingAccessibilityEventSender.java | 248 +++++++++++++++++++++ core/java/android/widget/AbsListView.java | 2 +- core/java/android/widget/AdapterView.java | 2 +- core/java/android/widget/CheckedTextView.java | 2 +- core/java/android/widget/CompoundButton.java | 2 +- core/java/android/widget/TextView.java | 6 +- .../com/android/internal/util/ObjectUtils.java | 12 + .../internal/widget/ResolverDrawerLayout.java | 4 +- .../printspooler/widget/PrintContentView.java | 3 +- 16 files changed, 621 insertions(+), 289 deletions(-) create mode 100644 core/java/android/view/accessibility/AccessibilityViewHierarchyState.java create mode 100644 core/java/android/view/accessibility/SendViewScrolledAccessibilityEvent.java create mode 100644 core/java/android/view/accessibility/SendWindowContentChangedAccessibilityEvent.java create mode 100644 core/java/android/view/accessibility/ThrottlingAccessibilityEventSender.java diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index 8f0168530273..ba39740be12e 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -24,7 +24,6 @@ import android.accessibilityservice.IAccessibilityServiceConnection; import android.annotation.NonNull; import android.annotation.TestApi; import android.graphics.Bitmap; -import android.graphics.Canvas; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; @@ -47,10 +46,14 @@ import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.IAccessibilityInteractionConnection; + +import com.android.internal.util.CollectionUtils; + import libcore.io.IoUtils; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.TimeoutException; @@ -580,6 +583,8 @@ public final class UiAutomation { // Execute the command *without* the lock being held. command.run(); + List eventsReceived = Collections.emptyList(); + // Acquire the lock and wait for the event. try { // Wait for the event. @@ -600,14 +605,14 @@ public final class UiAutomation { if (filter.accept(event)) { return event; } - event.recycle(); + eventsReceived = CollectionUtils.add(eventsReceived, event); } // Check if timed out and if not wait. final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis; if (remainingTimeMillis <= 0) { throw new TimeoutException("Expected event not received within: " - + timeoutMillis + " ms."); + + timeoutMillis + " ms, among " + eventsReceived); } synchronized (mLock) { if (mEventQueue.isEmpty()) { @@ -620,6 +625,10 @@ public final class UiAutomation { } } } finally { + for (int i = 0; i < CollectionUtils.size(eventsReceived); i++) { + AccessibilityEvent event = eventsReceived.get(i); + event.recycle(); + } synchronized (mLock) { mWaitingForEventDelivery = false; mEventQueue.clear(); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index cc63a62c5651..c02688ee4bfc 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -4407,7 +4407,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private CheckForLongPress mPendingCheckForLongPress; private CheckForTap mPendingCheckForTap = null; private PerformClick mPerformClick; - private SendViewScrolledAccessibilityEvent mSendViewScrolledAccessibilityEvent; private UnsetPressedState mUnsetPressedState; @@ -7156,7 +7155,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (gainFocus) { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); } else { - notifyViewAccessibilityStateChangedIfNeeded( + notifyAccessibilityStateChanged( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } @@ -8826,9 +8825,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final boolean nonEmptyDesc = contentDescription != null && contentDescription.length() > 0; if (nonEmptyDesc && getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } else { - notifyViewAccessibilityStateChangedIfNeeded( + notifyAccessibilityStateChanged( AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION); } } @@ -8861,7 +8860,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return; } mAccessibilityTraversalBeforeId = beforeId; - notifyViewAccessibilityStateChangedIfNeeded( + notifyAccessibilityStateChanged( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } @@ -8905,7 +8904,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return; } mAccessibilityTraversalAfterId = afterId; - notifyViewAccessibilityStateChangedIfNeeded( + notifyAccessibilityStateChanged( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } @@ -8948,7 +8947,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, && mID == View.NO_ID) { mID = generateViewId(); } - notifyViewAccessibilityStateChangedIfNeeded( + notifyAccessibilityStateChanged( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } @@ -10442,8 +10441,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (pflags3 != mPrivateFlags3) { mPrivateFlags3 = pflags3; - notifyViewAccessibilityStateChangedIfNeeded( - AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); + notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } @@ -11261,7 +11259,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK; mPrivateFlags2 |= (mode << PFLAG2_ACCESSIBILITY_LIVE_REGION_SHIFT) & PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK; - notifyViewAccessibilityStateChangedIfNeeded( + notifyAccessibilityStateChanged( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } @@ -11319,9 +11317,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags2 |= (mode << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT) & PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK; if (!maySkipNotify || oldIncludeForAccessibility != includeForAccessibility()) { - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } else { - notifyViewAccessibilityStateChangedIfNeeded( + notifyAccessibilityStateChanged( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } @@ -11496,25 +11494,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @hide */ - public void notifyViewAccessibilityStateChangedIfNeeded(int changeType) { - if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) { - return; - } - // If this is a live region, we should send a subtree change event - // from this view immediately. Otherwise, we can let it propagate up. - if (getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE) { - final AccessibilityEvent event = AccessibilityEvent.obtain(); - event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); - event.setContentChangeTypes(changeType); - sendAccessibilityEventUnchecked(event); - } else if (mParent != null) { - try { - mParent.notifySubtreeAccessibilityStateChanged(this, this, changeType); - } catch (AbstractMethodError e) { - Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + - " does not fully implement ViewParent", e); - } - } + public void notifyAccessibilityStateChanged(int changeType) { + notifyAccessibilityStateChanged(this, changeType); } /** @@ -11528,20 +11509,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @hide */ - public void notifySubtreeAccessibilityStateChangedIfNeeded() { + public void notifyAccessibilitySubtreeChanged() { + if ((mPrivateFlags2 & PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED) == 0) { + mPrivateFlags2 |= PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED; + notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); + } + } + + void notifyAccessibilityStateChanged(View source, int changeType) { if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) { return; } - if ((mPrivateFlags2 & PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED) == 0) { - mPrivateFlags2 |= PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED; - if (mParent != null) { - try { - mParent.notifySubtreeAccessibilityStateChanged( - this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); - } catch (AbstractMethodError e) { - Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + - " does not fully implement ViewParent", e); - } + if (mParent != null) { + try { + mParent.notifySubtreeAccessibilityStateChanged(this, source, changeType); + } catch (AbstractMethodError e) { + Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + + " does not fully implement ViewParent", e); } } } @@ -11563,8 +11547,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Reset the flag indicating the accessibility state of the subtree rooted * at this view changed. + * + * @hide */ - void resetSubtreeAccessibilityStateChanged() { + public void resetSubtreeAccessibilityStateChanged() { mPrivateFlags2 &= ~PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED; } @@ -11725,7 +11711,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, || getAccessibilitySelectionEnd() != end) && (start == end)) { setAccessibilitySelection(start, end); - notifyViewAccessibilityStateChangedIfNeeded( + notifyAccessibilityStateChanged( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); return true; } @@ -13713,7 +13699,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, ((!(mParent instanceof ViewGroup)) || ((ViewGroup) mParent).isShown())) { dispatchVisibilityAggregated(newVisibility == VISIBLE); } - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } } @@ -13759,13 +13745,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, || (changed & CLICKABLE) != 0 || (changed & LONG_CLICKABLE) != 0 || (changed & CONTEXT_CLICKABLE) != 0) { if (oldIncludeForAccessibility != includeForAccessibility()) { - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } else { - notifyViewAccessibilityStateChangedIfNeeded( + notifyAccessibilityStateChanged( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } else if ((changed & ENABLED_MASK) != 0) { - notifyViewAccessibilityStateChangedIfNeeded( + notifyAccessibilityStateChanged( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } @@ -13800,10 +13786,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param oldt Previous vertical scroll origin. */ protected void onScrollChanged(int l, int t, int oldl, int oldt) { - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - postSendViewScrolledAccessibilityEventCallback(l - oldl, t - oldt); + ViewRootImpl root = getViewRootImpl(); + if (root != null) { + root.getAccessibilityState() + .getSendViewScrolledAccessibilityEvent() + .post(this, /* dx */ l - oldl, /* dy */ t - oldt); } mBackgroundSizeChanged = true; @@ -14199,7 +14188,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } } @@ -14243,7 +14232,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } } @@ -14287,7 +14276,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } } @@ -14324,7 +14313,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } } @@ -14361,7 +14350,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } } @@ -14564,7 +14553,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (mTransformationInfo.mAlpha != alpha) { // Report visibility changes, which can affect children, to accessibility if ((alpha == 0) ^ (mTransformationInfo.mAlpha == 0)) { - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } mTransformationInfo.mAlpha = alpha; if (onSetAlpha((int) (alpha * 255))) { @@ -15066,7 +15055,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } } @@ -15100,7 +15089,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } } @@ -15270,7 +15259,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public void invalidateOutline() { rebuildOutline(); - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); invalidateViewProperty(false, false); } @@ -15465,7 +15454,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } invalidateParentIfNeeded(); } - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } } @@ -15513,7 +15502,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } invalidateParentIfNeeded(); } - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } } @@ -16391,18 +16380,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Post a callback to send a {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event. - * This event is sent at most once every - * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}. - */ - private void postSendViewScrolledAccessibilityEventCallback(int dx, int dy) { - if (mSendViewScrolledAccessibilityEvent == null) { - mSendViewScrolledAccessibilityEvent = new SendViewScrolledAccessibilityEvent(); - } - mSendViewScrolledAccessibilityEvent.post(dx, dy); - } - - /** * Called by a parent to request that a child update its values for mScrollX * and mScrollY if necessary. This will typically be done if the child is * animating a scroll using a {@link android.widget.Scroller Scroller} @@ -17657,7 +17634,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, removeUnsetPressCallback(); removeLongPressCallback(); removePerformClickCallback(); - cancel(mSendViewScrolledAccessibilityEvent); + if (mAttachInfo != null + && mAttachInfo.mViewRootImpl.mAccessibilityState != null + && mAttachInfo.mViewRootImpl.mAccessibilityState.isScrollEventSenderInitialized()) { + mAttachInfo.mViewRootImpl.mAccessibilityState + .getSendViewScrolledAccessibilityEvent() + .cancelIfPendingFor(this); + } stopNestedScroll(); // Anything that started animating right before detach should already @@ -20256,7 +20239,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mForegroundInfo.mBoundsChanged = true; } - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } return changed; } @@ -21683,7 +21666,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (selected) { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); } else { - notifyViewAccessibilityStateChangedIfNeeded( + notifyAccessibilityStateChanged( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } @@ -26240,53 +26223,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Resuable callback for sending - * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event. - */ - private class SendViewScrolledAccessibilityEvent implements Runnable { - public volatile boolean mIsPending; - public int mDeltaX; - public int mDeltaY; - - public void post(int dx, int dy) { - mDeltaX += dx; - mDeltaY += dy; - if (!mIsPending) { - mIsPending = true; - postDelayed(this, ViewConfiguration.getSendRecurringAccessibilityEventsInterval()); - } - } - - @Override - public void run() { - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - AccessibilityEvent event = AccessibilityEvent.obtain( - AccessibilityEvent.TYPE_VIEW_SCROLLED); - event.setScrollDeltaX(mDeltaX); - event.setScrollDeltaY(mDeltaY); - sendAccessibilityEventUnchecked(event); - } - reset(); - } - - private void reset() { - mIsPending = false; - mDeltaX = 0; - mDeltaY = 0; - } - } - - /** - * Remove the pending callback for sending a - * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event. - */ - private void cancel(@Nullable SendViewScrolledAccessibilityEvent callback) { - if (callback == null || !callback.mIsPending) return; - removeCallbacks(callback); - callback.reset(); - } - - /** *

* This class represents a delegate that can be registered in a {@link View} * to enhance accessibility support via composition rather via inheritance. diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 122df934111d..a19e1ab68b71 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -3637,44 +3637,34 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return ViewGroup.class.getName(); } - @Override - public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) { - // If this is a live region, we should send a subtree change event - // from this view. Otherwise, we can let it propagate up. - if (getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE) { - notifyViewAccessibilityStateChangedIfNeeded( - AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); - } else if (mParent != null) { - try { - mParent.notifySubtreeAccessibilityStateChanged(this, source, changeType); - } catch (AbstractMethodError e) { - Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + - " does not fully implement ViewParent", e); - } - } - } - /** @hide */ @Override - public void notifySubtreeAccessibilityStateChangedIfNeeded() { + public void notifyAccessibilitySubtreeChanged() { if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) { return; } // If something important for a11y is happening in this subtree, make sure it's dispatched // from a view that is important for a11y so it doesn't get lost. - if ((getImportantForAccessibility() != IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) - && !isImportantForAccessibility() && (getChildCount() > 0)) { + if (getImportantForAccessibility() != IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS + && !isImportantForAccessibility() + && getChildCount() > 0) { ViewParent a11yParent = getParentForAccessibility(); if (a11yParent instanceof View) { - ((View) a11yParent).notifySubtreeAccessibilityStateChangedIfNeeded(); + ((View) a11yParent).notifyAccessibilitySubtreeChanged(); return; } } - super.notifySubtreeAccessibilityStateChangedIfNeeded(); + super.notifyAccessibilitySubtreeChanged(); } @Override - void resetSubtreeAccessibilityStateChanged() { + public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) { + notifyAccessibilityStateChanged(source, changeType); + } + + /** @hide */ + @Override + public void resetSubtreeAccessibilityStateChanged() { super.resetSubtreeAccessibilityStateChanged(); View[] children = mChildren; final int childCount = mChildrenCount; @@ -5086,7 +5076,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } if (child.getVisibility() != View.GONE) { - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } if (mTransientIndices != null) { @@ -5356,7 +5346,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager dispatchViewRemoved(view); if (view.getVisibility() != View.GONE) { - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size(); @@ -6075,7 +6065,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (invalidate) { invalidateViewProperty(false, false); } - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } @Override diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 6c5091c28708..f81a4c33271a 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -89,9 +89,11 @@ import android.view.accessibility.AccessibilityManager.HighTextContrastChangeLis import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.accessibility.AccessibilityNodeProvider; +import android.view.accessibility.AccessibilityViewHierarchyState; import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.IAccessibilityInteractionConnection; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; +import android.view.accessibility.ThrottlingAccessibilityEventSender; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Interpolator; import android.view.inputmethod.InputMethodManager; @@ -113,7 +115,6 @@ import java.io.OutputStream; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; -import java.util.HashSet; import java.util.concurrent.CountDownLatch; /** @@ -460,10 +461,6 @@ public final class ViewRootImpl implements ViewParent, new AccessibilityInteractionConnectionManager(); final HighContrastTextManager mHighContrastTextManager; - SendWindowContentChangedAccessibilityEvent mSendWindowContentChangedAccessibilityEvent; - - HashSet mTempHashSet; - private final int mDensity; private final int mNoncompatDensity; @@ -478,6 +475,8 @@ public final class ViewRootImpl implements ViewParent, private boolean mNeedsRendererSetup; + protected AccessibilityViewHierarchyState mAccessibilityState; + /** * Consistency verifier for debugging purposes. */ @@ -7262,11 +7261,9 @@ public final class ViewRootImpl implements ViewParent, * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}. */ private void postSendWindowContentChangedCallback(View source, int changeType) { - if (mSendWindowContentChangedAccessibilityEvent == null) { - mSendWindowContentChangedAccessibilityEvent = - new SendWindowContentChangedAccessibilityEvent(); - } - mSendWindowContentChangedAccessibilityEvent.runOrPost(source, changeType); + getAccessibilityState() + .getSendWindowContentChangedAccessibilityEvent() + .runOrPost(source, changeType); } /** @@ -7274,9 +7271,18 @@ public final class ViewRootImpl implements ViewParent, * {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event. */ private void removeSendWindowContentChangedCallback() { - if (mSendWindowContentChangedAccessibilityEvent != null) { - mHandler.removeCallbacks(mSendWindowContentChangedAccessibilityEvent); + if (mAccessibilityState != null + && mAccessibilityState.isWindowContentChangedEventSenderInitialized()) { + ThrottlingAccessibilityEventSender.cancelIfPending( + mAccessibilityState.getSendWindowContentChangedAccessibilityEvent()); + } + } + + AccessibilityViewHierarchyState getAccessibilityState() { + if (mAccessibilityState == null) { + mAccessibilityState = new AccessibilityViewHierarchyState(); } + return mAccessibilityState; } @Override @@ -7314,12 +7320,8 @@ public final class ViewRootImpl implements ViewParent, return false; } - // Immediately flush pending content changed event (if any) to preserve event order - if (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED - && mSendWindowContentChangedAccessibilityEvent != null - && mSendWindowContentChangedAccessibilityEvent.mSource != null) { - mSendWindowContentChangedAccessibilityEvent.removeCallbacksAndRun(); - } + // Send any pending event to prevent reordering + flushPendingAccessibilityEvents(); // Intercept accessibility focus events fired by virtual nodes to keep // track of accessibility focus position in such nodes. @@ -7363,6 +7365,19 @@ public final class ViewRootImpl implements ViewParent, return true; } + /** @hide */ + public void flushPendingAccessibilityEvents() { + if (mAccessibilityState != null) { + if (mAccessibilityState.isScrollEventSenderInitialized()) { + mAccessibilityState.getSendViewScrolledAccessibilityEvent().sendNowIfPending(); + } + if (mAccessibilityState.isWindowContentChangedEventSenderInitialized()) { + mAccessibilityState.getSendWindowContentChangedAccessibilityEvent() + .sendNowIfPending(); + } + } + } + /** * Updates the focused virtual view, when necessary, in response to a * content changed event. @@ -7497,39 +7512,6 @@ public final class ViewRootImpl implements ViewParent, return View.TEXT_ALIGNMENT_RESOLVED_DEFAULT; } - private View getCommonPredecessor(View first, View second) { - if (mTempHashSet == null) { - mTempHashSet = new HashSet(); - } - HashSet seen = mTempHashSet; - seen.clear(); - View firstCurrent = first; - while (firstCurrent != null) { - seen.add(firstCurrent); - ViewParent firstCurrentParent = firstCurrent.mParent; - if (firstCurrentParent instanceof View) { - firstCurrent = (View) firstCurrentParent; - } else { - firstCurrent = null; - } - } - View secondCurrent = second; - while (secondCurrent != null) { - if (seen.contains(secondCurrent)) { - seen.clear(); - return secondCurrent; - } - ViewParent secondCurrentParent = secondCurrent.mParent; - if (secondCurrentParent instanceof View) { - secondCurrent = (View) secondCurrentParent; - } else { - secondCurrent = null; - } - } - seen.clear(); - return null; - } - void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( @@ -8140,80 +8122,6 @@ public final class ViewRootImpl implements ViewParent, } } - private class SendWindowContentChangedAccessibilityEvent implements Runnable { - private int mChangeTypes = 0; - - public View mSource; - public long mLastEventTimeMillis; - - @Override - public void run() { - // Protect against re-entrant code and attempt to do the right thing in the case that - // we're multithreaded. - View source = mSource; - mSource = null; - if (source == null) { - Log.e(TAG, "Accessibility content change has no source"); - return; - } - // The accessibility may be turned off while we were waiting so check again. - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - mLastEventTimeMillis = SystemClock.uptimeMillis(); - AccessibilityEvent event = AccessibilityEvent.obtain(); - event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); - event.setContentChangeTypes(mChangeTypes); - source.sendAccessibilityEventUnchecked(event); - } else { - mLastEventTimeMillis = 0; - } - // In any case reset to initial state. - source.resetSubtreeAccessibilityStateChanged(); - mChangeTypes = 0; - } - - public void runOrPost(View source, int changeType) { - if (mHandler.getLooper() != Looper.myLooper()) { - CalledFromWrongThreadException e = new CalledFromWrongThreadException("Only the " - + "original thread that created a view hierarchy can touch its views."); - // TODO: Throw the exception - Log.e(TAG, "Accessibility content change on non-UI thread. Future Android " - + "versions will throw an exception.", e); - // Attempt to recover. This code does not eliminate the thread safety issue, but - // it should force any issues to happen near the above log. - mHandler.removeCallbacks(this); - if (mSource != null) { - // Dispatch whatever was pending. It's still possible that the runnable started - // just before we removed the callbacks, and bad things will happen, but at - // least they should happen very close to the logged error. - run(); - } - } - if (mSource != null) { - // If there is no common predecessor, then mSource points to - // a removed view, hence in this case always prefer the source. - View predecessor = getCommonPredecessor(mSource, source); - mSource = (predecessor != null) ? predecessor : source; - mChangeTypes |= changeType; - return; - } - mSource = source; - mChangeTypes = changeType; - final long timeSinceLastMillis = SystemClock.uptimeMillis() - mLastEventTimeMillis; - final long minEventIntevalMillis = - ViewConfiguration.getSendRecurringAccessibilityEventsInterval(); - if (timeSinceLastMillis >= minEventIntevalMillis) { - removeCallbacksAndRun(); - } else { - mHandler.postDelayed(this, minEventIntevalMillis - timeSinceLastMillis); - } - } - - public void removeCallbacksAndRun() { - mHandler.removeCallbacks(this); - run(); - } - } - private static class KeyFallbackManager { // This is used to ensure that key-fallback events are only dispatched once. We attempt diff --git a/core/java/android/view/accessibility/AccessibilityViewHierarchyState.java b/core/java/android/view/accessibility/AccessibilityViewHierarchyState.java new file mode 100644 index 000000000000..447fafaae171 --- /dev/null +++ b/core/java/android/view/accessibility/AccessibilityViewHierarchyState.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2017 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.annotation.NonNull; +import android.annotation.Nullable; + +/** + * Accessibility-related state of a {@link android.view.ViewRootImpl} + * + * @hide + */ +public class AccessibilityViewHierarchyState { + private @Nullable SendViewScrolledAccessibilityEvent mSendViewScrolledAccessibilityEvent; + private @Nullable SendWindowContentChangedAccessibilityEvent + mSendWindowContentChangedAccessibilityEvent; + + /** + * @return a {@link SendViewScrolledAccessibilityEvent}, creating one if needed + */ + public @NonNull SendViewScrolledAccessibilityEvent getSendViewScrolledAccessibilityEvent() { + if (mSendViewScrolledAccessibilityEvent == null) { + mSendViewScrolledAccessibilityEvent = new SendViewScrolledAccessibilityEvent(); + } + return mSendViewScrolledAccessibilityEvent; + } + + public boolean isScrollEventSenderInitialized() { + return mSendViewScrolledAccessibilityEvent != null; + } + + /** + * @return a {@link SendWindowContentChangedAccessibilityEvent}, creating one if needed + */ + public @NonNull SendWindowContentChangedAccessibilityEvent + getSendWindowContentChangedAccessibilityEvent() { + if (mSendWindowContentChangedAccessibilityEvent == null) { + mSendWindowContentChangedAccessibilityEvent = + new SendWindowContentChangedAccessibilityEvent(); + } + return mSendWindowContentChangedAccessibilityEvent; + } + + public boolean isWindowContentChangedEventSenderInitialized() { + return mSendWindowContentChangedAccessibilityEvent != null; + } +} diff --git a/core/java/android/view/accessibility/SendViewScrolledAccessibilityEvent.java b/core/java/android/view/accessibility/SendViewScrolledAccessibilityEvent.java new file mode 100644 index 000000000000..40a1b6a28b98 --- /dev/null +++ b/core/java/android/view/accessibility/SendViewScrolledAccessibilityEvent.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017 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.annotation.NonNull; +import android.view.View; + +/** + * Sender for {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event. + * + * @hide + */ +public class SendViewScrolledAccessibilityEvent extends ThrottlingAccessibilityEventSender { + + public int mDeltaX; + public int mDeltaY; + + /** + * Post a scroll event to be sent for the given view + */ + public void post(View source, int dx, int dy) { + if (!isPendingFor(source)) sendNowIfPending(); + + mDeltaX += dx; + mDeltaY += dy; + + if (!isPendingFor(source)) scheduleFor(source); + } + + @Override + protected void performSendEvent(@NonNull View source) { + AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED); + event.setScrollDeltaX(mDeltaX); + event.setScrollDeltaY(mDeltaY); + source.sendAccessibilityEventUnchecked(event); + } + + @Override + protected void resetState(@NonNull View source) { + mDeltaX = 0; + mDeltaY = 0; + } +} diff --git a/core/java/android/view/accessibility/SendWindowContentChangedAccessibilityEvent.java b/core/java/android/view/accessibility/SendWindowContentChangedAccessibilityEvent.java new file mode 100644 index 000000000000..df38fba5ecfb --- /dev/null +++ b/core/java/android/view/accessibility/SendWindowContentChangedAccessibilityEvent.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2017 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 static com.android.internal.util.ObjectUtils.firstNotNull; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.view.View; +import android.view.ViewParent; + +import java.util.HashSet; + +/** + * @hide + */ +public class SendWindowContentChangedAccessibilityEvent + extends ThrottlingAccessibilityEventSender { + + private int mChangeTypes = 0; + + private HashSet mTempHashSet; + + @Override + protected void performSendEvent(@NonNull View source) { + AccessibilityEvent event = AccessibilityEvent.obtain(); + event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + event.setContentChangeTypes(mChangeTypes); + source.sendAccessibilityEventUnchecked(event); + } + + @Override + protected void resetState(@Nullable View source) { + if (source != null) { + source.resetSubtreeAccessibilityStateChanged(); + } + mChangeTypes = 0; + } + + /** + * Post the {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event with the given + * {@link AccessibilityEvent#getContentChangeTypes change type} for the given view + */ + public void runOrPost(View source, int changeType) { + if (source.getAccessibilityLiveRegion() != View.ACCESSIBILITY_LIVE_REGION_NONE) { + sendNowIfPending(); + mChangeTypes = changeType; + sendNow(source); + } else { + mChangeTypes |= changeType; + scheduleFor(source); + } + } + + @Override + protected @Nullable View tryMerge(@NonNull View oldSource, @NonNull View newSource) { + // If there is no common predecessor, then oldSource points to + // a removed view, hence in this case always prefer the newSource. + return firstNotNull( + getCommonPredecessor(oldSource, newSource), + newSource); + } + + private View getCommonPredecessor(View first, View second) { + if (mTempHashSet == null) { + mTempHashSet = new HashSet<>(); + } + HashSet seen = mTempHashSet; + seen.clear(); + View firstCurrent = first; + while (firstCurrent != null) { + seen.add(firstCurrent); + ViewParent firstCurrentParent = firstCurrent.getParent(); + if (firstCurrentParent instanceof View) { + firstCurrent = (View) firstCurrentParent; + } else { + firstCurrent = null; + } + } + View secondCurrent = second; + while (secondCurrent != null) { + if (seen.contains(secondCurrent)) { + seen.clear(); + return secondCurrent; + } + ViewParent secondCurrentParent = secondCurrent.getParent(); + if (secondCurrentParent instanceof View) { + secondCurrent = (View) secondCurrentParent; + } else { + secondCurrent = null; + } + } + seen.clear(); + return null; + } +} diff --git a/core/java/android/view/accessibility/ThrottlingAccessibilityEventSender.java b/core/java/android/view/accessibility/ThrottlingAccessibilityEventSender.java new file mode 100644 index 000000000000..66fa30107d2d --- /dev/null +++ b/core/java/android/view/accessibility/ThrottlingAccessibilityEventSender.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2017 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.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Handler; +import android.os.Looper; +import android.os.SystemClock; +import android.util.Log; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewRootImpl; +import android.view.ViewRootImpl.CalledFromWrongThreadException; + +/** + * A throttling {@link AccessibilityEvent} sender that relies on its currently associated + * 'source' view's {@link View#postDelayed delayed execution} to delay and possibly + * {@link #tryMerge merge} together any events that come in less than + * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval + * the configured amount of milliseconds} apart. + * + * The suggested usage is to create a singleton extending this class, holding any state specific to + * the particular event type that the subclass represents, and have an 'entrypoint' method that + * delegates to {@link #scheduleFor(View)}. + * For example: + * + * {@code + * public void post(View view, String text, int resId) { + * mText = text; + * mId = resId; + * scheduleFor(view); + * } + * } + * + * @see #scheduleFor(View) + * @see #tryMerge(View, View) + * @see #performSendEvent(View) + * @hide + */ +public abstract class ThrottlingAccessibilityEventSender { + + private static final boolean DEBUG = false; + private static final String LOG_TAG = "ThrottlingA11ySender"; + + View mSource; + private long mLastSendTimeMillis = Long.MIN_VALUE; + private boolean mIsPending = false; + + private final Runnable mWorker = () -> { + View source = mSource; + if (DEBUG) Log.d(LOG_TAG, thisClass() + ".run(mSource = " + source + ")"); + + if (!checkAndResetIsPending() || source == null) { + resetStateInternal(); + return; + } + + // Accessibility may be turned off while we were waiting + if (isAccessibilityEnabled(source)) { + mLastSendTimeMillis = SystemClock.uptimeMillis(); + performSendEvent(source); + } + resetStateInternal(); + }; + + /** + * Populate and send an {@link AccessibilityEvent} using the given {@code source} view, as well + * as any extra data from this instance's state. + * + * Send the event via {@link View#sendAccessibilityEventUnchecked(AccessibilityEvent)} or + * {@link View#sendAccessibilityEvent(int)} on the provided {@code source} view to allow for + * overrides of those methods on {@link View} subclasses to take effect, and/or make sure that + * an {@link View#getAccessibilityDelegate() accessibility delegate} is not ignored if any. + */ + protected abstract void performSendEvent(@NonNull View source); + + /** + * Perform optional cleanup after {@link #performSendEvent} + * + * @param source the view this event was associated with + */ + protected abstract void resetState(@Nullable View source); + + /** + * Attempt to merge the pending events for source views {@code oldSource} and {@code newSource} + * into one, with source set to the resulting {@link View} + * + * A result of {@code null} means merger is not possible, resulting in the currently pending + * event being flushed before proceeding. + */ + protected @Nullable View tryMerge(@NonNull View oldSource, @NonNull View newSource) { + return null; + } + + /** + * Schedules a {@link #performSendEvent} with the source {@link View} set to given + * {@code source} + * + * If an event is already scheduled a {@link #tryMerge merge} will be attempted. + * If merging is not possible (as indicated by the null result from {@link #tryMerge}), + * the currently scheduled event will be {@link #sendNow sent immediately} and the new one + * will be scheduled afterwards. + */ + protected final void scheduleFor(@NonNull View source) { + if (DEBUG) Log.d(LOG_TAG, thisClass() + ".scheduleFor(source = " + source + ")"); + + Handler uiHandler = source.getHandler(); + if (uiHandler == null || uiHandler.getLooper() != Looper.myLooper()) { + CalledFromWrongThreadException e = new CalledFromWrongThreadException( + "Expected to be called from main thread but was called from " + + Thread.currentThread()); + // TODO: Throw the exception + Log.e(LOG_TAG, "Accessibility content change on non-UI thread. Future Android " + + "versions will throw an exception.", e); + } + + if (!isAccessibilityEnabled(source)) return; + + if (mIsPending) { + View merged = tryMerge(mSource, source); + if (merged != null) { + setSource(merged); + return; + } else { + sendNow(); + } + } + + setSource(source); + + final long timeSinceLastMillis = SystemClock.uptimeMillis() - mLastSendTimeMillis; + final long minEventIntervalMillis = + ViewConfiguration.getSendRecurringAccessibilityEventsInterval(); + if (timeSinceLastMillis >= minEventIntervalMillis) { + sendNow(); + } else { + mSource.postDelayed(mWorker, minEventIntervalMillis - timeSinceLastMillis); + } + } + + static boolean isAccessibilityEnabled(@NonNull View contextProvider) { + return AccessibilityManager.getInstance(contextProvider.getContext()).isEnabled(); + } + + protected final void sendNow(View source) { + setSource(source); + sendNow(); + } + + private void sendNow() { + mSource.removeCallbacks(mWorker); + mWorker.run(); + } + + /** + * Flush the event if one is pending + */ + public void sendNowIfPending() { + if (mIsPending) sendNow(); + } + + /** + * Cancel the event if one is pending and is for the given view + */ + public final void cancelIfPendingFor(@NonNull View source) { + if (isPendingFor(source)) cancelIfPending(this); + } + + /** + * @return whether an event is currently pending for the given source view + */ + protected final boolean isPendingFor(@Nullable View source) { + return mIsPending && mSource == source; + } + + /** + * Cancel the event if one is not null and pending + */ + public static void cancelIfPending(@Nullable ThrottlingAccessibilityEventSender sender) { + if (sender == null || !sender.checkAndResetIsPending()) return; + sender.mSource.removeCallbacks(sender.mWorker); + sender.resetStateInternal(); + } + + void resetStateInternal() { + if (DEBUG) Log.d(LOG_TAG, thisClass() + ".resetStateInternal()"); + + resetState(mSource); + setSource(null); + } + + boolean checkAndResetIsPending() { + if (mIsPending) { + mIsPending = false; + return true; + } else { + return false; + } + } + + private void setSource(@Nullable View source) { + if (DEBUG) Log.d(LOG_TAG, thisClass() + ".setSource(" + source + ")"); + + if (source == null && mIsPending) { + Log.e(LOG_TAG, "mSource nullified while callback still pending: " + this); + return; + } + + if (source != null && !mIsPending) { + // At most one can be pending at any given time + View oldSource = mSource; + if (oldSource != null) { + ViewRootImpl viewRootImpl = oldSource.getViewRootImpl(); + if (viewRootImpl != null) { + viewRootImpl.flushPendingAccessibilityEvents(); + } + } + mIsPending = true; + } + mSource = source; + } + + String thisClass() { + return getClass().getSimpleName(); + } + + @Override + public String toString() { + return thisClass() + "(" + mSource + ")"; + } + +} diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index e0c897d3e25c..6bee58f96f8a 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -6849,7 +6849,7 @@ public abstract class AbsListView extends AdapterView implements Te // detached and we do not allow detached views to fire accessibility // events. So we are announcing that the subtree changed giving a chance // to clients holding on to a view in this subtree to refresh it. - notifyViewAccessibilityStateChangedIfNeeded( + notifyAccessibilityStateChanged( AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); // Don't scrap views that have transient state. diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java index 6c192563658e..08374cb11981 100644 --- a/core/java/android/widget/AdapterView.java +++ b/core/java/android/widget/AdapterView.java @@ -1093,7 +1093,7 @@ public abstract class AdapterView extends ViewGroup { checkSelectionChanged(); } - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } /** diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java index 92bfd56d8988..af01a3eb22bd 100644 --- a/core/java/android/widget/CheckedTextView.java +++ b/core/java/android/widget/CheckedTextView.java @@ -132,7 +132,7 @@ public class CheckedTextView extends TextView implements Checkable { if (mChecked != checked) { mChecked = checked; refreshDrawableState(); - notifyViewAccessibilityStateChangedIfNeeded( + notifyAccessibilityStateChanged( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java index 0762b15626f7..e57f15365c26 100644 --- a/core/java/android/widget/CompoundButton.java +++ b/core/java/android/widget/CompoundButton.java @@ -158,7 +158,7 @@ public abstract class CompoundButton extends Button implements Checkable { mCheckedFromResource = false; mChecked = checked; refreshDrawableState(); - notifyViewAccessibilityStateChangedIfNeeded( + notifyAccessibilityStateChanged( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); // Avoid infinite recursions if setChecked() is called from a listener diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 1e17f34af2a6..821ae07d136d 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -2360,7 +2360,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener setText(mText); if (hasPasswordTransformationMethod()) { - notifyViewAccessibilityStateChangedIfNeeded( + notifyAccessibilityStateChanged( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } @@ -5419,7 +5419,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener sendOnTextChanged(text, 0, oldlen, textLength); onTextChanged(text, 0, oldlen, textLength); - notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT); + notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT); if (needEditableForNotification) { sendAfterTextChanged((Editable) text); @@ -6151,7 +6151,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void setError(CharSequence error, Drawable icon) { createEditorIfNeeded(); mEditor.setError(error, icon); - notifyViewAccessibilityStateChangedIfNeeded( + notifyAccessibilityStateChanged( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } diff --git a/core/java/com/android/internal/util/ObjectUtils.java b/core/java/com/android/internal/util/ObjectUtils.java index 59e5a6402fb8..379602ac9041 100644 --- a/core/java/com/android/internal/util/ObjectUtils.java +++ b/core/java/com/android/internal/util/ObjectUtils.java @@ -29,6 +29,9 @@ public class ObjectUtils { return a != null ? a : Preconditions.checkNotNull(b); } + /** + * Compares two {@link Nullable} objects with {@code null} values considered the smallest + */ public static int compare(@Nullable T a, @Nullable T b) { if (a != null) { return (b != null) ? a.compareTo(b) : 1; @@ -36,4 +39,13 @@ public class ObjectUtils { return (b != null) ? -1 : 0; } } + + /** + * @return {@code null} if the given instance is not of the given calss, or the given + * instance otherwise + */ + @Nullable + public static T castOrNull(@Nullable S instance, @NonNull Class c) { + return c.isInstance(instance) ? (T) instance : null; + } } diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java index 7635a727ae85..6f2246aa3907 100644 --- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java +++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java @@ -22,9 +22,7 @@ import com.android.internal.R; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.Rect; -import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Parcel; @@ -504,7 +502,7 @@ public class ResolverDrawerLayout extends ViewGroup { } private void onCollapsedChanged(boolean isCollapsed) { - notifyViewAccessibilityStateChangedIfNeeded( + notifyAccessibilityStateChanged( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); if (mScrollIndicatorDrawable != null) { diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java index 8b00ed053b26..faa10cc69889 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java +++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java @@ -23,6 +23,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; + import com.android.printspooler.R; /** @@ -410,7 +411,7 @@ public final class PrintContentView extends ViewGroup implements View.OnClickLis mPrintButton.offsetTopAndBottom(dy); - mDraggableContent.notifySubtreeAccessibilityStateChangedIfNeeded(); + mDraggableContent.notifyAccessibilitySubtreeChanged(); onDragProgress(progress); } -- 2.11.0