77
88package com.facebook.react.modules.accessibilityinfo
99
10+ import android.app.UiModeManager
1011import android.content.ContentResolver
1112import android.content.Context
1213import android.database.ContentObserver
@@ -72,6 +73,11 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) :
7273 }
7374 }
7475 }
76+ private val uiModeManager: UiModeManager ? =
77+ context.applicationContext.getSystemService(Context .UI_MODE_SERVICE ) as UiModeManager ?
78+ // Listener that is notified when the contrast level changes (API 34+). Created lazily so that
79+ // older versions never load the ContrastChangeListener interface.
80+ private var contrastChangeListener: Any? = null
7581 private val accessibilityManager: AccessibilityManager ?
7682 private val touchExplorationStateChangeListener: ReactTouchExplorationStateChangeListener =
7783 ReactTouchExplorationStateChangeListener ()
@@ -80,6 +86,7 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) :
8086 private val contentResolver: ContentResolver
8187 private var reduceMotionEnabled = false
8288 private var highTextContrastEnabled = false
89+ private var contrastLevel = 0f
8390 private var touchExplorationEnabled = false
8491 private var accessibilityServiceEnabled = false
8592 private var recommendedTimeout = 0
@@ -95,6 +102,7 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) :
95102 accessibilityServiceEnabled = accessibilityManager?.isEnabled ? : false
96103 reduceMotionEnabled = isReduceMotionEnabledValue
97104 highTextContrastEnabled = isHighTextContrastEnabledValue
105+ contrastLevel = contrastLevelValue
98106 grayscaleModeEnabled = isGrayscaleEnabledValue
99107 }
100108
@@ -154,6 +162,14 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) :
154162 ) != 0
155163 }
156164
165+ private val contrastLevelValue: Float
166+ get() =
167+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .UPSIDE_DOWN_CAKE ) {
168+ uiModeManager?.contrast ? : 0f
169+ } else {
170+ 0f
171+ }
172+
157173 override fun isReduceMotionEnabled (successCallback : Callback ) {
158174 successCallback.invoke(reduceMotionEnabled)
159175 }
@@ -170,6 +186,10 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) :
170186 successCallback.invoke(highTextContrastEnabled)
171187 }
172188
189+ override fun getContrastLevel (successCallback : Callback ) {
190+ successCallback.invoke(contrastLevel.toDouble())
191+ }
192+
173193 override fun isTouchExplorationEnabled (successCallback : Callback ) {
174194 successCallback.invoke(touchExplorationEnabled)
175195 }
@@ -208,6 +228,42 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) :
208228 }
209229 }
210230
231+ private fun updateAndSendContrastLevelChangeEvent () {
232+ val newContrastLevel = contrastLevelValue
233+ if (contrastLevel != newContrastLevel) {
234+ contrastLevel = newContrastLevel
235+ getReactApplicationContextIfActiveOrWarn()
236+ ?.emitDeviceEvent(CONTRAST_LEVEL_EVENT_NAME , contrastLevel.toDouble())
237+ }
238+ }
239+
240+ private fun registerContrastChangeListener () {
241+ if (
242+ Build .VERSION .SDK_INT < Build .VERSION_CODES .UPSIDE_DOWN_CAKE ||
243+ contrastChangeListener != null
244+ ) {
245+ return
246+ }
247+ val manager = uiModeManager ? : return
248+ val listener =
249+ UiModeManager .ContrastChangeListener {
250+ if (reactApplicationContext.hasActiveReactInstance()) {
251+ updateAndSendContrastLevelChangeEvent()
252+ }
253+ }
254+ manager.addContrastChangeListener(reactApplicationContext.mainExecutor, listener)
255+ contrastChangeListener = listener
256+ }
257+
258+ private fun unregisterContrastChangeListener () {
259+ if (Build .VERSION .SDK_INT < Build .VERSION_CODES .UPSIDE_DOWN_CAKE ) {
260+ return
261+ }
262+ val listener = contrastChangeListener as ? UiModeManager .ContrastChangeListener ? : return
263+ uiModeManager?.removeContrastChangeListener(listener)
264+ contrastChangeListener = null
265+ }
266+
211267 private fun updateAndSendTouchExplorationChangeEvent (enabled : Boolean ) {
212268 if (touchExplorationEnabled != enabled) {
213269 touchExplorationEnabled = enabled
@@ -243,12 +299,15 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) :
243299 val highTextContrastUri =
244300 Settings .Secure .getUriFor(ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED_CONSTANT )
245301 contentResolver.registerContentObserver(highTextContrastUri, false , highTextContrastObserver)
302+ // Registering does not report the current value, so read it again below.
303+ registerContrastChangeListener()
246304 updateAndSendTouchExplorationChangeEvent(
247305 accessibilityManager?.isTouchExplorationEnabled == true ,
248306 )
249307 updateAndSendAccessibilityServiceChangeEvent(accessibilityManager?.isEnabled == true )
250308 updateAndSendReduceMotionChangeEvent()
251309 updateAndSendHighTextContrastChangeEvent()
310+ updateAndSendContrastLevelChangeEvent()
252311 updateAndSendInvertColorsChangeEvent()
253312 updateAndSendGrayscaleModeChangeEvent()
254313 }
@@ -260,6 +319,7 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) :
260319 accessibilityManager?.removeAccessibilityStateChangeListener(accessibilityServiceChangeListener)
261320 contentResolver.unregisterContentObserver(animationScaleObserver)
262321 contentResolver.unregisterContentObserver(highTextContrastObserver)
322+ unregisterContrastChangeListener()
263323 }
264324
265325 override fun initialize () {
@@ -274,6 +334,7 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) :
274334
275335 override fun invalidate () {
276336 reactApplicationContext.removeLifecycleEventListener(this )
337+ unregisterContrastChangeListener()
277338 super .invalidate()
278339 }
279340
@@ -318,5 +379,6 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) :
318379 " high_text_contrast_enabled" // constant is marked with @hide
319380 private const val INVERT_COLOR_EVENT_NAME = " invertColorDidChange"
320381 private const val GRAYSCALE_MODE_EVENT_NAME = " grayscaleModeDidChange"
382+ private const val CONTRAST_LEVEL_EVENT_NAME = " contrastLevelDidChange"
321383 }
322384}
0 commit comments