Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/jest-preset/jest/mocks/AccessibilityInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ const AccessibilityInfo = {
isAccessibilityServiceEnabled: jest.fn(() =>
Promise.resolve(false),
) as JestMockFn<$FlowFixMe, $FlowFixMe>,
getEnabledAccessibilityServices: jest.fn(() =>
Promise.resolve([]),
) as JestMockFn<$FlowFixMe, $FlowFixMe>,
isShowBordersEnabled: jest.fn(() => Promise.resolve(false)) as JestMockFn<
$FlowFixMe,
$FlowFixMe,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,42 @@ const AccessibilityInfo = {
});
},

/**
* Query the accessibility services that are currently enabled.
*
* Returns a promise which resolves to an array of service IDs, such as
* `com.google.android.marvin.talkback/.TalkBackService`. The array is empty
* when no service is enabled.
*
* @platform android
*/
getEnabledAccessibilityServices(): Promise<Array<string>> {
return new Promise((resolve, reject) => {
if (Platform.OS === 'android') {
if (
NativeAccessibilityInfoAndroid != null &&
NativeAccessibilityInfoAndroid.getEnabledAccessibilityServices != null
) {
NativeAccessibilityInfoAndroid.getEnabledAccessibilityServices(
resolve,
);
} else {
reject(
new Error(
'NativeAccessibilityInfoAndroid.getEnabledAccessibilityServices is not available',
),
);
}
} else {
reject(
new Error(
'getEnabledAccessibilityServices is only available on Android',
),
);
}
});
},

/**
* Add an event handler. Supported events:
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ const mockIsInvertColorsEnabled = jest.fn(onSuccess => onSuccess(true));
const mockIsReduceMotionEnabled = jest.fn(onSuccess => onSuccess(true));
const mockIsTouchExplorationEnabled = jest.fn(onSuccess => onSuccess(true));
const mockIsAccessibilityServiceEnabled = jest.fn(onSuccess => onSuccess(true));
const ANDROID_ENABLED_ACCESSIBILITY_SERVICES = [
'com.google.android.marvin.talkback/.TalkBackService',
];
const mockGetEnabledAccessibilityServices = jest.fn(onSuccess =>
onSuccess(ANDROID_ENABLED_ACCESSIBILITY_SERVICES),
);
const mockGetRecommendedTimeoutMillis = jest.fn((originalTimeout, onSuccess) =>
onSuccess(ANDROID_RECOMMENDED_TIMEOUT),
);
Expand Down Expand Up @@ -149,6 +155,10 @@ let mockNativeAccessibilityInfoAndroid: {
[onSuccess: (isAccessibilityServiceEnabled: boolean) => void],
void,
> | null,
getEnabledAccessibilityServices: JestMockFn<
[onSuccess: (enabledAccessibilityServices: Array<string>) => void],
void,
> | null,
getRecommendedTimeoutMillis: JestMockFn<
[originalTimeout: number, onSuccess: (recommendedTimeout: number) => void],
void,
Expand All @@ -160,6 +170,7 @@ let mockNativeAccessibilityInfoAndroid: {
isReduceMotionEnabled: mockIsReduceMotionEnabled,
isTouchExplorationEnabled: mockIsTouchExplorationEnabled,
isAccessibilityServiceEnabled: mockIsAccessibilityServiceEnabled,
getEnabledAccessibilityServices: mockGetEnabledAccessibilityServices,
getRecommendedTimeoutMillis: mockGetRecommendedTimeoutMillis,
};

Expand Down Expand Up @@ -553,6 +564,87 @@ describe('AccessibilityInfo', () => {
});
});

describe('getEnabledAccessibilityServices', () => {
describe('Android', () => {
it('should call getEnabledAccessibilityServices if available', async () => {
/* $FlowFixMe[incompatible-type] */
Platform.OS = 'android';

const enabledAccessibilityServices =
await AccessibilityInfo.getEnabledAccessibilityServices();

expect(mockGetEnabledAccessibilityServices).toHaveBeenCalled();
expect(enabledAccessibilityServices).toEqual(
ANDROID_ENABLED_ACCESSIBILITY_SERVICES,
);
});

it('should throw error if getEnabledAccessibilityServices is not available', async () => {
/* $FlowFixMe[incompatible-type] */
Platform.OS = 'android';

mockNativeAccessibilityInfoAndroid.getEnabledAccessibilityServices =
null;

const result: unknown =
await AccessibilityInfo.getEnabledAccessibilityServices().catch(
e => e,
);

invariant(
result instanceof Error,
'Expected getEnabledAccessibilityServices to reject',
);
expect(result.message).toEqual(
'NativeAccessibilityInfoAndroid.getEnabledAccessibilityServices is not available',
);
});

it('should throw error if NativeAccessibilityInfoAndroid is not available', async () => {
/* $FlowFixMe[incompatible-type] */
Platform.OS = 'android';

const nativeAccessibilityInfoModule = jest.requireMock(
'../NativeAccessibilityInfo',
);
nativeAccessibilityInfoModule.default = null;

const result: unknown =
await AccessibilityInfo.getEnabledAccessibilityServices().catch(
e => e,
);

invariant(
result instanceof Error,
'Expected getEnabledAccessibilityServices to reject',
);
expect(result.message).toEqual(
'NativeAccessibilityInfoAndroid.getEnabledAccessibilityServices is not available',
);
});
});

describe('iOS', () => {
it('should reject because getEnabledAccessibilityServices is only available on Android', async () => {
/* $FlowFixMe[incompatible-type] */
Platform.OS = 'ios';

const result: unknown =
await AccessibilityInfo.getEnabledAccessibilityServices().catch(
e => e,
);

invariant(
result instanceof Error,
'Expected getEnabledAccessibilityServices to reject',
);
expect(result.message).toEqual(
'getEnabledAccessibilityServices is only available on Android',
);
});
});
});

describe('isReduceMotionEnabled', () => {
describe('Android', () => {
it('should call isReduceMotionEnabled if available', async () => {
Expand Down Expand Up @@ -866,6 +958,7 @@ describe('AccessibilityInfo', () => {
isReduceMotionEnabled: mockIsReduceMotionEnabled,
isTouchExplorationEnabled: mockIsTouchExplorationEnabled,
isAccessibilityServiceEnabled: mockIsAccessibilityServiceEnabled,
getEnabledAccessibilityServices: mockGetEnabledAccessibilityServices,
getRecommendedTimeoutMillis: mockGetRecommendedTimeoutMillis,
};
jest.requireMock('../NativeAccessibilityInfo').default =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

package com.facebook.react.modules.accessibilityinfo

import android.accessibilityservice.AccessibilityServiceInfo
import android.content.ContentResolver
import android.content.Context
import android.database.ContentObserver
Expand All @@ -16,6 +17,7 @@ import android.provider.Settings
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityManager
import com.facebook.fbreact.specs.NativeAccessibilityInfoSpec
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.Callback
import com.facebook.react.bridge.LifecycleEventListener
import com.facebook.react.bridge.ReactApplicationContext
Expand Down Expand Up @@ -178,6 +180,14 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) :
successCallback.invoke(accessibilityServiceEnabled)
}

override fun getEnabledAccessibilityServices(successCallback: Callback) {
val enabledServices = Arguments.createArray()
accessibilityManager
?.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK)
?.forEach { serviceInfo -> serviceInfo.id?.let { enabledServices.pushString(it) } }
successCallback.invoke(enabledServices)
}

private fun updateAndSendReduceMotionChangeEvent() {
val isReduceMotionEnabled = isReduceMotionEnabledValue
if (reduceMotionEnabled != isReduceMotionEnabled) {
Expand Down
5 changes: 3 additions & 2 deletions packages/react-native/ReactNativeApi.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<29785e09db994cf0d43e7426f47c1b30>>
* @generated SignedSource<<fcffd1b1368e65be20bff06cfa3b049a>>
*
* This file was generated by scripts/js-api/build-types/index.js.
*/
Expand Down Expand Up @@ -91,6 +91,7 @@ declare const AccessibilityInfo_default: {
queue?: boolean
},
): void
getEnabledAccessibilityServices(): Promise<Array<string>>
getRecommendedTimeoutMillis(originalTimeout: number): Promise<number>
isAccessibilityServiceEnabled(): Promise<boolean>
isBoldTextEnabled(): Promise<boolean>
Expand Down Expand Up @@ -5773,7 +5774,7 @@ declare type WrapperComponentProvider = (
export {
AccessibilityActionEvent, // a276ec6e
AccessibilityActionInfo, // c94545a7
AccessibilityInfo, // 7d8718ed
AccessibilityInfo, // 132a4e01
AccessibilityProps, // 95dbb1d8
AccessibilityRole, // f2f2e066
AccessibilityState, // b0c2b3f7
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export interface Spec extends TurboModule {
readonly isAccessibilityServiceEnabled?: ?(
onSuccess: (isAccessibilityServiceEnabled: boolean) => void,
) => void;
readonly getEnabledAccessibilityServices?: ?(
onSuccess: (enabledAccessibilityServices: Array<string>) => void,
) => void;
readonly setAccessibilityFocus: (reactTag: number) => void;
readonly announceForAccessibility: (announcement: string) => void;
readonly getRecommendedTimeoutMillis?: (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,17 @@ export interface AccessibilityInfoStatic {
*/
isAccessibilityServiceEnabled(): Promise<boolean>;

/**
* Query the accessibility services that are currently enabled.
*
* Returns a promise which resolves to an array of service IDs, such as
* `com.google.android.marvin.talkback/.TalkBackService`. The array is empty
* when no service is enabled.
*
* @platform android
*/
getEnabledAccessibilityServices(): Promise<Array<string>>;

/**
* Add an event handler. Supported events:
* - announcementFinished: iOS-only event. Fires when the screen reader has finished making an announcement.
Expand Down
1 change: 1 addition & 0 deletions scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api
Original file line number Diff line number Diff line change
Expand Up @@ -2134,6 +2134,7 @@ interface _RCTTypedModuleConstants<StructType> : public NSDictionary <NSString,

protocol NativeAccessibilityInfoSpec : public RCTBridgeModule, public RCTTurboModule {
public virtual void announceForAccessibility:(NSString* announcement);
public virtual void getEnabledAccessibilityServices:(RCTResponseSenderBlock onSuccess);
public virtual void getRecommendedTimeoutMillis:onSuccess:(double mSec, RCTResponseSenderBlock onSuccess);
public virtual void isAccessibilityServiceEnabled:(RCTResponseSenderBlock onSuccess);
public virtual void isGrayscaleEnabled:(RCTResponseSenderBlock onSuccess);
Expand Down
1 change: 1 addition & 0 deletions scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api
Original file line number Diff line number Diff line change
Expand Up @@ -2192,6 +2192,7 @@ interface _RCTTypedModuleConstants<StructType> : public NSDictionary <NSString,

protocol NativeAccessibilityInfoSpec : public RCTBridgeModule, public RCTTurboModule {
public virtual void announceForAccessibility:(NSString* announcement);
public virtual void getEnabledAccessibilityServices:(RCTResponseSenderBlock onSuccess);
public virtual void getRecommendedTimeoutMillis:onSuccess:(double mSec, RCTResponseSenderBlock onSuccess);
public virtual void isAccessibilityServiceEnabled:(RCTResponseSenderBlock onSuccess);
public virtual void isGrayscaleEnabled:(RCTResponseSenderBlock onSuccess);
Expand Down
1 change: 1 addition & 0 deletions scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api
Original file line number Diff line number Diff line change
Expand Up @@ -2127,6 +2127,7 @@ interface _RCTTypedModuleConstants<StructType> : public NSDictionary <NSString,

protocol NativeAccessibilityInfoSpec : public RCTBridgeModule, public RCTTurboModule {
public virtual void announceForAccessibility:(NSString* announcement);
public virtual void getEnabledAccessibilityServices:(RCTResponseSenderBlock onSuccess);
public virtual void getRecommendedTimeoutMillis:onSuccess:(double mSec, RCTResponseSenderBlock onSuccess);
public virtual void isAccessibilityServiceEnabled:(RCTResponseSenderBlock onSuccess);
public virtual void isGrayscaleEnabled:(RCTResponseSenderBlock onSuccess);
Expand Down
1 change: 1 addition & 0 deletions scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api
Original file line number Diff line number Diff line change
Expand Up @@ -2134,6 +2134,7 @@ interface _RCTTypedModuleConstants<StructType> : public NSDictionary <NSString,

protocol NativeAccessibilityInfoSpec : public RCTBridgeModule, public RCTTurboModule {
public virtual void announceForAccessibility:(NSString* announcement);
public virtual void getEnabledAccessibilityServices:(RCTResponseSenderBlock onSuccess);
public virtual void getRecommendedTimeoutMillis:onSuccess:(double mSec, RCTResponseSenderBlock onSuccess);
public virtual void isAccessibilityServiceEnabled:(RCTResponseSenderBlock onSuccess);
public virtual void isGrayscaleEnabled:(RCTResponseSenderBlock onSuccess);
Expand Down
Loading