diff --git a/api/current.txt b/api/current.txt index 24501f3526090..96c50a296a2f5 100644 --- a/api/current.txt +++ b/api/current.txt @@ -33332,6 +33332,7 @@ package android.view { method public boolean dispatchKeyShortcutEvent(android.view.KeyEvent); method public boolean dispatchNestedFling(float, float, boolean); method public boolean dispatchNestedPreFling(float, float); + method public boolean dispatchNestedPrePerformAccessibilityAction(int, android.os.Bundle); method public boolean dispatchNestedPreScroll(int, int, int[], int[]); method public boolean dispatchNestedScroll(int, int, int, int, int[]); method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent); @@ -34144,6 +34145,7 @@ package android.view { method protected abstract void onLayout(boolean, int, int, int, int); method public boolean onNestedFling(android.view.View, float, float, boolean); method public boolean onNestedPreFling(android.view.View, float, float); + method public boolean onNestedPrePerformAccessibilityAction(android.view.View, int, android.os.Bundle); method public void onNestedPreScroll(android.view.View, int, int, int[]); method public void onNestedScroll(android.view.View, int, int, int, int); method public void onNestedScrollAccepted(android.view.View, android.view.View, int); @@ -34292,6 +34294,7 @@ package android.view { method public abstract void notifySubtreeAccessibilityStateChanged(android.view.View, android.view.View, int); method public abstract boolean onNestedFling(android.view.View, float, float, boolean); method public abstract boolean onNestedPreFling(android.view.View, float, float); + method public abstract boolean onNestedPrePerformAccessibilityAction(android.view.View, int, android.os.Bundle); method public abstract void onNestedPreScroll(android.view.View, int, int, int[]); method public abstract void onNestedScroll(android.view.View, int, int, int, int); method public abstract void onNestedScrollAccepted(android.view.View, android.view.View, int); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 23792be63652b..6a36c269df9ad 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -8158,6 +8158,34 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags2 &= ~PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED; } + /** + * Report an accessibility action to this view's parents for delegated processing. + * + *

Implementations of {@link #performAccessibilityAction(int, Bundle)} may internally + * call this method to delegate an accessibility action to a supporting parent. If the parent + * returns true from its + * {@link ViewParent#onNestedPrePerformAccessibilityAction(View, int, android.os.Bundle)} + * method this method will return true to signify that the action was consumed.

+ * + *

This method is useful for implementing nested scrolling child views. If + * {@link #isNestedScrollingEnabled()} returns true and the action is a scrolling action + * a custom view implementation may invoke this method to allow a parent to consume the + * scroll first. If this method returns true the custom view should skip its own scrolling + * behavior.

+ * + * @param action Accessibility action to delegate + * @param arguments Optional action arguments + * @return true if the action was consumed by a parent + */ + public boolean dispatchNestedPrePerformAccessibilityAction(int action, Bundle arguments) { + for (ViewParent p = getParent(); p != null; p = p.getParent()) { + if (p.onNestedPrePerformAccessibilityAction(this, action, arguments)) { + return true; + } + } + return false; + } + /** * Performs the specified accessibility action on the view. For * possible accessibility actions look at {@link AccessibilityNodeInfo}. @@ -8168,6 +8196,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * is responsible for handling this call. *

* + *

The default implementation will delegate + * {@link AccessibilityNodeInfo#ACTION_SCROLL_BACKWARD} and + * {@link AccessibilityNodeInfo#ACTION_SCROLL_FORWARD} to nested scrolling parents if + * {@link #isNestedScrollingEnabled() nested scrolling is enabled} on this view.

+ * * @param action The action to perform. * @param arguments Optional action arguments. * @return Whether the action was performed. @@ -8188,6 +8221,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide Until we've refactored all accessibility delegation methods. */ public boolean performAccessibilityActionInternal(int action, Bundle arguments) { + if (isNestedScrollingEnabled() + && (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD + || action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD)) { + if (dispatchNestedPrePerformAccessibilityAction(action, arguments)) { + return true; + } + } + switch (action) { case AccessibilityNodeInfo.ACTION_CLICK: { if (isClickable()) { diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 22c51859ea31d..6678ff223b14d 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -32,6 +32,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; import android.os.Build; +import android.os.Bundle; import android.os.Parcelable; import android.os.SystemClock; import android.util.AttributeSet; @@ -2921,6 +2922,22 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } + /** + * {@inheritDoc} + * + *

Subclasses should always call super.onNestedPrePerformAccessibilityAction

+ * + * @param target The target view dispatching this action + * @param action Action being performed; see + * {@link android.view.accessibility.AccessibilityNodeInfo} + * @param args Optional action arguments + * @return false by default. Subclasses should return true if they handle the event. + */ + @Override + public boolean onNestedPrePerformAccessibilityAction(View target, int action, Bundle args) { + return false; + } + /** * {@inheritDoc} */ diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java index 87a37f4739be9..035871d87671e 100644 --- a/core/java/android/view/ViewParent.java +++ b/core/java/android/view/ViewParent.java @@ -17,6 +17,7 @@ package android.view; import android.graphics.Rect; +import android.os.Bundle; import android.view.accessibility.AccessibilityEvent; /** @@ -551,4 +552,23 @@ public interface ViewParent { * @return true if this parent consumed the fling ahead of the target view */ public boolean onNestedPreFling(View target, float velocityX, float velocityY); + + /** + * React to an accessibility action delegated by a target descendant view before the target + * processes it. + * + *

This method may be called by a target descendant view if the target wishes to give + * a view in its parent chain a chance to react to the event before normal processing occurs. + * Most commonly this will be a scroll event such as + * {@link android.view.accessibility.AccessibilityNodeInfo#ACTION_SCROLL_FORWARD}. + * A ViewParent that supports acting as a nested scrolling parent should override this + * method and act accordingly to implement scrolling via accesibility systems.

+ * + * @param target The target view dispatching this action + * @param action Action being performed; see + * {@link android.view.accessibility.AccessibilityNodeInfo} + * @param arguments Optional action arguments + * @return true if the action was consumed by this ViewParent + */ + public boolean onNestedPrePerformAccessibilityAction(View target, int action, Bundle arguments); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index a8bc5c9485173..87d9a581feb8d 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -6417,6 +6417,11 @@ public final class ViewRootImpl implements ViewParent, return false; } + @Override + public boolean onNestedPrePerformAccessibilityAction(View target, int action, Bundle args) { + return false; + } + void changeCanvasOpacity(boolean opaque) { Log.d(TAG, "changeCanvasOpacity: opaque=" + opaque); if (mAttachInfo.mHardwareRenderer != null) { diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java index ed7af2f3525a1..4e48454250e01 100644 --- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java +++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java @@ -20,6 +20,7 @@ package com.android.internal.widget; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Rect; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; @@ -31,6 +32,8 @@ import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewParent; import android.view.ViewTreeObserver; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.AnimationUtils; import android.widget.AbsListView; import android.widget.OverScroller; @@ -367,8 +370,14 @@ public class ResolverDrawerLayout extends ViewGroup { child.offsetTopAndBottom((int) dy); } } + final boolean isCollapsedOld = mCollapseOffset != 0; mCollapseOffset = newPos; mTopOffset += dy; + final boolean isCollapsedNew = newPos != 0; + if (isCollapsedOld != isCollapsedNew) { + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); + } postInvalidateOnAnimation(); return dy; } @@ -570,6 +579,50 @@ public class ResolverDrawerLayout extends ViewGroup { return false; } + @Override + public boolean onNestedPrePerformAccessibilityAction(View target, int action, Bundle args) { + if (super.onNestedPrePerformAccessibilityAction(target, action, args)) { + return true; + } + + if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD && mCollapseOffset != 0) { + smoothScrollTo(0, 0); + return true; + } + return false; + } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(ResolverDrawerLayout.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(ResolverDrawerLayout.class.getName()); + if (isEnabled()) { + if (mCollapseOffset != 0) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); + info.setScrollable(true); + } + } + } + + @Override + public boolean performAccessibilityAction(int action, Bundle arguments) { + if (super.performAccessibilityAction(action, arguments)) { + return true; + } + + if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD && mCollapseOffset != 0) { + smoothScrollTo(0, 0); + return true; + } + return false; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int sourceWidth = MeasureSpec.getSize(widthMeasureSpec); @@ -615,7 +668,13 @@ public class ResolverDrawerLayout extends ViewGroup { mUncollapsibleHeight = heightUsed - mCollapsibleHeight; if (isLaidOut()) { + final boolean isCollapsedOld = mCollapseOffset != 0; mCollapseOffset = Math.min(mCollapseOffset, mCollapsibleHeight); + final boolean isCollapsedNew = mCollapseOffset != 0; + if (isCollapsedOld != isCollapsedNew) { + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); + } } else { // Start out collapsed at first unless we restored state for otherwise mCollapseOffset = mOpenOnLayout ? 0 : mCollapsibleHeight;