mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
add support for Android's "High Text Contrast" setting into AccessibilityInfo (#46746)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/46746 This change adds `isHighTextContrastEnabled()` to `AccessibilityInfo` to enable access to Android OS's "High contrast text" setting option. It also adds a new event, `highContrastTextChanged`, to enable listeners to subscribe to changes on this setting. ## Changelog [Android][Added] - Added `isHighTextContrastEnabled()` to `AccessibilityInfo` to read `ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED` setting value Reviewed By: NickGerleman Differential Revision: D63155444 fbshipit-source-id: 9829b40e6c183f6beba732190dc318894e9d9a3f
This commit is contained in:
committed by
Facebook GitHub Bot
parent
d19a2178b6
commit
d4ea147b41
+9
@@ -17,6 +17,7 @@ type AccessibilityChangeEventName =
|
||||
| 'grayscaleChanged' // iOS-only Event
|
||||
| 'invertColorsChanged' // iOS-only Event
|
||||
| 'reduceMotionChanged'
|
||||
| 'highTextContrastChanged' // Android-only Event
|
||||
| 'screenReaderChanged'
|
||||
| 'reduceTransparencyChanged'; // iOS-only Event
|
||||
|
||||
@@ -69,6 +70,14 @@ export interface AccessibilityInfoStatic {
|
||||
*/
|
||||
isReduceMotionEnabled: () => Promise<boolean>;
|
||||
|
||||
/**
|
||||
*
|
||||
* Query whether high text contrast is currently enabled.
|
||||
*
|
||||
* @platform android
|
||||
*/
|
||||
isHighTextContrastEnabled: () => Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Query whether reduce motion and prefer cross-fade transitions settings are currently enabled.
|
||||
*
|
||||
|
||||
+28
@@ -22,6 +22,7 @@ import NativeAccessibilityManagerIOS from './NativeAccessibilityManager';
|
||||
// Events that are only supported on Android.
|
||||
type AccessibilityEventDefinitionsAndroid = {
|
||||
accessibilityServiceChanged: [boolean],
|
||||
highTextContrastChanged: [boolean],
|
||||
};
|
||||
|
||||
// Events that are only supported on iOS.
|
||||
@@ -51,6 +52,7 @@ const EventNames: Map<
|
||||
? new Map([
|
||||
['change', 'touchExplorationDidChange'],
|
||||
['reduceMotionChanged', 'reduceMotionDidChange'],
|
||||
['highTextContrastChanged', 'highTextContrastDidChange'],
|
||||
['screenReaderChanged', 'touchExplorationDidChange'],
|
||||
['accessibilityServiceChanged', 'accessibilityServiceDidChange'],
|
||||
])
|
||||
@@ -179,6 +181,26 @@ const AccessibilityInfo = {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Query whether high text contrast is currently enabled. Android only.
|
||||
*
|
||||
* Returns a promise which resolves to a boolean.
|
||||
* The result is `true` when high text contrast is enabled and `false` otherwise.
|
||||
*/
|
||||
isHighTextContrastEnabled(): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (Platform.OS === 'android') {
|
||||
if (NativeAccessibilityInfoAndroid?.isHighTextContrastEnabled != null) {
|
||||
NativeAccessibilityInfoAndroid.isHighTextContrastEnabled(resolve);
|
||||
} else {
|
||||
reject(null);
|
||||
}
|
||||
} else {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Query whether reduce motion and prefer cross-fade transitions settings are currently enabled.
|
||||
*
|
||||
@@ -320,6 +342,12 @@ const AccessibilityInfo = {
|
||||
* - `success`: A boolean indicating whether the announcement was
|
||||
* successfully made.
|
||||
*
|
||||
* These events are only supported on Android:
|
||||
*
|
||||
* - `highTextContrastChanged`: Android-only event. Fires when the state of the high text contrast
|
||||
* toggle changes. The argument to the event handler is a boolean. The boolean is `true` when
|
||||
* high text contrast is enabled and `false` otherwise.
|
||||
*
|
||||
* See https://reactnative.dev/docs/accessibilityinfo#addeventlistener
|
||||
*/
|
||||
addEventListener<K: $Keys<AccessibilityEventDefinitions>>(
|
||||
|
||||
@@ -1559,6 +1559,7 @@ declare module.exports: getData;
|
||||
exports[`public API should not change unintentionally Libraries/Components/AccessibilityInfo/AccessibilityInfo.js 1`] = `
|
||||
"type AccessibilityEventDefinitionsAndroid = {
|
||||
accessibilityServiceChanged: [boolean],
|
||||
highTextContrastChanged: [boolean],
|
||||
};
|
||||
type AccessibilityEventDefinitionsIOS = {
|
||||
announcementFinished: [{ announcement: string, success: boolean }],
|
||||
@@ -1580,6 +1581,7 @@ declare const AccessibilityInfo: {
|
||||
isGrayscaleEnabled(): Promise<boolean>,
|
||||
isInvertColorsEnabled(): Promise<boolean>,
|
||||
isReduceMotionEnabled(): Promise<boolean>,
|
||||
isHighTextContrastEnabled(): Promise<boolean>,
|
||||
prefersCrossFadeTransitions(): Promise<boolean>,
|
||||
isReduceTransparencyEnabled(): Promise<boolean>,
|
||||
isScreenReaderEnabled(): Promise<boolean>,
|
||||
|
||||
@@ -3021,6 +3021,7 @@ public final class com/facebook/react/modules/accessibilityinfo/AccessibilityInf
|
||||
public fun initialize ()V
|
||||
public fun invalidate ()V
|
||||
public fun isAccessibilityServiceEnabled (Lcom/facebook/react/bridge/Callback;)V
|
||||
public fun isHighTextContrastEnabled (Lcom/facebook/react/bridge/Callback;)V
|
||||
public fun isReduceMotionEnabled (Lcom/facebook/react/bridge/Callback;)V
|
||||
public fun isTouchExplorationEnabled (Lcom/facebook/react/bridge/Callback;)V
|
||||
public fun onHostDestroy ()V
|
||||
|
||||
+55
-1
@@ -62,6 +62,19 @@ public class AccessibilityInfoModule(context: ReactApplicationContext) :
|
||||
}
|
||||
}
|
||||
}
|
||||
// Listener that is notified when the ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED changes.
|
||||
private val highTextContrastObserver: ContentObserver =
|
||||
object : ContentObserver(UiThreadUtil.getUiThreadHandler()) {
|
||||
override fun onChange(selfChange: Boolean) {
|
||||
this.onChange(selfChange, null)
|
||||
}
|
||||
|
||||
override fun onChange(selfChange: Boolean, uri: Uri?) {
|
||||
if (getReactApplicationContext().hasActiveReactInstance()) {
|
||||
updateAndSendHighTextContrastChangeEvent()
|
||||
}
|
||||
}
|
||||
}
|
||||
private val accessibilityManager: AccessibilityManager?
|
||||
private val touchExplorationStateChangeListener: ReactTouchExplorationStateChangeListener =
|
||||
ReactTouchExplorationStateChangeListener()
|
||||
@@ -69,6 +82,7 @@ public class AccessibilityInfoModule(context: ReactApplicationContext) :
|
||||
ReactAccessibilityServiceChangeListener()
|
||||
private val contentResolver: ContentResolver
|
||||
private var reduceMotionEnabled = false
|
||||
private var highTextContrastEnabled = false
|
||||
private var touchExplorationEnabled = false
|
||||
private var accessibilityServiceEnabled = false
|
||||
private var recommendedTimeout = 0
|
||||
@@ -81,6 +95,7 @@ public class AccessibilityInfoModule(context: ReactApplicationContext) :
|
||||
touchExplorationEnabled = accessibilityManager.isTouchExplorationEnabled
|
||||
accessibilityServiceEnabled = accessibilityManager.isEnabled
|
||||
reduceMotionEnabled = isReduceMotionEnabledValue
|
||||
highTextContrastEnabled = isHighTextContrastEnabledValue
|
||||
}
|
||||
|
||||
@get:TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
@@ -96,10 +111,24 @@ public class AccessibilityInfoModule(context: ReactApplicationContext) :
|
||||
return parsedValue == 0f
|
||||
}
|
||||
|
||||
@get:TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
private val isHighTextContrastEnabledValue: Boolean
|
||||
get() {
|
||||
return Settings.Secure.getInt(
|
||||
contentResolver,
|
||||
ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED_CONSTANT,
|
||||
0,
|
||||
) != 0
|
||||
}
|
||||
|
||||
override fun isReduceMotionEnabled(successCallback: Callback) {
|
||||
successCallback.invoke(reduceMotionEnabled)
|
||||
}
|
||||
|
||||
override fun isHighTextContrastEnabled(successCallback: Callback) {
|
||||
successCallback.invoke(highTextContrastEnabled)
|
||||
}
|
||||
|
||||
override fun isTouchExplorationEnabled(successCallback: Callback) {
|
||||
successCallback.invoke(touchExplorationEnabled)
|
||||
}
|
||||
@@ -119,6 +148,20 @@ public class AccessibilityInfoModule(context: ReactApplicationContext) :
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateAndSendHighTextContrastChangeEvent() {
|
||||
val isHighTextContrastEnabled = isHighTextContrastEnabledValue
|
||||
if (highTextContrastEnabled != isHighTextContrastEnabled) {
|
||||
highTextContrastEnabled = isHighTextContrastEnabled
|
||||
val reactApplicationContext = getReactApplicationContextIfActiveOrWarn()
|
||||
if (reactApplicationContext != null) {
|
||||
reactApplicationContext.emitDeviceEvent(
|
||||
HIGH_TEXT_CONTRAST_EVENT_NAME,
|
||||
highTextContrastEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateAndSendTouchExplorationChangeEvent(enabled: Boolean) {
|
||||
if (touchExplorationEnabled != enabled) {
|
||||
touchExplorationEnabled = enabled
|
||||
@@ -148,10 +191,14 @@ public class AccessibilityInfoModule(context: ReactApplicationContext) :
|
||||
accessibilityManager?.addAccessibilityStateChangeListener(accessibilityServiceChangeListener)
|
||||
val transitionUri = Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE)
|
||||
contentResolver.registerContentObserver(transitionUri, false, animationScaleObserver)
|
||||
val highTextContrastUri =
|
||||
Settings.Global.getUriFor(ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED_CONSTANT)
|
||||
contentResolver.registerContentObserver(highTextContrastUri, false, highTextContrastObserver)
|
||||
updateAndSendTouchExplorationChangeEvent(
|
||||
accessibilityManager?.isTouchExplorationEnabled == true)
|
||||
updateAndSendAccessibilityServiceChangeEvent(accessibilityManager?.isEnabled == true)
|
||||
updateAndSendReduceMotionChangeEvent()
|
||||
updateAndSendHighTextContrastChangeEvent()
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
@@ -160,6 +207,7 @@ public class AccessibilityInfoModule(context: ReactApplicationContext) :
|
||||
touchExplorationStateChangeListener)
|
||||
accessibilityManager?.removeAccessibilityStateChangeListener(accessibilityServiceChangeListener)
|
||||
contentResolver.unregisterContentObserver(animationScaleObserver)
|
||||
contentResolver.unregisterContentObserver(highTextContrastObserver)
|
||||
}
|
||||
|
||||
override fun initialize() {
|
||||
@@ -168,6 +216,7 @@ public class AccessibilityInfoModule(context: ReactApplicationContext) :
|
||||
accessibilityManager?.isTouchExplorationEnabled == true)
|
||||
updateAndSendAccessibilityServiceChangeEvent(accessibilityManager?.isEnabled == true)
|
||||
updateAndSendReduceMotionChangeEvent()
|
||||
updateAndSendHighTextContrastChangeEvent()
|
||||
}
|
||||
|
||||
override fun invalidate() {
|
||||
@@ -200,13 +249,18 @@ public class AccessibilityInfoModule(context: ReactApplicationContext) :
|
||||
}
|
||||
recommendedTimeout =
|
||||
accessibilityManager?.getRecommendedTimeoutMillis(
|
||||
originalTimeout.toInt(), AccessibilityManager.FLAG_CONTENT_CONTROLS) ?: 0
|
||||
originalTimeout.toInt(),
|
||||
AccessibilityManager.FLAG_CONTENT_CONTROLS,
|
||||
) ?: 0
|
||||
successCallback.invoke(recommendedTimeout)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val REDUCE_MOTION_EVENT_NAME = "reduceMotionDidChange"
|
||||
private const val HIGH_TEXT_CONTRAST_EVENT_NAME = "highTextContrastDidChange"
|
||||
private const val TOUCH_EXPLORATION_EVENT_NAME = "touchExplorationDidChange"
|
||||
private const val ACCESSIBILITY_SERVICE_EVENT_NAME = "accessibilityServiceDidChange"
|
||||
private const val ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED_CONSTANT =
|
||||
"high_text_contrast_enabled" // constant is marked with @hide
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -151,6 +151,7 @@ jest
|
||||
isGrayscaleEnabled: jest.fn(() => Promise.resolve(false)),
|
||||
isInvertColorsEnabled: jest.fn(() => Promise.resolve(false)),
|
||||
isReduceMotionEnabled: jest.fn(() => Promise.resolve(false)),
|
||||
isHighTextContrastEnabled: jest.fn(() => Promise.resolve(false)),
|
||||
prefersCrossFadeTransitions: jest.fn(() => Promise.resolve(false)),
|
||||
isReduceTransparencyEnabled: jest.fn(() => Promise.resolve(false)),
|
||||
isScreenReaderEnabled: jest.fn(() => Promise.resolve(false)),
|
||||
|
||||
@@ -16,6 +16,9 @@ export interface Spec extends TurboModule {
|
||||
+isReduceMotionEnabled: (
|
||||
onSuccess: (isReduceMotionEnabled: boolean) => void,
|
||||
) => void;
|
||||
+isHighTextContrastEnabled?: (
|
||||
onSuccess: (isHighTextContrastEnabled: boolean) => void,
|
||||
) => void;
|
||||
+isTouchExplorationEnabled: (
|
||||
onSuccess: (isScreenReaderEnabled: boolean) => void,
|
||||
) => void;
|
||||
|
||||
Reference in New Issue
Block a user