Merge "Add a11y support for system-wide bold text setting" into androidx-main
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
index 1ba93dd..6b9c9b4 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
@@ -557,7 +557,8 @@
         current.overflow != overflow ||
         current.maxLines != maxLines ||
         current.density != density ||
-        current.placeholders != placeholders
+        current.placeholders != placeholders ||
+        current.fontFamilyResolver !== fontFamilyResolver
     ) {
         TextDelegate(
             text = text,
@@ -592,7 +593,8 @@
         current.softWrap != softWrap ||
         current.overflow != overflow ||
         current.maxLines != maxLines ||
-        current.density != density
+        current.density != density ||
+        current.fontFamilyResolver !== fontFamilyResolver
     ) {
         TextDelegate(
             text = AnnotatedString(text),
diff --git a/compose/ui/ui-text/api/current.txt b/compose/ui/ui-text/api/current.txt
index c8aa120..71901ff 100644
--- a/compose/ui/ui-text/api/current.txt
+++ b/compose/ui/ui-text/api/current.txt
@@ -550,6 +550,9 @@
   public final class AndroidFontLoader_androidKt {
   }
 
+  public final class AndroidFontResolveInterceptor_androidKt {
+  }
+
   public final class AndroidFontUtils_androidKt {
   }
 
diff --git a/compose/ui/ui-text/api/public_plus_experimental_current.txt b/compose/ui/ui-text/api/public_plus_experimental_current.txt
index 604b381..32259b5 100644
--- a/compose/ui/ui-text/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-text/api/public_plus_experimental_current.txt
@@ -565,6 +565,9 @@
   public final class AndroidFontLoader_androidKt {
   }
 
+  public final class AndroidFontResolveInterceptor_androidKt {
+  }
+
   public final class AndroidFontUtils_androidKt {
   }
 
diff --git a/compose/ui/ui-text/api/restricted_current.txt b/compose/ui/ui-text/api/restricted_current.txt
index c8aa120..71901ff 100644
--- a/compose/ui/ui-text/api/restricted_current.txt
+++ b/compose/ui/ui-text/api/restricted_current.txt
@@ -550,6 +550,9 @@
   public final class AndroidFontLoader_androidKt {
   }
 
+  public final class AndroidFontResolveInterceptor_androidKt {
+  }
+
   public final class AndroidFontUtils_androidKt {
   }
 
diff --git a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/TextTestExtensions.kt b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/TextTestExtensions.kt
index 3458f16..de9bfc3 100644
--- a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/TextTestExtensions.kt
+++ b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/TextTestExtensions.kt
@@ -27,6 +27,8 @@
 import androidx.compose.ui.text.font.PlatformFontFamilyTypefaceAdapter
 import androidx.compose.ui.text.font.TypefaceRequestCache
 import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.text.font.AndroidFontResolveInterceptor
+import androidx.compose.ui.text.font.PlatformResolveInterceptor
 import kotlin.math.ceil
 import kotlin.math.roundToInt
 
@@ -43,13 +45,18 @@
 @OptIn(ExperimentalTextApi::class)
 internal fun UncachedFontFamilyResolver(
     context: Context
-): FontFamily.Resolver = UncachedFontFamilyResolver(AndroidFontLoader(context))
+): FontFamily.Resolver = UncachedFontFamilyResolver(
+    AndroidFontLoader(context),
+    AndroidFontResolveInterceptor(context)
+)
 
 @OptIn(ExperimentalTextApi::class)
 internal fun UncachedFontFamilyResolver(
-    platformFontLoader: PlatformFontLoader
+    platformFontLoader: PlatformFontLoader,
+    platformResolveInterceptor: PlatformResolveInterceptor
 ): FontFamily.Resolver = FontFamilyResolverImpl(
     platformFontLoader,
+    platformResolveInterceptor,
     TypefaceRequestCache(),
     FontListFontFamilyTypefaceAdapter(AsyncTypefaceCache()),
     PlatformFontFamilyTypefaceAdapter()
diff --git a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontFamilyResolverImplCancellationTest.kt b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontFamilyResolverImplCancellationTest.kt
index 1d1e891..ef79faf8 100644
--- a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontFamilyResolverImplCancellationTest.kt
+++ b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontFamilyResolverImplCancellationTest.kt
@@ -50,6 +50,7 @@
 
     private val context = InstrumentationRegistry.getInstrumentation().context
     private val fontLoader = AndroidFontLoader(context)
+    private val fontResolveInterceptor = AndroidFontResolveInterceptor(context)
 
     @Before
     fun setup() {
@@ -62,6 +63,7 @@
         val injectedContext = scope.coroutineContext.minusKey(CoroutineExceptionHandler)
         subject = FontFamilyResolverImpl(
             fontLoader,
+            fontResolveInterceptor,
             typefaceRequestCache,
             FontListFontFamilyTypefaceAdapter(asyncTypefaceCache, injectedContext))
         typefaceLoader = AsyncTestTypefaceLoader()
diff --git a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontFamilyResolverImplTest.kt b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontFamilyResolverImplTest.kt
index 68ea243..4d0fad8 100644
--- a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontFamilyResolverImplTest.kt
+++ b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontFamilyResolverImplTest.kt
@@ -22,8 +22,10 @@
 import androidx.compose.ui.text.ExperimentalTextApi
 import androidx.compose.ui.text.FontTestData
 import androidx.compose.ui.text.UncachedFontFamilyResolver
+import androidx.compose.ui.text.font.testutils.AsyncFauxFont
 import androidx.compose.ui.text.font.testutils.AsyncTestTypefaceLoader
 import androidx.compose.ui.text.font.testutils.BlockingFauxFont
+import androidx.compose.ui.text.font.testutils.OptionalFauxFont
 import androidx.compose.ui.text.matchers.assertThat
 import androidx.compose.ui.text.platform.bitmap
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -54,6 +56,8 @@
     private val context = InstrumentationRegistry.getInstrumentation().context
 
     private val fontLoader = AndroidFontLoader(context)
+    // This is the default value that Android uses
+    private val accessibilityFontWeightAdjustment = 300
     private lateinit var subject: FontFamilyResolverImpl
 
     @Before
@@ -62,16 +66,25 @@
         typefaceCache = TypefaceRequestCache()
         dispatcher = UnconfinedTestDispatcher()
         scope = TestScope(dispatcher)
+        initializeSubject()
+        typefaceLoader = AsyncTestTypefaceLoader()
+    }
+
+    private fun initializeSubject(
+        platformResolveInterceptor: PlatformResolveInterceptor =
+            AndroidFontResolveInterceptor(context)
+    ) {
         val injectedContext = scope.coroutineContext.minusKey(CoroutineExceptionHandler)
+
         subject = FontFamilyResolverImpl(
             fontLoader,
+            platformResolveInterceptor = platformResolveInterceptor,
             typefaceRequestCache = typefaceCache,
             fontListFontFamilyTypefaceAdapter = FontListFontFamilyTypefaceAdapter(
                 asyncTypefaceCache,
                 injectedContext
             )
         )
-        typefaceLoader = AsyncTestTypefaceLoader()
     }
 
     private fun resolveAsTypeface(
@@ -428,7 +441,10 @@
             override suspend fun awaitLoad(font: Font): Any = Typeface.DEFAULT
             override val cacheKey: String = "Not the default resource loader"
         }
-        val otherTypeface = UncachedFontFamilyResolver(newFontLoader)
+        val otherTypeface = UncachedFontFamilyResolver(
+            newFontLoader,
+            PlatformResolveInterceptor.Default
+        )
             .resolve(fontFamily).value as Typeface
 
         assertThat(typeface).isNotSameInstanceAs(otherTypeface)
@@ -459,14 +475,17 @@
             }
         )
         val firstAndroidResourceLoader = AndroidFontLoader(context)
+        val androidResolveInterceptor = AndroidFontResolveInterceptor(context)
         val typeface = FontFamilyResolverImpl(
             fontLoader,
+            androidResolveInterceptor,
             typefaceCache,
             FontListFontFamilyTypefaceAdapter(asyncTypefaceCache)
         ).resolve(fontFamily).value as Typeface
         val secondAndroidResourceLoader = AndroidFontLoader(context)
         val otherTypeface = FontFamilyResolverImpl(
             fontLoader,
+            androidResolveInterceptor,
             typefaceCache,
             FontListFontFamilyTypefaceAdapter(asyncTypefaceCache)
         ).resolve(fontFamily).value as Typeface
@@ -599,4 +618,159 @@
         assertThat(typeface500).hasWeightAndStyle(FontWeight.W100, FontStyle.Normal)
         assertThat(typeface600).hasWeightAndStyle(FontWeight.W600, FontStyle.Normal)
     }
+
+    @Test
+    fun androidFontResolveInterceptor_affectsTheFontWeight() {
+        initializeSubject(AndroidFontResolveInterceptor(accessibilityFontWeightAdjustment))
+        val fontFamily = FontFamily(
+            FontTestData.FONT_400_REGULAR,
+            FontTestData.FONT_500_REGULAR,
+            FontTestData.FONT_600_REGULAR,
+            FontTestData.FONT_700_REGULAR,
+            FontTestData.FONT_800_REGULAR
+        )
+        val typeface = resolveAsTypeface(
+            fontFamily = fontFamily,
+            fontWeight = FontWeight.W400
+        )
+
+        assertThat(typeface).hasWeightAndStyle(FontWeight.W700, FontStyle.Normal)
+    }
+
+    @Test
+    fun androidFontResolveInterceptor_doesNotAffectTheFontStyle() {
+        initializeSubject(AndroidFontResolveInterceptor(accessibilityFontWeightAdjustment))
+
+        val typeface = resolveAsTypeface(
+            fontWeight = FontWeight.W400,
+            fontStyle = FontStyle.Italic
+        )
+
+        assertThat(typeface).hasWeightAndStyle(FontWeight.W700, FontStyle.Italic)
+    }
+
+    @Test
+    fun platformResolveInterceptor_affectsTheResolvedFontStyle() {
+        initializeSubject(
+            platformResolveInterceptor = object : PlatformResolveInterceptor {
+                override fun interceptFontStyle(fontStyle: FontStyle) = FontStyle.Italic
+            }
+        )
+
+        val typeface = resolveAsTypeface(
+            fontWeight = FontWeight.Normal,
+            fontStyle = FontStyle.Normal
+        )
+
+        assertThat(typeface).hasWeightAndStyle(FontWeight.Normal, FontStyle.Italic)
+    }
+
+    @Test
+    fun platformResolveInterceptor_affectsTheResolvedFontSynthesis() {
+        initializeSubject(
+            platformResolveInterceptor = object : PlatformResolveInterceptor {
+                override fun interceptFontSynthesis(fontSynthesis: FontSynthesis) =
+                    FontSynthesis.All
+            }
+        )
+
+        val fontFamily = FontTestData.FONT_100_REGULAR.toFontFamily()
+
+        val typeface = resolveAsTypeface(
+            fontFamily = fontFamily,
+            fontWeight = FontWeight.Bold,
+            fontStyle = FontStyle.Italic,
+            fontSynthesis = FontSynthesis.None
+        )
+
+        assertThat(typeface).hasWeightAndStyle(FontWeight.Bold, FontStyle.Italic)
+    }
+
+    @Test
+    fun platformResolveInterceptor_affectsTheResolvedFontFamily() {
+        initializeSubject(
+            platformResolveInterceptor = object : PlatformResolveInterceptor {
+                override fun interceptFontFamily(fontFamily: FontFamily?) =
+                    FontTestData.FONT_100_REGULAR.toFontFamily()
+            }
+        )
+
+        val typeface = resolveAsTypeface(fontFamily = FontFamily.Cursive)
+
+        assertThat(typeface).hasWeightAndStyle(FontWeight.W100, FontStyle.Normal)
+    }
+
+    @Test
+    fun androidResolveInterceptor_affectsAsyncFontResolution_withFallback() {
+        initializeSubject(AndroidFontResolveInterceptor(accessibilityFontWeightAdjustment))
+
+        val loader = AsyncTestTypefaceLoader()
+        val asyncFauxFontW400 = AsyncFauxFont(loader, FontWeight.W400)
+        val asyncFauxFontW700 = AsyncFauxFont(loader, FontWeight.W700)
+        val blockingFauxFontW400 = BlockingFauxFont(loader, Typeface.DEFAULT, FontWeight.W400)
+
+        val fontFamily = FontFamily(
+            asyncFauxFontW400,
+            blockingFauxFontW400,
+            asyncFauxFontW700
+        )
+
+        val fallbackTypeface = resolveAsTypeface(fontFamily, FontWeight.W400)
+        assertThat(fallbackTypeface).hasWeightAndStyle(FontWeight.W700, FontStyle.Normal)
+
+        // loads the W700 async font which should be the matched font
+        loader.completeOne(asyncFauxFontW700, Typeface.MONOSPACE)
+
+        val typeface = resolveAsTypeface(fontFamily, FontWeight.W400)
+        assertThat(typeface).isSameInstanceAs(Typeface.MONOSPACE)
+    }
+
+    @Test
+    fun androidResolveInterceptor_affectsAsyncFontResolution_withBlockingFont() {
+        initializeSubject(AndroidFontResolveInterceptor(accessibilityFontWeightAdjustment))
+
+        val loader = AsyncTestTypefaceLoader()
+        val asyncFauxFontW400 = AsyncFauxFont(loader, FontWeight.W400)
+        val asyncFauxFontW700 = AsyncFauxFont(loader, FontWeight.W700)
+        val blockingFauxFontW700 = BlockingFauxFont(loader, Typeface.SANS_SERIF, FontWeight.W700)
+
+        val fontFamily = FontFamily(
+            asyncFauxFontW400,
+            asyncFauxFontW700,
+            blockingFauxFontW700
+        )
+
+        val blockingTypeface = resolveAsTypeface(fontFamily, FontWeight.W400)
+        assertThat(blockingTypeface).isSameInstanceAs(Typeface.SANS_SERIF)
+
+        // loads the W700 async font which should be the matched font
+        loader.completeOne(asyncFauxFontW700, Typeface.MONOSPACE)
+
+        val typeface = resolveAsTypeface(fontFamily, FontWeight.W400)
+        assertThat(typeface).isSameInstanceAs(Typeface.MONOSPACE)
+    }
+
+    @Test
+    fun androidResolveInterceptor_choosesOptionalFont_whenWeightMatches() {
+        val loader = AsyncTestTypefaceLoader()
+        val optionalFauxFontW400 = OptionalFauxFont(loader, Typeface.MONOSPACE, FontWeight.W400)
+        val optionalFauxFontW700 = OptionalFauxFont(loader, Typeface.SERIF, FontWeight.W700)
+        val blockingFauxFontW700 = BlockingFauxFont(loader, Typeface.SANS_SERIF, FontWeight.W700)
+
+        initializeSubject()
+
+        val fontFamily = FontFamily(
+            optionalFauxFontW400,
+            optionalFauxFontW700,
+            blockingFauxFontW700
+        )
+
+        val typefaceNoAdjustment = resolveAsTypeface(fontFamily, FontWeight.W400)
+        assertThat(typefaceNoAdjustment).isSameInstanceAs(Typeface.MONOSPACE)
+
+        initializeSubject(AndroidFontResolveInterceptor(accessibilityFontWeightAdjustment))
+
+        val typeface = resolveAsTypeface(fontFamily, FontWeight.W400)
+        assertThat(typeface).isSameInstanceAs(Typeface.SERIF)
+    }
 }
\ No newline at end of file
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/font/AndroidFontResolveInterceptor.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/font/AndroidFontResolveInterceptor.android.kt
new file mode 100644
index 0000000..0c91c61
--- /dev/null
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/font/AndroidFontResolveInterceptor.android.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.text.font
+
+import android.content.Context
+import android.content.res.Configuration
+import android.graphics.fonts.FontStyle
+import android.os.Build
+import androidx.compose.ui.unit.Density
+
+/**
+ * Android implementation for [PlatformResolveInterceptor].
+ *
+ * Android system accessibility should be able to override any font metric that
+ * affects how FontFamily is resolved. Font Size preference is handled by [Density.fontScale].
+ * This interceptor currently adjusts the Font Weight for Bold Text feature.
+ */
+internal data class AndroidFontResolveInterceptor(
+    private val fontWeightAdjustment: Int
+) : PlatformResolveInterceptor {
+
+    override fun interceptFontWeight(fontWeight: FontWeight): FontWeight {
+        // do not intercept if fontWeightAdjustment is not set or undefined
+        if (fontWeightAdjustment == 0 ||
+            fontWeightAdjustment == Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) {
+            return fontWeight
+        }
+
+        val finalFontWeight = (fontWeight.weight + fontWeightAdjustment).coerceIn(
+            FontStyle.FONT_WEIGHT_MIN,
+            FontStyle.FONT_WEIGHT_MAX
+        )
+        return FontWeight(finalFontWeight)
+    }
+}
+
+/**
+ * A helper function to create an interceptor using a context.
+ */
+internal fun AndroidFontResolveInterceptor(context: Context): AndroidFontResolveInterceptor {
+    val fontWeightAdjustment = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+        context.resources.configuration.fontWeightAdjustment
+    } else {
+        0
+    }
+    return AndroidFontResolveInterceptor(fontWeightAdjustment)
+}
\ No newline at end of file
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/font/FontFamilyResolver.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/font/FontFamilyResolver.android.kt
index b565fd5..bb37ba3 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/font/FontFamilyResolver.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/font/FontFamilyResolver.android.kt
@@ -36,7 +36,10 @@
 fun createFontFamilyResolver(
     context: Context
 ): FontFamily.Resolver {
-    return FontFamilyResolverImpl(AndroidFontLoader(context))
+    return FontFamilyResolverImpl(
+        AndroidFontLoader(context),
+        AndroidFontResolveInterceptor(context)
+    )
 }
 
 /**
@@ -69,6 +72,7 @@
 ): FontFamily.Resolver {
     return FontFamilyResolverImpl(
         AndroidFontLoader(context),
+        AndroidFontResolveInterceptor(context),
         GlobalTypefaceRequestCache,
         FontListFontFamilyTypefaceAdapter(
             GlobalAsyncTypefaceCache,
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontFamilyResolver.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontFamilyResolver.kt
index fdc1d67..3fd77b3 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontFamilyResolver.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontFamilyResolver.kt
@@ -26,6 +26,8 @@
 @ExperimentalTextApi
 internal class FontFamilyResolverImpl(
     internal val platformFontLoader: PlatformFontLoader /* exposed for desktop ParagraphBuilder */,
+    private val platformResolveInterceptor: PlatformResolveInterceptor =
+        PlatformResolveInterceptor.Default,
     private val typefaceRequestCache: TypefaceRequestCache = GlobalTypefaceRequestCache,
     private val fontListFontFamilyTypefaceAdapter: FontListFontFamilyTypefaceAdapter =
         FontListFontFamilyTypefaceAdapter(GlobalAsyncTypefaceCache),
@@ -33,7 +35,7 @@
         PlatformFontFamilyTypefaceAdapter()
 ) : FontFamily.Resolver {
     private val createDefaultTypeface: (TypefaceRequest) -> Any = {
-        resolve(null, it.fontWeight, it.fontStyle, it.fontSynthesis).value
+        resolve(it.copy(fontFamily = null)).value
     }
 
     override suspend fun preload(
@@ -46,9 +48,9 @@
 
         val typeRequests = fontFamily.fonts.fastMap {
             TypefaceRequest(
-                fontFamily,
-                it.weight,
-                it.style,
+                platformResolveInterceptor.interceptFontFamily(fontFamily),
+                platformResolveInterceptor.interceptFontWeight(it.weight),
+                platformResolveInterceptor.interceptFontStyle(it.style),
                 FontSynthesis.All,
                 platformFontLoader.cacheKey
             )
@@ -76,21 +78,27 @@
         fontStyle: FontStyle,
         fontSynthesis: FontSynthesis,
     ): State<Any> {
-        val typeRequest = TypefaceRequest(
-            fontFamily,
-            fontWeight,
-            fontStyle,
-            fontSynthesis,
+        return resolve(TypefaceRequest(
+            platformResolveInterceptor.interceptFontFamily(fontFamily),
+            platformResolveInterceptor.interceptFontWeight(fontWeight),
+            platformResolveInterceptor.interceptFontStyle(fontStyle),
+            platformResolveInterceptor.interceptFontSynthesis(fontSynthesis),
             platformFontLoader.cacheKey
-        )
-        val result = typefaceRequestCache.runCached(typeRequest) { onAsyncCompletion ->
+        ))
+    }
+
+    /**
+     * Resolves the final [typefaceRequest] without interceptors.
+     */
+    private fun resolve(typefaceRequest: TypefaceRequest): State<Any> {
+        val result = typefaceRequestCache.runCached(typefaceRequest) { onAsyncCompletion ->
             fontListFontFamilyTypefaceAdapter.resolve(
-                typeRequest,
+                typefaceRequest,
                 platformFontLoader,
                 onAsyncCompletion,
                 createDefaultTypeface
             ) ?: platformFamilyTypefaceAdapter.resolve(
-                typeRequest,
+                typefaceRequest,
                 platformFontLoader,
                 onAsyncCompletion,
                 createDefaultTypeface
@@ -100,6 +108,27 @@
     }
 }
 
+/**
+ * Platform level [FontFamily.Resolver] argument interceptor. This interface is
+ * intended to bridge accessibility constraints on any platform with
+ * Compose through the use of [FontFamilyResolverImpl.resolve].
+ */
+internal interface PlatformResolveInterceptor {
+
+    fun interceptFontFamily(fontFamily: FontFamily?): FontFamily? = fontFamily
+
+    fun interceptFontWeight(fontWeight: FontWeight): FontWeight = fontWeight
+
+    fun interceptFontStyle(fontStyle: FontStyle): FontStyle = fontStyle
+
+    fun interceptFontSynthesis(fontSynthesis: FontSynthesis): FontSynthesis = fontSynthesis
+
+    companion object {
+        // NO-OP default interceptor
+        internal val Default: PlatformResolveInterceptor = object : PlatformResolveInterceptor {}
+    }
+}
+
 internal val GlobalTypefaceRequestCache = TypefaceRequestCache()
 @OptIn(ExperimentalTextApi::class)
 internal val GlobalAsyncTypefaceCache = AsyncTypefaceCache()
diff --git a/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/font/FontFamilyResolver.sikio.kt b/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/font/FontFamilyResolver.sikio.kt
index a0964d6..5be43e1f 100644
--- a/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/font/FontFamilyResolver.sikio.kt
+++ b/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/font/FontFamilyResolver.sikio.kt
@@ -59,6 +59,7 @@
 ): FontFamily.Resolver {
     return FontFamilyResolverImpl(
         SkiaFontLoader(),
+        PlatformResolveInterceptor.Default,
         GlobalTypefaceRequestCache,
         FontListFontFamilyTypefaceAdapter(
             GlobalAsyncTypefaceCache,
diff --git a/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/font/AndroidFontResolverInterceptorTest.kt b/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/font/AndroidFontResolverInterceptorTest.kt
new file mode 100644
index 0000000..3de00a3a
--- /dev/null
+++ b/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/font/AndroidFontResolverInterceptorTest.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.text.font
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class AndroidFontResolverInterceptorTest {
+    private lateinit var subject: AndroidFontResolveInterceptor
+
+    private fun createSubject(adjustment: Int) {
+        subject = AndroidFontResolveInterceptor(adjustment)
+    }
+
+    @Test
+    fun fontWeightDoesNotChange_whenBoldTextAccessibilityIsNotEnabled() {
+        val testedWeight = 400
+        val adjustment = 0
+        val expected = 400
+        createSubject(adjustment)
+        assertThat(subject.interceptFontWeight(FontWeight(testedWeight)).weight)
+            .isEqualTo(expected)
+    }
+
+    @Test
+    fun fontWeightIncreases_whenBoldTextAccessibilityIsEnabled() {
+        val testedWeight = 400
+        val adjustment = 300
+        val expected = 700
+        createSubject(adjustment)
+        assertThat(subject.interceptFontWeight(FontWeight(testedWeight)).weight)
+            .isEqualTo(expected)
+    }
+
+    @Test
+    fun fontWeightNeverExceeds1000_whenBoldTextAccessibilityIsEnabled() {
+        val testedWeight = 500
+        val adjustment = 600
+        createSubject(adjustment)
+        assertThat(subject.interceptFontWeight(FontWeight(testedWeight)).weight)
+            .isEqualTo(android.graphics.fonts.FontStyle.FONT_WEIGHT_MAX)
+    }
+
+    @Test
+    fun fontWeightWontBeZero_whenBoldTextAccessibilityIsEnabled() {
+        val testedWeight = 500
+        val adjustment = -600
+        createSubject(adjustment)
+        assertThat(subject.interceptFontWeight(FontWeight(testedWeight)).weight)
+            .isEqualTo(android.graphics.fonts.FontStyle.FONT_WEIGHT_MIN)
+    }
+
+    @Test
+    fun fontWeightWontBeNegative_whenBoldTextAccessibilityIsEnabled() {
+        val testedWeight = 500
+        val adjustment = -500
+        createSubject(adjustment)
+        assertThat(subject.interceptFontWeight(FontWeight(testedWeight)).weight)
+            .isEqualTo(android.graphics.fonts.FontStyle.FONT_WEIGHT_MIN)
+    }
+
+    @Test
+    fun fontWeightWontOverflow_whenBoldTextAccessibilityIsEnabled() {
+        val testedWeight = 500
+        val adjustment = Int.MAX_VALUE
+        createSubject(adjustment)
+        assertThat(subject.interceptFontWeight(FontWeight(testedWeight)).weight)
+            .isEqualTo(testedWeight)
+    }
+
+    @Test
+    fun otherFontArgumentsWontChange_whenBoldTextAccessibilityIsEnabled() {
+        val adjustment = 300
+        createSubject(adjustment)
+        assertThat(subject.interceptFontFamily(FontFamily.SansSerif))
+            .isEqualTo(FontFamily.SansSerif)
+
+        assertThat(subject.interceptFontStyle(FontStyle.Normal))
+            .isEqualTo(FontStyle.Normal)
+
+        assertThat(subject.interceptFontSynthesis(FontSynthesis.Weight))
+            .isEqualTo(FontSynthesis.Weight)
+    }
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index 3efe4c2..1b9ac43 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -54,6 +54,7 @@
 import androidx.compose.runtime.collection.mutableVectorOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.referentialEqualityPolicy
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.InternalComposeUiApi
@@ -378,7 +379,20 @@
     @Suppress("DEPRECATION", "OverridingDeprecatedMember")
     override val fontLoader: Font.ResourceLoader = AndroidFontResourceLoader(context)
 
-    override val fontFamilyResolver: FontFamily.Resolver = createFontFamilyResolver(context)
+    // Backed by mutableStateOf so that the local provider recomposes when it changes
+    // FontFamily.Resolver is not guaranteed to be stable or immutable, hence referential check
+    override var fontFamilyResolver: FontFamily.Resolver by mutableStateOf(
+        createFontFamilyResolver(context),
+        referentialEqualityPolicy()
+    )
+        private set
+
+    // keeps track of changes in font weight adjustment to update fontFamilyResolver
+    private var currentFontWeightAdjustment: Int =
+        context.resources.configuration.fontWeightAdjustmentCompat
+
+    private val Configuration.fontWeightAdjustmentCompat: Int
+        get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) fontWeightAdjustment else 0
 
     // Backed by mutableStateOf so that the ambient provider recomposes when it changes
     override var layoutDirection by mutableStateOf(
@@ -1409,6 +1423,10 @@
     override fun onConfigurationChanged(newConfig: Configuration) {
         super.onConfigurationChanged(newConfig)
         density = Density(context)
+        if (newConfig.fontWeightAdjustmentCompat != currentFontWeightAdjustment) {
+            currentFontWeightAdjustment = newConfig.fontWeightAdjustmentCompat
+            fontFamilyResolver = createFontFamilyResolver(context)
+        }
         configurationChangeObserver(newConfig)
     }