mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
feat: add isAccessibilityServiceEnabled (#31396)
Summary: fix https://github.com/facebook/react-native/issues/30863 This PR adds `isAccessibilityServiceEnabled` to get if accessibility services are enabled on Android. ## Changelog <!-- Help reviewers and the release process by writing your own changelog entry. For an example, see: https://github.com/facebook/react-native/wiki/Changelog --> [Android] [Added] - Added `isAccessibilityServiceEnabled` to get if accessibility services are enabled Pull Request resolved: https://github.com/facebook/react-native/pull/31396 Test Plan:  Reviewed By: yungsters Differential Revision: D31911880 Pulled By: lunaleaps fbshipit-source-id: 9ae294999a6d46bf051ab658507bf97764a945d2
This commit is contained in:
committed by
Facebook GitHub Bot
parent
bbb52aa276
commit
c8b83d4e0b
@@ -19,6 +19,11 @@ import NativeAccessibilityManagerIOS from './NativeAccessibilityManager';
|
||||
import legacySendAccessibilityEvent from './legacySendAccessibilityEvent';
|
||||
import type {ElementRef} from 'react';
|
||||
|
||||
// Events that are only supported on Android.
|
||||
type AccessibilityEventDefinitionsAndroid = {
|
||||
accessibilityServiceChanged: [boolean],
|
||||
};
|
||||
|
||||
// Events that are only supported on iOS.
|
||||
type AccessibilityEventDefinitionsIOS = {
|
||||
announcementFinished: [{announcement: string, success: boolean}],
|
||||
@@ -29,6 +34,7 @@ type AccessibilityEventDefinitionsIOS = {
|
||||
};
|
||||
|
||||
type AccessibilityEventDefinitions = {
|
||||
...AccessibilityEventDefinitionsAndroid,
|
||||
...AccessibilityEventDefinitionsIOS,
|
||||
change: [boolean], // screenReaderChanged
|
||||
reduceMotionChanged: [boolean],
|
||||
@@ -44,6 +50,7 @@ const EventNames: Map<$Keys<AccessibilityEventDefinitions>, string> =
|
||||
['change', 'touchExplorationDidChange'],
|
||||
['reduceMotionChanged', 'reduceMotionDidChange'],
|
||||
['screenReaderChanged', 'touchExplorationDidChange'],
|
||||
['accessibilityServiceChanged', 'accessibilityServiceDidChange'],
|
||||
])
|
||||
: new Map([
|
||||
['announcementFinished', 'announcementFinished'],
|
||||
@@ -224,6 +231,33 @@ const AccessibilityInfo = {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Query whether Accessibility Service is currently enabled.
|
||||
*
|
||||
* Returns a promise which resolves to a boolean.
|
||||
* The result is `true` when any service is enabled and `false` otherwise.
|
||||
*
|
||||
* @platform android
|
||||
*
|
||||
* See https://reactnative.dev/docs/accessibilityinfo/#isaccessibilityserviceenabled-android
|
||||
*/
|
||||
isAccessibilityServiceEnabled(): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (Platform.OS === 'android') {
|
||||
if (
|
||||
NativeAccessibilityInfoAndroid != null &&
|
||||
NativeAccessibilityInfoAndroid.isAccessibilityServiceEnabled != null
|
||||
) {
|
||||
NativeAccessibilityInfoAndroid.isAccessibilityServiceEnabled(resolve);
|
||||
} else {
|
||||
reject(null);
|
||||
}
|
||||
} else {
|
||||
reject(null);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Add an event handler. Supported events:
|
||||
*
|
||||
|
||||
@@ -18,6 +18,9 @@ export interface Spec extends TurboModule {
|
||||
+isTouchExplorationEnabled: (
|
||||
onSuccess: (isScreenReaderEnabled: boolean) => void,
|
||||
) => void;
|
||||
+isAccessibilityServiceEnabled?: ?(
|
||||
onSuccess: (isAccessibilityServiceEnabled: boolean) => void,
|
||||
) => void;
|
||||
+setAccessibilityFocus: (reactTag: number) => void;
|
||||
+announceForAccessibility: (announcement: string) => void;
|
||||
+getRecommendedTimeoutMillis?: (
|
||||
|
||||
+41
@@ -46,6 +46,19 @@ public class AccessibilityInfoModule extends NativeAccessibilityInfoSpec
|
||||
}
|
||||
}
|
||||
|
||||
// Android can listen for accessibility service enable with `accessibilityStateChange`, but
|
||||
// `accessibilityState` conflicts with React Native props and confuses developers. Therefore, the
|
||||
// name `accessibilityServiceChange` is used here instead.
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
private class ReactAccessibilityServiceChangeListener
|
||||
implements AccessibilityManager.AccessibilityStateChangeListener {
|
||||
|
||||
@Override
|
||||
public void onAccessibilityStateChanged(boolean enabled) {
|
||||
updateAndSendAccessibilityServiceChangeEvent(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
// Listener that is notified when the global TRANSITION_ANIMATION_SCALE.
|
||||
private final ContentObserver animationScaleObserver =
|
||||
new ContentObserver(new Handler(Looper.getMainLooper())) {
|
||||
@@ -64,13 +77,16 @@ public class AccessibilityInfoModule extends NativeAccessibilityInfoSpec
|
||||
|
||||
private @Nullable AccessibilityManager mAccessibilityManager;
|
||||
private @Nullable ReactTouchExplorationStateChangeListener mTouchExplorationStateChangeListener;
|
||||
private @Nullable ReactAccessibilityServiceChangeListener mAccessibilityServiceChangeListener;
|
||||
private final ContentResolver mContentResolver;
|
||||
private boolean mReduceMotionEnabled = false;
|
||||
private boolean mTouchExplorationEnabled = false;
|
||||
private boolean mAccessibilityServiceEnabled = false;
|
||||
private int mRecommendedTimeout;
|
||||
|
||||
private static final String REDUCE_MOTION_EVENT_NAME = "reduceMotionDidChange";
|
||||
private static final String TOUCH_EXPLORATION_EVENT_NAME = "touchExplorationDidChange";
|
||||
private static final String ACCESSIBILITY_SERVICE_EVENT_NAME = "accessibilityServiceDidChange";
|
||||
|
||||
public AccessibilityInfoModule(ReactApplicationContext context) {
|
||||
super(context);
|
||||
@@ -79,8 +95,10 @@ public class AccessibilityInfoModule extends NativeAccessibilityInfoSpec
|
||||
(AccessibilityManager) appContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
|
||||
mContentResolver = getReactApplicationContext().getContentResolver();
|
||||
mTouchExplorationEnabled = mAccessibilityManager.isTouchExplorationEnabled();
|
||||
mAccessibilityServiceEnabled = mAccessibilityManager.isEnabled();
|
||||
mReduceMotionEnabled = this.getIsReduceMotionEnabledValue();
|
||||
mTouchExplorationStateChangeListener = new ReactTouchExplorationStateChangeListener();
|
||||
mAccessibilityServiceChangeListener = new ReactAccessibilityServiceChangeListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -106,6 +124,11 @@ public class AccessibilityInfoModule extends NativeAccessibilityInfoSpec
|
||||
successCallback.invoke(mTouchExplorationEnabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isAccessibilityServiceEnabled(Callback successCallback) {
|
||||
successCallback.invoke(mAccessibilityServiceEnabled);
|
||||
}
|
||||
|
||||
private void updateAndSendReduceMotionChangeEvent() {
|
||||
boolean isReduceMotionEnabled = this.getIsReduceMotionEnabledValue();
|
||||
|
||||
@@ -134,16 +157,31 @@ public class AccessibilityInfoModule extends NativeAccessibilityInfoSpec
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAndSendAccessibilityServiceChangeEvent(boolean enabled) {
|
||||
if (mAccessibilityServiceEnabled != enabled) {
|
||||
mAccessibilityServiceEnabled = enabled;
|
||||
|
||||
ReactApplicationContext reactApplicationContext = getReactApplicationContextIfActiveOrWarn();
|
||||
if (reactApplicationContext != null) {
|
||||
getReactApplicationContext()
|
||||
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
||||
.emit(ACCESSIBILITY_SERVICE_EVENT_NAME, mAccessibilityServiceEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public void onHostResume() {
|
||||
mAccessibilityManager.addTouchExplorationStateChangeListener(
|
||||
mTouchExplorationStateChangeListener);
|
||||
mAccessibilityManager.addAccessibilityStateChangeListener(mAccessibilityServiceChangeListener);
|
||||
|
||||
Uri transitionUri = Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE);
|
||||
mContentResolver.registerContentObserver(transitionUri, false, animationScaleObserver);
|
||||
|
||||
updateAndSendTouchExplorationChangeEvent(mAccessibilityManager.isTouchExplorationEnabled());
|
||||
updateAndSendAccessibilityServiceChangeEvent(mAccessibilityManager.isEnabled());
|
||||
updateAndSendReduceMotionChangeEvent();
|
||||
}
|
||||
|
||||
@@ -152,6 +190,8 @@ public class AccessibilityInfoModule extends NativeAccessibilityInfoSpec
|
||||
public void onHostPause() {
|
||||
mAccessibilityManager.removeTouchExplorationStateChangeListener(
|
||||
mTouchExplorationStateChangeListener);
|
||||
mAccessibilityManager.removeAccessibilityStateChangeListener(
|
||||
mAccessibilityServiceChangeListener);
|
||||
|
||||
mContentResolver.unregisterContentObserver(animationScaleObserver);
|
||||
}
|
||||
@@ -160,6 +200,7 @@ public class AccessibilityInfoModule extends NativeAccessibilityInfoSpec
|
||||
public void initialize() {
|
||||
getReactApplicationContext().addLifecycleEventListener(this);
|
||||
updateAndSendTouchExplorationChangeEvent(mAccessibilityManager.isTouchExplorationEnabled());
|
||||
updateAndSendAccessibilityServiceChangeEvent(mAccessibilityManager.isEnabled());
|
||||
updateAndSendReduceMotionChangeEvent();
|
||||
}
|
||||
|
||||
|
||||
@@ -123,6 +123,7 @@ jest
|
||||
default: {
|
||||
addEventListener: jest.fn(),
|
||||
announceForAccessibility: jest.fn(),
|
||||
isAccessibilityServiceEnabled: jest.fn(),
|
||||
isBoldTextEnabled: jest.fn(),
|
||||
isGrayscaleEnabled: jest.fn(),
|
||||
isInvertColorsEnabled: jest.fn(),
|
||||
|
||||
@@ -942,6 +942,19 @@ class EnabledExamples extends React.Component<{}> {
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{Platform.OS === 'android' ? (
|
||||
<RNTesterBlock
|
||||
title="isAccessibilityServiceEnabled()"
|
||||
description={
|
||||
'Event emitted whenever an accessibility service is enabled. This includes TalkBack as well as assistive technologies such as "Select to Speak".'
|
||||
}>
|
||||
<EnabledExample
|
||||
test="any accessibility service"
|
||||
eventListener="accessibilityServiceChanged"
|
||||
/>
|
||||
</RNTesterBlock>
|
||||
) : null}
|
||||
|
||||
<RNTesterBlock title="isReduceMotionEnabled()">
|
||||
<EnabledExample
|
||||
test="reduce motion"
|
||||
@@ -969,7 +982,8 @@ class EnabledExample extends React.Component<
|
||||
| 'invertColorsChanged'
|
||||
| 'reduceTransparencyChanged'
|
||||
| 'reduceMotionChanged'
|
||||
| 'screenReaderChanged',
|
||||
| 'screenReaderChanged'
|
||||
| 'accessibilityServiceChanged',
|
||||
test: string,
|
||||
},
|
||||
{
|
||||
@@ -991,6 +1005,10 @@ class EnabledExample extends React.Component<
|
||||
return AccessibilityInfo.isReduceMotionEnabled().then(state => {
|
||||
this.setState({isEnabled: state});
|
||||
});
|
||||
case 'accessibilityServiceChanged':
|
||||
return AccessibilityInfo.isAccessibilityServiceEnabled().then(state => {
|
||||
this.setState({isEnabled: state});
|
||||
});
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user