- Add support for "reduce motion" into AccessibilityInfo (#23839)

Summary:
This PR adds `isReduceMotionEnabled()` to `AccessibilityInfo` in other to add support for "reduce motion", exposing the Operational System's settings option. Additionally, it adds a new event, `reduceMotionChanged`, in order to listen for this flag's update.

With this feature, developers will be able to disable or reduce animations, _**something that will be required as soon as WCAG 2.1 draft got approved**._ See [WCAG 2.1 — 2.3.3 Animations from Interaction criteria](https://knowbility.org/blog/2018/WCAG21-233Animations/)

It's exposed by [`UIAccessibility`' isReduceMotionEnabled ](https://developer.apple.com/documentation/uikit/uiaccessibility/1615133-isreducemotionenabled
) on iOS and [Settings.Global.TRANSITION_ANIMATION_SCALE](https://developer.android.com/reference/android/provider/Settings.Global#TRANSITION_ANIMATION_SCALE) on Android.

Up until now, `AccessibilityInfo` only exposes screen reader flag. By adding this second accessibility option, it's a good opportunity to rename `fetch` method to an appropriate name, `isScreenReaderEnabled`, as well as rename `change` event to `screenReaderChanged`, which will make it clearer and more specific.

(In case it's approved, a follow-up PR could exposes [more iOS acessibility flags](https://developer.apple.com/documentation/uikit/uiaccessibility), such as `isShakeToUndoEnabled`, `isReduceTransparencyEnabled`, `isGrayscaleEnabled`, `isInvertColorsEnabled`)

(iOS code inspired by [phonegap-mobile-accessibility](https://github.com/phonegap/phonegap-mobile-accessibility). And Android by [Flutter](https://github.com/flutter/engine/blob/master/shell/platform/android/io/flutter/view/AccessibilityBridge.java
))
Pull Request resolved: https://github.com/facebook/react-native/pull/23839

Differential Revision: D14406227

Pulled By: hramos

fbshipit-source-id: adf43be84c488522bf1e29d862681220ad193883
This commit is contained in:
Estevão Lucas
2019-03-12 20:23:54 -07:00
committed by Facebook Github Bot
parent 8e490d4d87
commit 0090ab32c2
5 changed files with 183 additions and 32 deletions
@@ -9,7 +9,13 @@ import javax.annotation.Nullable;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.ContentResolver;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.view.accessibility.AccessibilityManager;
import com.facebook.react.bridge.Callback;
@@ -36,21 +42,42 @@ public class AccessibilityInfoModule extends ReactContextBaseJavaModule
@Override
public void onTouchExplorationStateChanged(boolean enabled) {
updateAndSendChangeEvent(enabled);
updateAndSendTouchExplorationChangeEvent(enabled);
}
}
// Listener that is notified when the global TRANSITION_ANIMATION_SCALE.
private final ContentObserver animationScaleObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
@Override
public void onChange(boolean selfChange) {
this.onChange(selfChange, null);
}
@Override
public void onChange(boolean selfChange, Uri uri) {
if (getReactApplicationContext().hasActiveCatalystInstance()) {
AccessibilityInfoModule.this.updateAndSendReduceMotionChangeEvent();
}
}
};
private @Nullable AccessibilityManager mAccessibilityManager;
private @Nullable ReactTouchExplorationStateChangeListener mTouchExplorationStateChangeListener;
private boolean mEnabled = false;
private final ContentResolver mContentResolver;
private boolean mReduceMotionEnabled = false;
private boolean mTouchExplorationEnabled = false;
private static final String EVENT_NAME = "touchExplorationDidChange";
private static final String REDUCE_MOTION_EVENT_NAME = "reduceMotionDidChange";
private static final String TOUCH_EXPLORATION_EVENT_NAME = "touchExplorationDidChange";
public AccessibilityInfoModule(ReactApplicationContext context) {
super(context);
Context appContext = context.getApplicationContext();
mAccessibilityManager = (AccessibilityManager) appContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
mEnabled = mAccessibilityManager.isTouchExplorationEnabled();
mContentResolver = getReactApplicationContext().getContentResolver();
mTouchExplorationEnabled = mAccessibilityManager.isTouchExplorationEnabled();
mReduceMotionEnabled = this.getIsReduceMotionEnabledValue();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mTouchExplorationStateChangeListener = new ReactTouchExplorationStateChangeListener();
}
@@ -61,16 +88,41 @@ public class AccessibilityInfoModule extends ReactContextBaseJavaModule
return "AccessibilityInfo";
}
@ReactMethod
public void isTouchExplorationEnabled(Callback successCallback) {
successCallback.invoke(mEnabled);
private boolean getIsReduceMotionEnabledValue() {
String value = Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1 ? null
: Settings.Global.getString(
mContentResolver,
Settings.Global.TRANSITION_ANIMATION_SCALE
);
return value != null && value.equals("0.0");
}
private void updateAndSendChangeEvent(boolean enabled) {
if (mEnabled != enabled) {
mEnabled = enabled;
@ReactMethod
public void isReduceMotionEnabled(Callback successCallback) {
successCallback.invoke(mReduceMotionEnabled);
}
@ReactMethod
public void isTouchExplorationEnabled(Callback successCallback) {
successCallback.invoke(mTouchExplorationEnabled);
}
private void updateAndSendReduceMotionChangeEvent() {
boolean isReduceMotionEnabled = this.getIsReduceMotionEnabledValue();
if (mReduceMotionEnabled != isReduceMotionEnabled) {
mReduceMotionEnabled = isReduceMotionEnabled;
getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(EVENT_NAME, mEnabled);
.emit(REDUCE_MOTION_EVENT_NAME, mReduceMotionEnabled);
}
}
private void updateAndSendTouchExplorationChangeEvent(boolean enabled) {
if (mTouchExplorationEnabled != enabled) {
mTouchExplorationEnabled = enabled;
getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(TOUCH_EXPLORATION_EVENT_NAME, mTouchExplorationEnabled);
}
}
@@ -80,7 +132,14 @@ public class AccessibilityInfoModule extends ReactContextBaseJavaModule
mAccessibilityManager.addTouchExplorationStateChangeListener(
mTouchExplorationStateChangeListener);
}
updateAndSendChangeEvent(mAccessibilityManager.isTouchExplorationEnabled());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
Uri transitionUri = Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE);
mContentResolver.registerContentObserver(transitionUri, false, animationScaleObserver);
}
updateAndSendTouchExplorationChangeEvent(mAccessibilityManager.isTouchExplorationEnabled());
updateAndSendReduceMotionChangeEvent();
}
@Override
@@ -89,12 +148,17 @@ public class AccessibilityInfoModule extends ReactContextBaseJavaModule
mAccessibilityManager.removeTouchExplorationStateChangeListener(
mTouchExplorationStateChangeListener);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
mContentResolver.unregisterContentObserver(animationScaleObserver);
}
}
@Override
public void initialize() {
getReactApplicationContext().addLifecycleEventListener(this);
updateAndSendChangeEvent(mAccessibilityManager.isTouchExplorationEnabled());
updateAndSendTouchExplorationChangeEvent(mAccessibilityManager.isTouchExplorationEnabled());
updateAndSendReduceMotionChangeEvent();
}
@Override