feat(Android): add support for detecting grayscale mode enabled on android (#47497)

Summary:
On android the isGrayScaleEnabled method of AccessibilityInfo always returns false due to missing implementation. This PR fills the gap by providing the native module logic for checking grayscale mode.

## Changelog:
- Added native module code to check for grayscale mode on android
- Updated js accessibility info module  to return the correct promise instead of default false for isGrayScaleEnabled()
- Moved the test for isGrayScaleEnabled() out of ios scope to common Android and iOS scope

[ANDROID] [ADDED] - logic to check for grayscale mode on android

Pull Request resolved: https://github.com/facebook/react-native/pull/47497

Test Plan:
Tested on :
- Google Pixel 7 Pro (Android 14)
- OnePlus 12 (Android 14)
- Pixel 6 (Android 15)

Reviewed By: cortinico

Differential Revision: D65662583

Pulled By: javache

fbshipit-source-id: 39f9ce37c9375b5380257847395393045eedbadc
This commit is contained in:
oddlyspaced 2024-11-11 07:38:21 -08:00 committed by Facebook GitHub Bot
parent 2c31fe99e1
commit e70202e606
4 changed files with 48 additions and 7 deletions

View File

@ -56,6 +56,7 @@ const EventNames: Map<
['screenReaderChanged', 'touchExplorationDidChange'],
['accessibilityServiceChanged', 'accessibilityServiceDidChange'],
['invertColorsChanged', 'invertColorDidChange'],
['grayscaleChanged', 'grayscaleModeDidChange'],
])
: new Map([
['announcementFinished', 'announcementFinished'],
@ -114,7 +115,13 @@ const AccessibilityInfo = {
*/
isGrayscaleEnabled(): Promise<boolean> {
if (Platform.OS === 'android') {
return Promise.resolve(false);
return new Promise((resolve, reject) => {
if (NativeAccessibilityInfoAndroid?.isGrayscaleEnabled != null) {
NativeAccessibilityInfoAndroid.isGrayscaleEnabled(resolve);
} else {
reject(null);
}
});
} else {
return new Promise((resolve, reject) => {
if (NativeAccessibilityManagerIOS != null) {

View File

@ -87,6 +87,7 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) :
private var accessibilityServiceEnabled = false
private var recommendedTimeout = 0
private var invertColorsEnabled = false
private var grayscaleModeEnabled = false
init {
val appContext = context.applicationContext
@ -97,6 +98,7 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) :
accessibilityServiceEnabled = accessibilityManager.isEnabled
reduceMotionEnabled = isReduceMotionEnabledValue
highTextContrastEnabled = isHighTextContrastEnabledValue
grayscaleModeEnabled = isGrayscaleEnabledValue
}
@get:TargetApi(Build.VERSION_CODES.LOLLIPOP)
@ -123,6 +125,21 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) :
}
}
@get:TargetApi(Build.VERSION_CODES.LOLLIPOP)
private val isGrayscaleEnabledValue: Boolean
get() {
try {
val colorCorrectionSettingKey = "accessibility_display_daltonizer_enabled"
val colorModeSettingKey = "accessibility_display_daltonizer"
// for grayscale mode to be detected, the color correction accessibility setting should be
// on and the color correction mode should be set to grayscale (0)
return Settings.Secure.getInt(contentResolver, colorCorrectionSettingKey) == 1 &&
Settings.Secure.getInt(contentResolver, colorModeSettingKey) == 0
} catch (e: Settings.SettingNotFoundException) {
return false
}
}
@get:TargetApi(Build.VERSION_CODES.LOLLIPOP)
private val isHighTextContrastEnabledValue: Boolean
get() {
@ -141,6 +158,10 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) :
successCallback.invoke(invertColorsEnabled)
}
override fun isGrayscaleEnabled(successCallback: Callback) {
successCallback.invoke(grayscaleModeEnabled)
}
override fun isHighTextContrastEnabled(successCallback: Callback) {
successCallback.invoke(highTextContrastEnabled)
}
@ -211,6 +232,17 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) :
}
}
private fun updateAndSendGrayscaleModeChangeEvent() {
val isGrayscaleModeEnabled = isGrayscaleEnabledValue
if (grayscaleModeEnabled != isGrayscaleModeEnabled) {
grayscaleModeEnabled = isGrayscaleModeEnabled
val reactApplicationContext = getReactApplicationContextIfActiveOrWarn()
if (reactApplicationContext != null) {
reactApplicationContext.emitDeviceEvent(GRAYSCALE_MODE_EVENT_NAME, grayscaleModeEnabled)
}
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
override fun onHostResume() {
accessibilityManager?.addTouchExplorationStateChangeListener(
@ -227,6 +259,7 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) :
updateAndSendReduceMotionChangeEvent()
updateAndSendHighTextContrastChangeEvent()
updateAndSendInvertColorsChangeEvent()
updateAndSendGrayscaleModeChangeEvent()
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@ -292,5 +325,6 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) :
private const val ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED_CONSTANT =
"high_text_contrast_enabled" // constant is marked with @hide
private const val INVERT_COLOR_EVENT_NAME = "invertColorDidChange"
private const val GRAYSCALE_MODE_EVENT_NAME = "grayscaleModeDidChange"
}
}

View File

@ -34,6 +34,9 @@ export interface Spec extends TurboModule {
mSec: number,
onSuccess: (recommendedTimeoutMillis: number) => void,
) => void;
+isGrayscaleEnabled?: (
onSuccess: (isGrayscaleEnabled: boolean) => void,
) => void;
}
export default (TurboModuleRegistry.get<Spec>('AccessibilityInfo'): ?Spec);

View File

@ -1336,12 +1336,6 @@ class EnabledExamples extends React.Component<{}> {
eventListener="boldTextChanged"
/>
</RNTesterBlock>
<RNTesterBlock title="isGrayScaleEnabled()">
<EnabledExample
test="gray scale"
eventListener="grayscaleChanged"
/>
</RNTesterBlock>
<RNTesterBlock title="isReduceTransparencyEnabled()">
<EnabledExample
test="reduce transparency"
@ -1384,6 +1378,9 @@ class EnabledExamples extends React.Component<{}> {
eventListener="screenReaderChanged"
/>
</RNTesterBlock>
<RNTesterBlock title="isGrayScaleEnabled()">
<EnabledExample test="gray scale" eventListener="grayscaleChanged" />
</RNTesterBlock>
</View>
);
}