From 45db61e59fac0a634ba31b4c365c813116624587 Mon Sep 17 00:00:00 2001 From: Typin Date: Sun, 22 Mar 2026 16:33:58 +0800 Subject: [PATCH 1/3] [Android] [Fixed] - Fix borderBottomColor not overriding borderColor The resolve() method in BorderColors had incorrect fallback priority order. Physical edge colors (BOTTOM, TOP, LEFT, RIGHT) should have higher priority than logical block colors (BLOCK_START, BLOCK_END, BLOCK) and shorthand colors. This ensures setting borderBottomColor properly overrides borderBlockColor. Fixes #38335 --- .../react/uimanager/style/BorderColors.kt | 42 +++++++++++++------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/style/BorderColors.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/style/BorderColors.kt index d42df53d3884..605c1e087bb8 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/style/BorderColors.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/style/BorderColors.kt @@ -59,27 +59,35 @@ internal value class BorderColors( * @throws IllegalArgumentException if layoutDirection is not LTR or RTL */ fun resolve(layoutDirection: Int, context: Context): ColorEdges { + // Fix for issue #38335: borderBottomColor does not override borderColor on Android + // Physical edge colors (LEFT, RIGHT, TOP, BOTTOM) should have higher priority than + // logical block colors (BLOCK_START, BLOCK_END, BLOCK) and shorthand colors (ALL, HORIZONTAL, VERTICAL) + // This ensures that setting borderBottomColor properly overrides borderBlockColor return when (layoutDirection) { LayoutDirection.LTR -> ColorEdges( + // Left: START > LEFT > HORIZONTAL > ALL edgeColors[LogicalEdge.START.ordinal] ?: edgeColors[LogicalEdge.LEFT.ordinal] ?: edgeColors[LogicalEdge.HORIZONTAL.ordinal] ?: edgeColors[LogicalEdge.ALL.ordinal] ?: Color.BLACK, - edgeColors[LogicalEdge.BLOCK_START.ordinal] - ?: edgeColors[LogicalEdge.TOP.ordinal] + // Top: Physical edge (TOP) > BLOCK_START > BLOCK > VERTICAL > ALL + edgeColors[LogicalEdge.TOP.ordinal] + ?: edgeColors[LogicalEdge.BLOCK_START.ordinal] ?: edgeColors[LogicalEdge.BLOCK.ordinal] ?: edgeColors[LogicalEdge.VERTICAL.ordinal] ?: edgeColors[LogicalEdge.ALL.ordinal] ?: Color.BLACK, + // Right: END > RIGHT > HORIZONTAL > ALL edgeColors[LogicalEdge.END.ordinal] ?: edgeColors[LogicalEdge.RIGHT.ordinal] ?: edgeColors[LogicalEdge.HORIZONTAL.ordinal] ?: edgeColors[LogicalEdge.ALL.ordinal] ?: Color.BLACK, - edgeColors[LogicalEdge.BLOCK_END.ordinal] - ?: edgeColors[LogicalEdge.BOTTOM.ordinal] + // Bottom: Physical edge (BOTTOM) > BLOCK_END > BLOCK > VERTICAL > ALL + edgeColors[LogicalEdge.BOTTOM.ordinal] + ?: edgeColors[LogicalEdge.BLOCK_END.ordinal] ?: edgeColors[LogicalEdge.BLOCK.ordinal] ?: edgeColors[LogicalEdge.VERTICAL.ordinal] ?: edgeColors[LogicalEdge.ALL.ordinal] @@ -87,50 +95,60 @@ internal value class BorderColors( ) LayoutDirection.RTL -> if (I18nUtil.instance.doLeftAndRightSwapInRTL(context)) { + // RTL with doLeftAndRightSwapInRTL: START/END swap with LEFT/RIGHT ColorEdges( + // Left (becomes RTL right): END > RIGHT > HORIZONTAL > ALL edgeColors[LogicalEdge.END.ordinal] ?: edgeColors[LogicalEdge.RIGHT.ordinal] ?: edgeColors[LogicalEdge.HORIZONTAL.ordinal] ?: edgeColors[LogicalEdge.ALL.ordinal] ?: Color.BLACK, - edgeColors[LogicalEdge.BLOCK_START.ordinal] - ?: edgeColors[LogicalEdge.TOP.ordinal] + // Top: Physical edge (TOP) > BLOCK_START > BLOCK > VERTICAL > ALL + edgeColors[LogicalEdge.TOP.ordinal] + ?: edgeColors[LogicalEdge.BLOCK_START.ordinal] ?: edgeColors[LogicalEdge.BLOCK.ordinal] ?: edgeColors[LogicalEdge.VERTICAL.ordinal] ?: edgeColors[LogicalEdge.ALL.ordinal] ?: Color.BLACK, + // Right (becomes RTL left): START > LEFT > HORIZONTAL > ALL edgeColors[LogicalEdge.START.ordinal] ?: edgeColors[LogicalEdge.LEFT.ordinal] ?: edgeColors[LogicalEdge.HORIZONTAL.ordinal] ?: edgeColors[LogicalEdge.ALL.ordinal] ?: Color.BLACK, - edgeColors[LogicalEdge.BLOCK_END.ordinal] - ?: edgeColors[LogicalEdge.BOTTOM.ordinal] + // Bottom: Physical edge (BOTTOM) > BLOCK_END > BLOCK > VERTICAL > ALL + edgeColors[LogicalEdge.BOTTOM.ordinal] + ?: edgeColors[LogicalEdge.BLOCK_END.ordinal] ?: edgeColors[LogicalEdge.BLOCK.ordinal] ?: edgeColors[LogicalEdge.VERTICAL.ordinal] ?: edgeColors[LogicalEdge.ALL.ordinal] ?: Color.BLACK, ) } else { + // RTL without swap: START/END don't affect LEFT/RIGHT ColorEdges( + // Left: END > LEFT > HORIZONTAL > ALL edgeColors[LogicalEdge.END.ordinal] ?: edgeColors[LogicalEdge.LEFT.ordinal] ?: edgeColors[LogicalEdge.HORIZONTAL.ordinal] ?: edgeColors[LogicalEdge.ALL.ordinal] ?: Color.BLACK, - edgeColors[LogicalEdge.BLOCK_START.ordinal] - ?: edgeColors[LogicalEdge.TOP.ordinal] + // Top: Physical edge (TOP) > BLOCK_START > BLOCK > VERTICAL > ALL + edgeColors[LogicalEdge.TOP.ordinal] + ?: edgeColors[LogicalEdge.BLOCK_START.ordinal] ?: edgeColors[LogicalEdge.BLOCK.ordinal] ?: edgeColors[LogicalEdge.VERTICAL.ordinal] ?: edgeColors[LogicalEdge.ALL.ordinal] ?: Color.BLACK, + // Right: START > RIGHT > HORIZONTAL > ALL edgeColors[LogicalEdge.START.ordinal] ?: edgeColors[LogicalEdge.RIGHT.ordinal] ?: edgeColors[LogicalEdge.HORIZONTAL.ordinal] ?: edgeColors[LogicalEdge.ALL.ordinal] ?: Color.BLACK, - edgeColors[LogicalEdge.BLOCK_END.ordinal] - ?: edgeColors[LogicalEdge.BOTTOM.ordinal] + // Bottom: Physical edge (BOTTOM) > BLOCK_END > BLOCK > VERTICAL > ALL + edgeColors[LogicalEdge.BOTTOM.ordinal] + ?: edgeColors[LogicalEdge.BLOCK_END.ordinal] ?: edgeColors[LogicalEdge.BLOCK.ordinal] ?: edgeColors[LogicalEdge.VERTICAL.ordinal] ?: edgeColors[LogicalEdge.ALL.ordinal] From 65dce42b518a07c34b5887b37a5d94ebe64fbad7 Mon Sep 17 00:00:00 2001 From: Typin Date: Sun, 22 Mar 2026 16:34:03 +0800 Subject: [PATCH 2/3] [Android] [Fixed] - Fix HermesSamplingProfiler JNI 'disable' method mapping The registerNatives() function incorrectly mapped the JNI 'disable' method to HermesSamplingProfiler::enable instead of HermesSamplingProfiler::disable, which corrupted the sampling profiler state when calling HermesSamplingProfiler.disable() from Java. Fixes #56126 --- .../jni/react/hermes/instrumentation/HermesSamplingProfiler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/hermes/instrumentation/HermesSamplingProfiler.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/hermes/instrumentation/HermesSamplingProfiler.cpp index 6bd061f26b9a..77015a6b7577 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/hermes/instrumentation/HermesSamplingProfiler.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/hermes/instrumentation/HermesSamplingProfiler.cpp @@ -34,7 +34,7 @@ void HermesSamplingProfiler::dumpSampledTraceToFile( void HermesSamplingProfiler::registerNatives() { javaClassLocal()->registerNatives({ makeNativeMethod("enable", HermesSamplingProfiler::enable), - makeNativeMethod("disable", HermesSamplingProfiler::enable), + makeNativeMethod("disable", HermesSamplingProfiler::disable), makeNativeMethod( "dumpSampledTraceToFile", HermesSamplingProfiler::dumpSampledTraceToFile), From 62702ce43fe27a937580c069b933fafde5e59f32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B4=BE=E5=A4=AA=E6=BB=A8?= Date: Sun, 29 Mar 2026 07:42:27 +0800 Subject: [PATCH 3/3] feat: Add getInstalledAccessibilityServices and getEnabledAccessibilityServices APIs Add new AccessibilityInfo APIs to query installed and enabled accessibility services on Android. - `getInstalledAccessibilityServices()`: Returns list of all installed accessibility services - `getEnabledAccessibilityServices()`: Returns list of currently enabled accessibility services Each service object contains: - `id`: The unique identifier of the accessibility service - `name`: The human-readable name of the service This allows developers to tailor their app's UI based on specific accessibility services the user has installed or enabled. Closes #30864 Closes #30862 [ANDROID] [ADDED] - Added getInstalledAccessibilityServices and getEnabledAccessibilityServices APIs Co-Authored-By: Claude Opus 4.6 --- .../jest/mocks/AccessibilityInfo.js | 6 ++ .../AccessibilityInfo/AccessibilityInfo.d.ts | 31 ++++++++++ .../AccessibilityInfo/AccessibilityInfo.js | 56 +++++++++++++++++++ .../AccessibilityInfoModule.kt | 40 +++++++++++++ .../modules/NativeAccessibilityInfo.js | 6 ++ 5 files changed, 139 insertions(+) diff --git a/packages/jest-preset/jest/mocks/AccessibilityInfo.js b/packages/jest-preset/jest/mocks/AccessibilityInfo.js index 4dc215bcabd7..ea75dab51f24 100644 --- a/packages/jest-preset/jest/mocks/AccessibilityInfo.js +++ b/packages/jest-preset/jest/mocks/AccessibilityInfo.js @@ -57,6 +57,12 @@ const AccessibilityInfo = { getRecommendedTimeoutMillis: jest.fn(() => Promise.resolve(false), ) as JestMockFn<$FlowFixMe, $FlowFixMe>, + getInstalledAccessibilityServices: jest.fn(() => + Promise.resolve([]), + ) as JestMockFn<$FlowFixMe, $FlowFixMe>, + getEnabledAccessibilityServices: jest.fn(() => + Promise.resolve([]), + ) as JestMockFn<$FlowFixMe, $FlowFixMe>, }; export default AccessibilityInfo; diff --git a/packages/react-native/Libraries/Components/AccessibilityInfo/AccessibilityInfo.d.ts b/packages/react-native/Libraries/Components/AccessibilityInfo/AccessibilityInfo.d.ts index bad75fca1346..3941716e0fac 100644 --- a/packages/react-native/Libraries/Components/AccessibilityInfo/AccessibilityInfo.d.ts +++ b/packages/react-native/Libraries/Components/AccessibilityInfo/AccessibilityInfo.d.ts @@ -40,6 +40,11 @@ type AccessibilityAnnouncementFinishedEventHandler = ( type AccessibilityEventTypes = 'click' | 'focus' | 'viewHoverEnter'; +type AccessibilityServiceInfo = { + id: string; + name: string; +}; + /** * @see https://reactnative.dev/docs/accessibilityinfo */ @@ -171,6 +176,31 @@ export interface AccessibilityInfoStatic { * @platform android */ getRecommendedTimeoutMillis: (originalTimeout: number) => Promise; + + /** + * Get a list of installed accessibility services. + * + * Returns a promise which resolves to an array of accessibility service objects. + * Each object contains: + * - `id`: The unique identifier of the accessibility service + * - `name`: The human-readable name of the accessibility service + * + * @platform android + */ + getInstalledAccessibilityServices: () => Promise>; + + /** + * Get a list of enabled accessibility services. + * + * Returns a promise which resolves to an array of accessibility service objects. + * Each object contains: + * - `id`: The unique identifier of the accessibility service + * - `name`: The human-readable name of the accessibility service + * + * @platform android + */ + getEnabledAccessibilityServices: () => Promise>; + sendAccessibilityEvent: ( handle: HostInstance, eventType: AccessibilityEventTypes, @@ -179,3 +209,4 @@ export interface AccessibilityInfoStatic { export const AccessibilityInfo: AccessibilityInfoStatic; export type AccessibilityInfo = AccessibilityInfoStatic; +export type {AccessibilityServiceInfo}; diff --git a/packages/react-native/Libraries/Components/AccessibilityInfo/AccessibilityInfo.js b/packages/react-native/Libraries/Components/AccessibilityInfo/AccessibilityInfo.js index b74d517151b0..37770af068d1 100644 --- a/packages/react-native/Libraries/Components/AccessibilityInfo/AccessibilityInfo.js +++ b/packages/react-native/Libraries/Components/AccessibilityInfo/AccessibilityInfo.js @@ -525,6 +525,62 @@ const AccessibilityInfo = { return Promise.resolve(originalTimeout); } }, + + /** + * Get a list of installed accessibility services. Android only. + * + * Returns a promise which resolves to an array of accessibility service objects. + * Each object contains: + * - `id`: The unique identifier of the accessibility service + * - `name`: The human-readable name of the accessibility service + * + * See https://reactnative.dev/docs/accessibilityinfo#getinstalledaccessibilityservices + */ + getInstalledAccessibilityServices(): Promise> { + return new Promise((resolve, reject) => { + if (Platform.OS === 'android') { + if (NativeAccessibilityInfoAndroid?.getInstalledAccessibilityServices != null) { + NativeAccessibilityInfoAndroid.getInstalledAccessibilityServices(resolve); + } else { + reject( + new Error( + 'NativeAccessibilityInfoAndroid.getInstalledAccessibilityServices is not available', + ), + ); + } + } else { + resolve([]); + } + }); + }, + + /** + * Get a list of enabled accessibility services. Android only. + * + * Returns a promise which resolves to an array of accessibility service objects. + * Each object contains: + * - `id`: The unique identifier of the accessibility service + * - `name`: The human-readable name of the accessibility service + * + * See https://reactnative.dev/docs/accessibilityinfo#getenabledaccessibilityservices + */ + getEnabledAccessibilityServices(): Promise> { + return new Promise((resolve, reject) => { + if (Platform.OS === 'android') { + if (NativeAccessibilityInfoAndroid?.getEnabledAccessibilityServices != null) { + NativeAccessibilityInfoAndroid.getEnabledAccessibilityServices(resolve); + } else { + reject( + new Error( + 'NativeAccessibilityInfoAndroid.getEnabledAccessibilityServices is not available', + ), + ); + } + } else { + resolve([]); + } + }); + }, }; export default AccessibilityInfo; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/accessibilityinfo/AccessibilityInfoModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/accessibilityinfo/AccessibilityInfoModule.kt index c3c5cff31518..c3c2e01b2c1d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/accessibilityinfo/AccessibilityInfoModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/accessibilityinfo/AccessibilityInfoModule.kt @@ -15,6 +15,10 @@ import android.os.Build import android.provider.Settings import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityManager +import android.accessibilityservice.AccessibilityServiceInfo +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.WritableArray +import com.facebook.react.bridge.WritableMap import com.facebook.fbreact.specs.NativeAccessibilityInfoSpec import com.facebook.react.bridge.Callback import com.facebook.react.bridge.LifecycleEventListener @@ -308,6 +312,42 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) : successCallback.invoke(recommendedTimeout) } + override fun getInstalledAccessibilityServices(successCallback: Callback) { + val servicesList: WritableArray = Arguments.createArray() + if (accessibilityManager == null) { + successCallback.invoke(servicesList) + return + } + + val installedServices = accessibilityManager.getInstalledAccessibilityServiceList() + for (service in installedServices) { + val serviceMap: WritableMap = Arguments.createMap() + serviceMap.putString("id", service.id) + serviceMap.putString("name", service.resolveInfo.loadLabel(reactApplicationContext.packageManager).toString()) + servicesList.pushMap(serviceMap) + } + successCallback.invoke(servicesList) + } + + override fun getEnabledAccessibilityServices(successCallback: Callback) { + val servicesList: WritableArray = Arguments.createArray() + if (accessibilityManager == null) { + successCallback.invoke(servicesList) + return + } + + val enabledServices = accessibilityManager.getEnabledAccessibilityServiceList( + AccessibilityServiceInfo.FEEDBACK_ALL_MASK + ) + for (service in enabledServices) { + val serviceMap: WritableMap = Arguments.createMap() + serviceMap.putString("id", service.id) + serviceMap.putString("name", service.resolveInfo.loadLabel(reactApplicationContext.packageManager).toString()) + servicesList.pushMap(serviceMap) + } + successCallback.invoke(servicesList) + } + companion object { const val NAME: String = NativeAccessibilityInfoSpec.NAME private const val REDUCE_MOTION_EVENT_NAME = "reduceMotionDidChange" diff --git a/packages/react-native/src/private/specs_DEPRECATED/modules/NativeAccessibilityInfo.js b/packages/react-native/src/private/specs_DEPRECATED/modules/NativeAccessibilityInfo.js index 358134c9ed1b..52a9bad8b72f 100644 --- a/packages/react-native/src/private/specs_DEPRECATED/modules/NativeAccessibilityInfo.js +++ b/packages/react-native/src/private/specs_DEPRECATED/modules/NativeAccessibilityInfo.js @@ -37,6 +37,12 @@ export interface Spec extends TurboModule { +isGrayscaleEnabled?: ( onSuccess: (isGrayscaleEnabled: boolean) => void, ) => void; + +getInstalledAccessibilityServices?: ( + onSuccess: (services: Array<{id: string, name: string}>) => void, + ) => void; + +getEnabledAccessibilityServices?: ( + onSuccess: (services: Array<{id: string, name: string}>) => void, + ) => void; } export default (TurboModuleRegistry.get('AccessibilityInfo'): ?Spec);