diff options
author | 2018-01-23 21:12:11 +0000 | |
---|---|---|
committer | 2018-01-26 11:32:25 -0800 | |
commit | 72c510f1c4d5cff42a3925a784027e258ac0bcdc (patch) | |
tree | 3b2d6d9da641f38f6c8afe7fea3fec1789b89055 | |
parent | f4c5faf932a6a3e5a3120f5a3953af11872e6bf9 (diff) |
Revert "Move A11y events throttling away from View(RootImpl)"
This reverts commit e4d31b3c103045d5b2b141a05084dced595cc64f.
Fixes: 71904218
Test: presubmit
Change-Id: Id73bde1a0c11696cf561c84cde027cdca4c6a00f
17 files changed, 321 insertions, 640 deletions
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index ba39740be12e..8f0168530273 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -24,6 +24,7 @@ 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; @@ -46,14 +47,10 @@ 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; @@ -583,8 +580,6 @@ public final class UiAutomation { // Execute the command *without* the lock being held. command.run(); - List<AccessibilityEvent> eventsReceived = Collections.emptyList(); - // Acquire the lock and wait for the event. try { // Wait for the event. @@ -605,14 +600,14 @@ public final class UiAutomation { if (filter.accept(event)) { return event; } - eventsReceived = CollectionUtils.add(eventsReceived, event); + event.recycle(); } // 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, among " + eventsReceived); + + timeoutMillis + " ms."); } synchronized (mLock) { if (mEventQueue.isEmpty()) { @@ -625,10 +620,6 @@ 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 3d6a6fee081b..5150c1fec8a3 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -4442,6 +4442,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private CheckForLongPress mPendingCheckForLongPress; private CheckForTap mPendingCheckForTap = null; private PerformClick mPerformClick; + private SendViewScrolledAccessibilityEvent mSendViewScrolledAccessibilityEvent; private UnsetPressedState mUnsetPressedState; @@ -7201,7 +7202,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (gainFocus) { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); } else { - notifyAccessibilityStateChanged( + notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } @@ -7271,7 +7272,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public void setAccessibilityPaneTitle(CharSequence accessibilityPaneTitle) { if (!TextUtils.equals(accessibilityPaneTitle, mAccessibilityPaneTitle)) { mAccessibilityPaneTitle = accessibilityPaneTitle; - notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_TITLE); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_TITLE); } } @@ -8924,9 +8926,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); - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } else { - notifyAccessibilityStateChanged( + notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION); } } @@ -8959,7 +8961,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return; } mAccessibilityTraversalBeforeId = beforeId; - notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } /** @@ -9002,7 +9005,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return; } mAccessibilityTraversalAfterId = afterId; - notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } /** @@ -9044,7 +9048,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, && mID == View.NO_ID) { mID = generateViewId(); } - notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } /** @@ -10544,7 +10549,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (pflags3 != mPrivateFlags3) { mPrivateFlags3 = pflags3; - notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } @@ -11374,7 +11380,8 @@ 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; - notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } @@ -11431,9 +11438,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags2 |= (mode << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT) & PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK; if (!maySkipNotify || oldIncludeForAccessibility != includeForAccessibility()) { - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } else { - notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } } @@ -11609,32 +11617,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @hide */ - public void notifyAccessibilityStateChanged(int changeType) { - notifyAccessibilityStateChanged(this, changeType); - } - - /** - * 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 - * notification this method is a NOP until the notification has been sent. - * - * @hide - */ - 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) { + public void notifyViewAccessibilityStateChangedIfNeeded(int changeType) { if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) { return; } + // Changes to views with a pane title count as window state changes, as the pane title // marks them as significant parts of the UI. if (!TextUtils.isEmpty(getAccessibilityPaneTitle())) { @@ -11652,12 +11639,49 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } - if (mParent != null) { + // 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, source, changeType); + mParent.notifySubtreeAccessibilityStateChanged(this, this, changeType); } catch (AbstractMethodError e) { - Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() - + " does not fully implement ViewParent", e); + Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + + " does not fully implement ViewParent", e); + } + } + } + + /** + * 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 + * notification this method is a NOP until the notification has been sent. + * + * @hide + */ + public void notifySubtreeAccessibilityStateChangedIfNeeded() { + 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); + } } } } @@ -11679,10 +11703,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Reset the flag indicating the accessibility state of the subtree rooted * at this view changed. - * - * @hide */ - public void resetSubtreeAccessibilityStateChanged() { + void resetSubtreeAccessibilityStateChanged() { mPrivateFlags2 &= ~PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED; } @@ -11843,7 +11865,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, || getAccessibilitySelectionEnd() != end) && (start == end)) { setAccessibilitySelection(start, end); - notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); return true; } } break; @@ -12643,7 +12666,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } if (!TextUtils.isEmpty(getAccessibilityPaneTitle())) { if (isVisible != oldVisible) { - notifyAccessibilityStateChanged(isVisible + notifyViewAccessibilityStateChangedIfNeeded(isVisible ? AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED : AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED); } @@ -13860,7 +13883,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, ((!(mParent instanceof ViewGroup)) || ((ViewGroup) mParent).isShown())) { dispatchVisibilityAggregated(newVisibility == VISIBLE); } - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -13906,12 +13929,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, || (changed & CLICKABLE) != 0 || (changed & LONG_CLICKABLE) != 0 || (changed & CONTEXT_CLICKABLE) != 0) { if (oldIncludeForAccessibility != includeForAccessibility()) { - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } else { - notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } else if ((changed & ENABLED_MASK) != 0) { - notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } } @@ -13945,13 +13970,10 @@ 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) { - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); - ViewRootImpl root = getViewRootImpl(); - if (root != null) { - root.getAccessibilityState() - .getSendViewScrolledAccessibilityEvent() - .post(this, /* dx */ l - oldl, /* dy */ t - oldt); + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + postSendViewScrolledAccessibilityEventCallback(l - oldl, t - oldt); } mBackgroundSizeChanged = true; @@ -14347,7 +14369,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -14391,7 +14413,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -14435,7 +14457,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -14472,7 +14494,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -14509,7 +14531,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -14712,7 +14734,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)) { - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } mTransformationInfo.mAlpha = alpha; if (onSetAlpha((int) (alpha * 255))) { @@ -15214,7 +15236,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -15248,7 +15270,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -15418,7 +15440,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public void invalidateOutline() { rebuildOutline(); - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); invalidateViewProperty(false, false); } @@ -15613,7 +15635,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } invalidateParentIfNeeded(); } - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -15661,7 +15683,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } invalidateParentIfNeeded(); } - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -16539,6 +16561,18 @@ 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} @@ -17793,13 +17827,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, removeUnsetPressCallback(); removeLongPressCallback(); removePerformClickCallback(); - if (mAttachInfo != null - && mAttachInfo.mViewRootImpl.mAccessibilityState != null - && mAttachInfo.mViewRootImpl.mAccessibilityState.isScrollEventSenderInitialized()) { - mAttachInfo.mViewRootImpl.mAccessibilityState - .getSendViewScrolledAccessibilityEvent() - .cancelIfPendingFor(this); - } + cancel(mSendViewScrolledAccessibilityEvent); stopNestedScroll(); // Anything that started animating right before detach should already @@ -20427,7 +20455,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mForegroundInfo.mBoundsChanged = true; } - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } return changed; } @@ -21871,7 +21899,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (selected) { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); } else { - notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } } @@ -26458,6 +26487,53 @@ 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(); + } + + /** * <p> * This class represents a delegate that can be registered in a {@link View} * to enhance accessibility support via composition rather via inheritance. @@ -27074,7 +27150,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mTooltipInfo.mTooltipPopup.show(this, x, y, fromTouch, mTooltipInfo.mTooltipText); mAttachInfo.mTooltipHost = this; // The available accessibility actions have changed - notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); + notifyViewAccessibilityStateChangedIfNeeded(CONTENT_CHANGE_TYPE_UNDEFINED); return true; } @@ -27094,7 +27170,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mAttachInfo.mTooltipHost = null; } // The available accessibility actions have changed - notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); + notifyViewAccessibilityStateChangedIfNeeded(CONTENT_CHANGE_TYPE_UNDEFINED); } private boolean showLongClickTooltip(int x, int y) { diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 4631261e8caa..a8bdb85660d0 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -3649,34 +3649,44 @@ 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 notifyAccessibilitySubtreeChanged() { + public void notifySubtreeAccessibilityStateChangedIfNeeded() { 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).notifyAccessibilitySubtreeChanged(); + ((View) a11yParent).notifySubtreeAccessibilityStateChangedIfNeeded(); return; } } - super.notifyAccessibilitySubtreeChanged(); + super.notifySubtreeAccessibilityStateChangedIfNeeded(); } @Override - public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) { - notifyAccessibilityStateChanged(source, changeType); - } - - /** @hide */ - @Override - public void resetSubtreeAccessibilityStateChanged() { + void resetSubtreeAccessibilityStateChanged() { super.resetSubtreeAccessibilityStateChanged(); View[] children = mChildren; final int childCount = mChildrenCount; @@ -5088,7 +5098,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } if (child.getVisibility() != View.GONE) { - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } if (mTransientIndices != null) { @@ -5358,7 +5368,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager dispatchViewRemoved(view); if (view.getVisibility() != View.GONE) { - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size(); @@ -6077,7 +6087,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (invalidate) { invalidateViewProperty(false, false); } - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } @Override diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 30f584c570ca..29246fd34748 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -89,11 +89,9 @@ 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; @@ -115,6 +113,7 @@ 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; /** @@ -461,6 +460,10 @@ public final class ViewRootImpl implements ViewParent, new AccessibilityInteractionConnectionManager(); final HighContrastTextManager mHighContrastTextManager; + SendWindowContentChangedAccessibilityEvent mSendWindowContentChangedAccessibilityEvent; + + HashSet<View> mTempHashSet; + private final int mDensity; private final int mNoncompatDensity; @@ -475,8 +478,6 @@ public final class ViewRootImpl implements ViewParent, private boolean mNeedsRendererSetup; - protected AccessibilityViewHierarchyState mAccessibilityState; - /** * Consistency verifier for debugging purposes. */ @@ -7258,9 +7259,11 @@ public final class ViewRootImpl implements ViewParent, * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}. */ private void postSendWindowContentChangedCallback(View source, int changeType) { - getAccessibilityState() - .getSendWindowContentChangedAccessibilityEvent() - .runOrPost(source, changeType); + if (mSendWindowContentChangedAccessibilityEvent == null) { + mSendWindowContentChangedAccessibilityEvent = + new SendWindowContentChangedAccessibilityEvent(); + } + mSendWindowContentChangedAccessibilityEvent.runOrPost(source, changeType); } /** @@ -7268,18 +7271,9 @@ public final class ViewRootImpl implements ViewParent, * {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event. */ private void removeSendWindowContentChangedCallback() { - if (mAccessibilityState != null - && mAccessibilityState.isWindowContentChangedEventSenderInitialized()) { - ThrottlingAccessibilityEventSender.cancelIfPending( - mAccessibilityState.getSendWindowContentChangedAccessibilityEvent()); - } - } - - AccessibilityViewHierarchyState getAccessibilityState() { - if (mAccessibilityState == null) { - mAccessibilityState = new AccessibilityViewHierarchyState(); + if (mSendWindowContentChangedAccessibilityEvent != null) { + mHandler.removeCallbacks(mSendWindowContentChangedAccessibilityEvent); } - return mAccessibilityState; } @Override @@ -7317,8 +7311,12 @@ public final class ViewRootImpl implements ViewParent, return false; } - // Send any pending event to prevent reordering - flushPendingAccessibilityEvents(); + // 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(); + } // Intercept accessibility focus events fired by virtual nodes to keep // track of accessibility focus position in such nodes. @@ -7362,19 +7360,6 @@ 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. @@ -7509,6 +7494,39 @@ 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<View>(); + } + HashSet<View> 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( @@ -8119,6 +8137,80 @@ 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 deleted file mode 100644 index 447fafaae171..000000000000 --- a/core/java/android/view/accessibility/AccessibilityViewHierarchyState.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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 deleted file mode 100644 index 40a1b6a28b98..000000000000 --- a/core/java/android/view/accessibility/SendViewScrolledAccessibilityEvent.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 deleted file mode 100644 index df38fba5ecfb..000000000000 --- a/core/java/android/view/accessibility/SendWindowContentChangedAccessibilityEvent.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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<View> 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<View> 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 deleted file mode 100644 index 66fa30107d2d..000000000000 --- a/core/java/android/view/accessibility/ThrottlingAccessibilityEventSender.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * 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 594d24005262..a13de75edee8 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -6864,7 +6864,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> 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. - notifyAccessibilityStateChanged( + notifyViewAccessibilityStateChangedIfNeeded( 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 08374cb11981..6c192563658e 100644 --- a/core/java/android/widget/AdapterView.java +++ b/core/java/android/widget/AdapterView.java @@ -1093,7 +1093,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { checkSelectionChanged(); } - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } /** diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java index af01a3eb22bd..92bfd56d8988 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(); - notifyAccessibilityStateChanged( + notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java index e57f15365c26..0762b15626f7 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(); - notifyAccessibilityStateChanged( + notifyViewAccessibilityStateChangedIfNeeded( 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 7d3fcf469551..5710db3ce8e0 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -2397,7 +2397,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener setText(mText); if (hasPasswordTransformationMethod()) { - notifyAccessibilityStateChanged( + notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } @@ -5152,7 +5152,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void setAccessibilityHeading(boolean isHeading) { if (isHeading != mIsAccessibilityHeading) { mIsAccessibilityHeading = isHeading; - notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } @@ -5659,7 +5660,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener sendOnTextChanged(text, 0, oldlen, textLength); onTextChanged(text, 0, oldlen, textLength); - notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT); + notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT); if (needEditableForNotification) { sendAfterTextChanged((Editable) text); @@ -6393,7 +6394,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void setError(CharSequence error, Drawable icon) { createEditorIfNeeded(); mEditor.setError(error, icon); - notifyAccessibilityStateChanged( + notifyViewAccessibilityStateChangedIfNeeded( 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 379602ac9041..59e5a6402fb8 100644 --- a/core/java/com/android/internal/util/ObjectUtils.java +++ b/core/java/com/android/internal/util/ObjectUtils.java @@ -29,9 +29,6 @@ public class ObjectUtils { return a != null ? a : Preconditions.checkNotNull(b); } - /** - * Compares two {@link Nullable} objects with {@code null} values considered the smallest - */ public static <T extends Comparable> int compare(@Nullable T a, @Nullable T b) { if (a != null) { return (b != null) ? a.compareTo(b) : 1; @@ -39,13 +36,4 @@ 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 <S, T extends S> T castOrNull(@Nullable S instance, @NonNull Class<T> 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 6f2246aa3907..7635a727ae85 100644 --- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java +++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java @@ -22,7 +22,9 @@ 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; @@ -502,7 +504,7 @@ public class ResolverDrawerLayout extends ViewGroup { } private void onCollapsedChanged(boolean isCollapsed) { - notifyAccessibilityStateChanged( + notifyViewAccessibilityStateChangedIfNeeded( 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 faa10cc69889..8b00ed053b26 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java +++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java @@ -23,7 +23,6 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; - import com.android.printspooler.R; /** @@ -411,7 +410,7 @@ public final class PrintContentView extends ViewGroup implements View.OnClickLis mPrintButton.offsetTopAndBottom(dy); - mDraggableContent.notifyAccessibilitySubtreeChanged(); + mDraggableContent.notifySubtreeAccessibilityStateChangedIfNeeded(); onDragProgress(progress); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 5401e744f3ca..65c45a3120c1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -409,7 +409,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { if (visible) { // Appear and change rotBtn.setVisibility(View.VISIBLE); - mNavigationBarView.notifyAccessibilitySubtreeChanged(); + mNavigationBarView.notifySubtreeAccessibilityStateChangedIfNeeded(); if (skipAnim) { currentView.setAlpha(1f); @@ -437,7 +437,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { if (skipAnim) { rotBtn.setVisibility(View.INVISIBLE); - mNavigationBarView.notifyAccessibilitySubtreeChanged(); + mNavigationBarView.notifySubtreeAccessibilityStateChangedIfNeeded(); return; } @@ -454,7 +454,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { @Override public void onAnimationEnd(Animator animation) { rotBtn.setVisibility(View.INVISIBLE); - mNavigationBarView.notifyAccessibilitySubtreeChanged(); + mNavigationBarView.notifySubtreeAccessibilityStateChangedIfNeeded(); } }); |