From 35b6f544632ccb39b96a105c2470213237da64b4 Mon Sep 17 00:00:00 2001 From: Qasid Ahmad Sadiq Date: Tue, 3 Dec 2019 15:54:56 -0800 Subject: [PATCH] Throttle stateDescription changed events Similar to scrolling, state description changes can happen very frequently, so let's not overwhelm the service when the user is focused on something like a slider of progress bar. Isolating and reusing scroll event throttling code to do this. Test: Added CTS tests in other CL, tried scroll viewws around the system, and progress bar and slider in talkback test app (which have already adopted state API). Fix: 145626718 Change-Id: I4f7d2e526d7d453485615c089b04a8a698828279 --- core/java/android/view/View.java | 121 ++++++++++++++++++++++--------- 1 file changed, 88 insertions(+), 33 deletions(-) diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 6724e9d3289d7..d047692e08e09 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -4784,7 +4784,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private CheckForTap mPendingCheckForTap = null; private PerformClick mPerformClick; private SendViewScrolledAccessibilityEvent mSendViewScrolledAccessibilityEvent; - + private SendAccessibilityEventThrottle mSendStateChangedAccessibilityEvent; private UnsetPressedState mUnsetPressedState; /** @@ -8154,13 +8154,46 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if ((event.getEventType() & POPULATING_ACCESSIBILITY_EVENT_TYPES) != 0) { dispatchPopulateAccessibilityEvent(event); } - // In the beginning we called #isShown(), so we know that getParent() is not null. + SendAccessibilityEventThrottle throttle = getThrottleForAccessibilityEvent(event); + if (throttle != null) { + throttle.post(event); + } else { + requestParentSendAccessibilityEvent(event); + } + } + + private void requestParentSendAccessibilityEvent(AccessibilityEvent event) { ViewParent parent = getParent(); if (parent != null) { getParent().requestSendAccessibilityEvent(this, event); } } + private SendAccessibilityEventThrottle getThrottleForAccessibilityEvent( + AccessibilityEvent event) { + if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { + if (mSendViewScrolledAccessibilityEvent == null) { + mSendViewScrolledAccessibilityEvent = new SendViewScrolledAccessibilityEvent(); + } + return mSendViewScrolledAccessibilityEvent; + } + boolean isStateContentChanged = (event.getContentChangeTypes() + & AccessibilityEvent.CONTENT_CHANGE_TYPE_STATE_DESCRIPTION) != 0; + if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED + && isStateContentChanged) { + if (mSendStateChangedAccessibilityEvent == null) { + mSendStateChangedAccessibilityEvent = new SendAccessibilityEventThrottle(); + } + return mSendStateChangedAccessibilityEvent; + } + return null; + } + + private void clearAccessibilityThrottles() { + cancel(mSendViewScrolledAccessibilityEvent); + cancel(mSendStateChangedAccessibilityEvent); + } + /** * Dispatches an {@link AccessibilityEvent} to the {@link View} first and then * to its children for adding their text content to the event. Note that the @@ -10381,8 +10414,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, && getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); } - notifyViewAccessibilityStateChangedIfNeeded( - AccessibilityEvent.CONTENT_CHANGE_TYPE_STATE_DESCRIPTION); + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + AccessibilityEvent event = AccessibilityEvent.obtain(); + event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_STATE_DESCRIPTION); + sendAccessibilityEventUnchecked(event); + } } /** @@ -15986,10 +16023,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ protected void onScrollChanged(int l, int t, int oldl, int oldt) { notifySubtreeAccessibilityStateChangedIfNeeded(); - - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - postSendViewScrolledAccessibilityEventCallback(l - oldl, t - oldt); - } + postSendViewScrolledAccessibilityEventCallback(l - oldl, t - oldt); mBackgroundSizeChanged = true; mDefaultFocusHighlightSizeChanged = true; @@ -18712,10 +18746,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}. */ private void postSendViewScrolledAccessibilityEventCallback(int dx, int dy) { - if (mSendViewScrolledAccessibilityEvent == null) { - mSendViewScrolledAccessibilityEvent = new SendViewScrolledAccessibilityEvent(); + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + AccessibilityEvent event = + AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED); + event.setScrollDeltaX(dx); + event.setScrollDeltaY(dy); + sendAccessibilityEventUnchecked(event); } - mSendViewScrolledAccessibilityEvent.post(dx, dy); } /** @@ -20013,7 +20050,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, removeUnsetPressCallback(); removeLongPressCallback(); removePerformClickCallback(); - cancel(mSendViewScrolledAccessibilityEvent); + clearAccessibilityThrottles(); stopNestedScroll(); // Anything that started animating right before detach should already @@ -28997,49 +29034,67 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } - /** - * Resuable callback for sending - * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event. - */ - private class SendViewScrolledAccessibilityEvent implements Runnable { + private class SendAccessibilityEventThrottle implements Runnable { public volatile boolean mIsPending; - public int mDeltaX; - public int mDeltaY; + private AccessibilityEvent mAccessibilityEvent; - public void post(int dx, int dy) { - mDeltaX += dx; - mDeltaY += dy; + public void post(AccessibilityEvent accessibilityEvent) { + updateWithAccessibilityEvent(accessibilityEvent); if (!mIsPending) { mIsPending = true; - postDelayed(this, ViewConfiguration.getSendRecurringAccessibilityEventsInterval()); + 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); + requestParentSendAccessibilityEvent(mAccessibilityEvent); } reset(); } - private void reset() { + public void updateWithAccessibilityEvent(AccessibilityEvent accessibilityEvent) { + mAccessibilityEvent = accessibilityEvent; + } + + public void reset() { mIsPending = false; + mAccessibilityEvent = null; + } + + } + + /** + * Resuable callback for sending + * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event. + */ + private class SendViewScrolledAccessibilityEvent extends SendAccessibilityEventThrottle { + public int mDeltaX; + public int mDeltaY; + + @Override + public void updateWithAccessibilityEvent(AccessibilityEvent accessibilityEvent) { + super.updateWithAccessibilityEvent(accessibilityEvent); + mDeltaX += accessibilityEvent.getScrollDeltaX(); + mDeltaY += accessibilityEvent.getScrollDeltaY(); + accessibilityEvent.setScrollDeltaX(mDeltaX); + accessibilityEvent.setScrollDeltaY(mDeltaY); + } + + @Override + public void reset() { + super.reset(); mDeltaX = 0; mDeltaY = 0; } } - /** - * Remove the pending callback for sending a - * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event. + * Remove the pending callback for sending a throttled accessibility event. */ @UnsupportedAppUsage - private void cancel(@Nullable SendViewScrolledAccessibilityEvent callback) { + private void cancel(@Nullable SendAccessibilityEventThrottle callback) { if (callback == null || !callback.mIsPending) return; removeCallbacks(callback); callback.reset();