From c7aa6dc8270c0eabc913fe6c617c8131e3f4b3c5 Mon Sep 17 00:00:00 2001 From: Marc Mulcahy Date: Sun, 29 Sep 2019 19:43:21 -0700 Subject: [PATCH] Add onSlidingComplete callbacks when sliders adjusted via a11y (#26600) Summary: When sliders are adjusted via accessibility, no onSlidingComplete callback is generated. This causes problems for components which perform behavior in this callback, and means that such components don't behave properly when adjusted via accessibility. For example, if an app hosting a volume control slider only commits the volume change to the hardware on onSlidingComplete, it is impossible for a screen reader user to ever actually adjust the volume. Ensure that sliders call the onSlidingComplete callback after adjusted via accessibility. ## Changelog [General] [Fix] - Add onSlidingComplete callbacks when sliders adjusted via a11y. [CATEGORY] [TYPE] - Message Pull Request resolved: https://github.com/facebook/react-native/pull/26600 Test Plan: Prior to this change, using the RNTester slider example with a screen reader, the onSlidingComplete callback tests never shows any callbacks when the slider is adjusted. With this change applied, the callback test will show a number of callbacks corresponding to the number of times the slider was adjusted via the screen reader. Differential Revision: D17661157 Pulled By: cpojer fbshipit-source-id: a6eedef099c6c1b571b290c329059ac9b69b53dd --- React/Views/RCTSlider.m | 20 ++++++++++++ .../views/slider/ReactSliderManager.java | 31 ++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/React/Views/RCTSlider.m b/React/Views/RCTSlider.m index 11898a82fa4..5b45c76c7cf 100644 --- a/React/Views/RCTSlider.m +++ b/React/Views/RCTSlider.m @@ -84,4 +84,24 @@ return [self thumbImageForState:UIControlStateNormal]; } +- (void)accessibilityIncrement +{ + [super accessibilityIncrement]; + if (_onSlidingComplete) { + _onSlidingComplete(@{ + @"value": @(self.value), + }); + } +} + +- (void)accessibilityDecrement +{ + [super accessibilityDecrement]; + if (_onSlidingComplete) { + _onSlidingComplete(@{ + @"value": @(self.value), + }); + } +} + @end diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/slider/ReactSliderManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/slider/ReactSliderManager.java index 1b31adfdaad..7f20cb03cd9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/slider/ReactSliderManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/slider/ReactSliderManager.java @@ -10,10 +10,14 @@ import android.content.Context; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; +import android.os.Bundle; import android.view.View; import android.view.ViewGroup; import android.widget.SeekBar; import androidx.annotation.Nullable; +import androidx.core.view.AccessibilityDelegateCompat; +import androidx.core.view.ViewCompat; +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.common.MapBuilder; @@ -132,7 +136,9 @@ public class ReactSliderManager extends SimpleViewManager @Override protected ReactSlider createViewInstance(ThemedReactContext context) { - return new ReactSlider(context, null, STYLE); + final ReactSlider slider = new ReactSlider(context, null, STYLE); + ViewCompat.setAccessibilityDelegate(slider, sAccessibilityDelegate); + return slider; } @Override @@ -256,4 +262,27 @@ public class ReactSliderManager extends SimpleViewManager protected ViewManagerDelegate getDelegate() { return mDelegate; } + + protected static class ReactSliderAccessibilityDelegate extends AccessibilityDelegateCompat { + private static boolean isSliderAction(int action) { + return (action == AccessibilityActionCompat.ACTION_SCROLL_FORWARD.getId()) + || (action == AccessibilityActionCompat.ACTION_SCROLL_BACKWARD.getId()) + || (action == AccessibilityActionCompat.ACTION_SET_PROGRESS.getId()); + } + + @Override + public boolean performAccessibilityAction(View host, int action, Bundle args) { + if (isSliderAction(action)) { + ON_CHANGE_LISTENER.onStartTrackingTouch((SeekBar) host); + } + final boolean rv = super.performAccessibilityAction(host, action, args); + if (isSliderAction(action)) { + ON_CHANGE_LISTENER.onStopTrackingTouch((SeekBar) host); + } + return rv; + } + }; + + protected static ReactSliderAccessibilityDelegate sAccessibilityDelegate = + new ReactSliderAccessibilityDelegate(); }