Skip to content

Commit c8b83d4

Browse files
grgr-dkrkfacebook-github-bot
authored andcommitted
feat: add isAccessibilityServiceEnabled (#31396)
Summary: fix #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: #31396 Test Plan: ![accessibilityService](https://user-images.githubusercontent.com/40130327/115560972-11d5b100-a2f0-11eb-8aa2-7c52dc71ca59.gif) Reviewed By: yungsters Differential Revision: D31911880 Pulled By: lunaleaps fbshipit-source-id: 9ae294999a6d46bf051ab658507bf97764a945d2
1 parent bbb52aa commit c8b83d4

File tree

5 files changed

+98
-1
lines changed

5 files changed

+98
-1
lines changed

Libraries/Components/AccessibilityInfo/AccessibilityInfo.js

+34
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ import NativeAccessibilityManagerIOS from './NativeAccessibilityManager';
1919
import legacySendAccessibilityEvent from './legacySendAccessibilityEvent';
2020
import type {ElementRef} from 'react';
2121

22+
// Events that are only supported on Android.
23+
type AccessibilityEventDefinitionsAndroid = {
24+
accessibilityServiceChanged: [boolean],
25+
};
26+
2227
// Events that are only supported on iOS.
2328
type AccessibilityEventDefinitionsIOS = {
2429
announcementFinished: [{announcement: string, success: boolean}],
@@ -29,6 +34,7 @@ type AccessibilityEventDefinitionsIOS = {
2934
};
3035

3136
type AccessibilityEventDefinitions = {
37+
...AccessibilityEventDefinitionsAndroid,
3238
...AccessibilityEventDefinitionsIOS,
3339
change: [boolean], // screenReaderChanged
3440
reduceMotionChanged: [boolean],
@@ -44,6 +50,7 @@ const EventNames: Map<$Keys<AccessibilityEventDefinitions>, string> =
4450
['change', 'touchExplorationDidChange'],
4551
['reduceMotionChanged', 'reduceMotionDidChange'],
4652
['screenReaderChanged', 'touchExplorationDidChange'],
53+
['accessibilityServiceChanged', 'accessibilityServiceDidChange'],
4754
])
4855
: new Map([
4956
['announcementFinished', 'announcementFinished'],
@@ -224,6 +231,33 @@ const AccessibilityInfo = {
224231
});
225232
},
226233

234+
/**
235+
* Query whether Accessibility Service is currently enabled.
236+
*
237+
* Returns a promise which resolves to a boolean.
238+
* The result is `true` when any service is enabled and `false` otherwise.
239+
*
240+
* @platform android
241+
*
242+
* See https://reactnative.dev/docs/accessibilityinfo/#isaccessibilityserviceenabled-android
243+
*/
244+
isAccessibilityServiceEnabled(): Promise<boolean> {
245+
return new Promise((resolve, reject) => {
246+
if (Platform.OS === 'android') {
247+
if (
248+
NativeAccessibilityInfoAndroid != null &&
249+
NativeAccessibilityInfoAndroid.isAccessibilityServiceEnabled != null
250+
) {
251+
NativeAccessibilityInfoAndroid.isAccessibilityServiceEnabled(resolve);
252+
} else {
253+
reject(null);
254+
}
255+
} else {
256+
reject(null);
257+
}
258+
});
259+
},
260+
227261
/**
228262
* Add an event handler. Supported events:
229263
*

Libraries/Components/AccessibilityInfo/NativeAccessibilityInfo.js

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ export interface Spec extends TurboModule {
1818
+isTouchExplorationEnabled: (
1919
onSuccess: (isScreenReaderEnabled: boolean) => void,
2020
) => void;
21+
+isAccessibilityServiceEnabled?: ?(
22+
onSuccess: (isAccessibilityServiceEnabled: boolean) => void,
23+
) => void;
2124
+setAccessibilityFocus: (reactTag: number) => void;
2225
+announceForAccessibility: (announcement: string) => void;
2326
+getRecommendedTimeoutMillis?: (

ReactAndroid/src/main/java/com/facebook/react/modules/accessibilityinfo/AccessibilityInfoModule.java

+41
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,19 @@ public void onTouchExplorationStateChanged(boolean enabled) {
4646
}
4747
}
4848

49+
// Android can listen for accessibility service enable with `accessibilityStateChange`, but
50+
// `accessibilityState` conflicts with React Native props and confuses developers. Therefore, the
51+
// name `accessibilityServiceChange` is used here instead.
52+
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
53+
private class ReactAccessibilityServiceChangeListener
54+
implements AccessibilityManager.AccessibilityStateChangeListener {
55+
56+
@Override
57+
public void onAccessibilityStateChanged(boolean enabled) {
58+
updateAndSendAccessibilityServiceChangeEvent(enabled);
59+
}
60+
}
61+
4962
// Listener that is notified when the global TRANSITION_ANIMATION_SCALE.
5063
private final ContentObserver animationScaleObserver =
5164
new ContentObserver(new Handler(Looper.getMainLooper())) {
@@ -64,13 +77,16 @@ public void onChange(boolean selfChange, Uri uri) {
6477

6578
private @Nullable AccessibilityManager mAccessibilityManager;
6679
private @Nullable ReactTouchExplorationStateChangeListener mTouchExplorationStateChangeListener;
80+
private @Nullable ReactAccessibilityServiceChangeListener mAccessibilityServiceChangeListener;
6781
private final ContentResolver mContentResolver;
6882
private boolean mReduceMotionEnabled = false;
6983
private boolean mTouchExplorationEnabled = false;
84+
private boolean mAccessibilityServiceEnabled = false;
7085
private int mRecommendedTimeout;
7186

7287
private static final String REDUCE_MOTION_EVENT_NAME = "reduceMotionDidChange";
7388
private static final String TOUCH_EXPLORATION_EVENT_NAME = "touchExplorationDidChange";
89+
private static final String ACCESSIBILITY_SERVICE_EVENT_NAME = "accessibilityServiceDidChange";
7490

7591
public AccessibilityInfoModule(ReactApplicationContext context) {
7692
super(context);
@@ -79,8 +95,10 @@ public AccessibilityInfoModule(ReactApplicationContext context) {
7995
(AccessibilityManager) appContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
8096
mContentResolver = getReactApplicationContext().getContentResolver();
8197
mTouchExplorationEnabled = mAccessibilityManager.isTouchExplorationEnabled();
98+
mAccessibilityServiceEnabled = mAccessibilityManager.isEnabled();
8299
mReduceMotionEnabled = this.getIsReduceMotionEnabledValue();
83100
mTouchExplorationStateChangeListener = new ReactTouchExplorationStateChangeListener();
101+
mAccessibilityServiceChangeListener = new ReactAccessibilityServiceChangeListener();
84102
}
85103

86104
@Override
@@ -106,6 +124,11 @@ public void isTouchExplorationEnabled(Callback successCallback) {
106124
successCallback.invoke(mTouchExplorationEnabled);
107125
}
108126

127+
@Override
128+
public void isAccessibilityServiceEnabled(Callback successCallback) {
129+
successCallback.invoke(mAccessibilityServiceEnabled);
130+
}
131+
109132
private void updateAndSendReduceMotionChangeEvent() {
110133
boolean isReduceMotionEnabled = this.getIsReduceMotionEnabledValue();
111134

@@ -134,16 +157,31 @@ private void updateAndSendTouchExplorationChangeEvent(boolean enabled) {
134157
}
135158
}
136159

160+
private void updateAndSendAccessibilityServiceChangeEvent(boolean enabled) {
161+
if (mAccessibilityServiceEnabled != enabled) {
162+
mAccessibilityServiceEnabled = enabled;
163+
164+
ReactApplicationContext reactApplicationContext = getReactApplicationContextIfActiveOrWarn();
165+
if (reactApplicationContext != null) {
166+
getReactApplicationContext()
167+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
168+
.emit(ACCESSIBILITY_SERVICE_EVENT_NAME, mAccessibilityServiceEnabled);
169+
}
170+
}
171+
}
172+
137173
@Override
138174
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
139175
public void onHostResume() {
140176
mAccessibilityManager.addTouchExplorationStateChangeListener(
141177
mTouchExplorationStateChangeListener);
178+
mAccessibilityManager.addAccessibilityStateChangeListener(mAccessibilityServiceChangeListener);
142179

143180
Uri transitionUri = Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE);
144181
mContentResolver.registerContentObserver(transitionUri, false, animationScaleObserver);
145182

146183
updateAndSendTouchExplorationChangeEvent(mAccessibilityManager.isTouchExplorationEnabled());
184+
updateAndSendAccessibilityServiceChangeEvent(mAccessibilityManager.isEnabled());
147185
updateAndSendReduceMotionChangeEvent();
148186
}
149187

@@ -152,6 +190,8 @@ public void onHostResume() {
152190
public void onHostPause() {
153191
mAccessibilityManager.removeTouchExplorationStateChangeListener(
154192
mTouchExplorationStateChangeListener);
193+
mAccessibilityManager.removeAccessibilityStateChangeListener(
194+
mAccessibilityServiceChangeListener);
155195

156196
mContentResolver.unregisterContentObserver(animationScaleObserver);
157197
}
@@ -160,6 +200,7 @@ public void onHostPause() {
160200
public void initialize() {
161201
getReactApplicationContext().addLifecycleEventListener(this);
162202
updateAndSendTouchExplorationChangeEvent(mAccessibilityManager.isTouchExplorationEnabled());
203+
updateAndSendAccessibilityServiceChangeEvent(mAccessibilityManager.isEnabled());
163204
updateAndSendReduceMotionChangeEvent();
164205
}
165206

jest/setup.js

+1
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ jest
123123
default: {
124124
addEventListener: jest.fn(),
125125
announceForAccessibility: jest.fn(),
126+
isAccessibilityServiceEnabled: jest.fn(),
126127
isBoldTextEnabled: jest.fn(),
127128
isGrayscaleEnabled: jest.fn(),
128129
isInvertColorsEnabled: jest.fn(),

packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js

+19-1
Original file line numberDiff line numberDiff line change
@@ -942,6 +942,19 @@ class EnabledExamples extends React.Component<{}> {
942942
</>
943943
) : null}
944944

945+
{Platform.OS === 'android' ? (
946+
<RNTesterBlock
947+
title="isAccessibilityServiceEnabled()"
948+
description={
949+
'Event emitted whenever an accessibility service is enabled. This includes TalkBack as well as assistive technologies such as "Select to Speak".'
950+
}>
951+
<EnabledExample
952+
test="any accessibility service"
953+
eventListener="accessibilityServiceChanged"
954+
/>
955+
</RNTesterBlock>
956+
) : null}
957+
945958
<RNTesterBlock title="isReduceMotionEnabled()">
946959
<EnabledExample
947960
test="reduce motion"
@@ -969,7 +982,8 @@ class EnabledExample extends React.Component<
969982
| 'invertColorsChanged'
970983
| 'reduceTransparencyChanged'
971984
| 'reduceMotionChanged'
972-
| 'screenReaderChanged',
985+
| 'screenReaderChanged'
986+
| 'accessibilityServiceChanged',
973987
test: string,
974988
},
975989
{
@@ -991,6 +1005,10 @@ class EnabledExample extends React.Component<
9911005
return AccessibilityInfo.isReduceMotionEnabled().then(state => {
9921006
this.setState({isEnabled: state});
9931007
});
1008+
case 'accessibilityServiceChanged':
1009+
return AccessibilityInfo.isAccessibilityServiceEnabled().then(state => {
1010+
this.setState({isEnabled: state});
1011+
});
9941012
default:
9951013
return null;
9961014
}

0 commit comments

Comments
 (0)