Migrate FontScalingTile

Fixes: 301056434
Flag: aconfig com.android.systemui.qs_new_tiles DEVELOPMENT
Test: atest SystemUiRoboTests
Test: atest FontScalingTileDataInteractorTest
FontScalingTileUserActionInteractorTest FontScalingTileMapperTest

Change-Id: I81777fe908e6c17a0cb6bbd3565f66ef59c9bade
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
new file mode 100644
index 0000000..b7b3fdb
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 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 com.android.systemui.qs.tiles.impl.fontscaling.domain
+
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
+import com.android.systemui.qs.tiles.impl.fontscaling.qsFontScalingTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FontScalingTileMapperTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val fontScalingTileConfig = kosmos.qsFontScalingTileConfig
+
+    private val mapper by lazy {
+        FontScalingTileMapper(
+            context.orCreateTestableResources
+                .apply { addOverride(R.drawable.ic_qs_font_scaling, TestStubDrawable()) }
+                .resources,
+            context.theme
+        )
+    }
+
+    @Test
+    fun activeStateMatchesEnabledModel() {
+        val inputModel = FontScalingTileModel
+
+        val outputState = mapper.map(fontScalingTileConfig, inputModel)
+
+        val expectedState = createFontScalingTileState()
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    private fun createFontScalingTileState(): QSTileState =
+        QSTileState(
+            {
+                Icon.Loaded(
+                    context.getDrawable(
+                        R.drawable.ic_qs_font_scaling,
+                    )!!,
+                    null
+                )
+            },
+            context.getString(R.string.quick_settings_font_scaling_label),
+            QSTileState.ActivationState.ACTIVE,
+            null,
+            setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+            context.getString(R.string.quick_settings_font_scaling_label),
+            null,
+            QSTileState.SideViewIcon.Chevron,
+            QSTileState.EnabledState.ENABLED,
+            Switch::class.qualifiedName
+        )
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractorTest.kt
new file mode 100644
index 0000000..39bc8a6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractorTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 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 com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor
+
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FontScalingTileDataInteractorTest : SysuiTestCase() {
+    private val underTest: FontScalingTileDataInteractor = FontScalingTileDataInteractor()
+    private val testUser = UserHandle.of(1)
+
+    @Test
+    fun collectsExactlyOneValue() = runTest {
+        val flowValues by
+            collectValues(underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)))
+
+        runCurrent()
+
+        Truth.assertThat(flowValues.size).isEqualTo(1)
+    }
+
+    @Test
+    fun lastValueIsNotEmpty() = runTest {
+        val flowValue by
+            collectLastValue(underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)))
+
+        runCurrent()
+
+        Truth.assertThat(flowValue).isNotNull()
+    }
+
+    @Test
+    fun isAvailable() = runTest {
+        val availability by collectLastValue(underTest.availability(testUser))
+
+        Truth.assertThat(availability).isTrue()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt
new file mode 100644
index 0000000..2384915
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2024 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 com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor
+
+import android.provider.Settings
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.fontscaling.FontScalingDialogDelegate
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.intentInputs
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
+import com.android.systemui.statusbar.phone.FakeKeyguardStateController
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FontScalingUserActionInteractorTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val qsTileIntentUserActionHandler = FakeQSTileIntentUserInputHandler()
+    private val keyguardStateController = FakeKeyguardStateController()
+
+    private lateinit var underTest: FontScalingTileUserActionInteractor
+
+    @Mock private lateinit var fontScalingDialogDelegate: FontScalingDialogDelegate
+    @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+    @Mock private lateinit var dialog: SystemUIDialog
+    @Mock private lateinit var activityStarter: ActivityStarter
+
+    @Captor private lateinit var argumentCaptor: ArgumentCaptor<Runnable>
+
+    @Before
+    fun setup() {
+        activityStarter = mock<ActivityStarter>()
+        dialogLaunchAnimator = mock<DialogLaunchAnimator>()
+        dialog = mock<SystemUIDialog>()
+        fontScalingDialogDelegate =
+            mock<FontScalingDialogDelegate> { whenever(createDialog()).thenReturn(dialog) }
+        argumentCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+
+        underTest =
+            FontScalingTileUserActionInteractor(
+                kosmos.testScope.coroutineContext,
+                qsTileIntentUserActionHandler,
+                { fontScalingDialogDelegate },
+                keyguardStateController,
+                dialogLaunchAnimator,
+                activityStarter
+            )
+    }
+
+    @Test
+    fun clickTile_screenUnlocked_showDialogAnimationFromView() =
+        kosmos.testScope.runTest {
+            keyguardStateController.isShowing = false
+            val testView = View(context)
+
+            underTest.handleInput(click(FontScalingTileModel, view = testView))
+
+            verify(activityStarter)
+                .executeRunnableDismissingKeyguard(
+                    argumentCaptor.capture(),
+                    eq(null),
+                    eq(true),
+                    eq(true),
+                    eq(false)
+                )
+            argumentCaptor.value.run()
+            verify(dialogLaunchAnimator).showFromView(any(), eq(testView), nullable(), anyBoolean())
+        }
+
+    @Test
+    fun clickTile_onLockScreen_neverShowDialogAnimationFromView_butShowsDialog() =
+        kosmos.testScope.runTest {
+            keyguardStateController.isShowing = true
+            val testView = View(context)
+
+            underTest.handleInput(click(FontScalingTileModel, view = testView))
+
+            verify(activityStarter)
+                .executeRunnableDismissingKeyguard(
+                    argumentCaptor.capture(),
+                    eq(null),
+                    eq(true),
+                    eq(true),
+                    eq(false)
+                )
+            argumentCaptor.value.run()
+            verify(dialogLaunchAnimator, never())
+                .showFromView(any(), eq(testView), nullable(), anyBoolean())
+            verify(dialog).show()
+        }
+
+    @Test
+    fun handleLongClick() =
+        kosmos.testScope.runTest {
+            underTest.handleInput(QSTileInputTestKtx.longClick(FontScalingTileModel))
+
+            Truth.assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1)
+            val intentInput = qsTileIntentUserActionHandler.intentInputs.last()
+            val actualIntentAction = intentInput.intent.action
+            val expectedIntentAction = Settings.ACTION_TEXT_READING_SETTINGS
+            Truth.assertThat(actualIntentAction).isEqualTo(expectedIntentAction)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java
index c669c6f..1f6ba29 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java
@@ -33,6 +33,10 @@
         mOccluded = occluded;
     }
 
+    public void setShowing(boolean isShowing) {
+        mShowing = isShowing;
+    }
+
     @Override
     public boolean isShowing() {
         return mShowing;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
index 135ab35..4047623 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
@@ -31,6 +31,10 @@
 import com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor.ColorCorrectionTileDataInteractor
 import com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor.ColorCorrectionUserActionInteractor
 import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.FontScalingTileMapper
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor.FontScalingTileDataInteractor
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor.FontScalingTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
 import com.android.systemui.qs.tiles.impl.inversion.domain.ColorInversionTileMapper
 import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionTileDataInteractor
 import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionUserActionInteractor
@@ -93,6 +97,7 @@
     companion object {
         const val COLOR_CORRECTION_TILE_SPEC = "color_correction"
         const val COLOR_INVERSION_TILE_SPEC = "inversion"
+        const val FONT_SCALING_TILE_SPEC = "font_scaling"
 
         @Provides
         @IntoMap
@@ -155,5 +160,36 @@
                 stateInteractor,
                 mapper,
             )
+
+        @Provides
+        @IntoMap
+        @StringKey(FONT_SCALING_TILE_SPEC)
+        fun provideFontScalingTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+            QSTileConfig(
+                tileSpec = TileSpec.create(FONT_SCALING_TILE_SPEC),
+                uiConfig =
+                    QSTileUIConfig.Resource(
+                        iconRes = R.drawable.ic_qs_font_scaling,
+                        labelRes = R.string.quick_settings_font_scaling_label,
+                    ),
+                instanceId = uiEventLogger.getNewInstanceId(),
+            )
+
+        /** Inject FontScaling Tile into tileViewModelMap in QSModule */
+        @Provides
+        @IntoMap
+        @StringKey(FONT_SCALING_TILE_SPEC)
+        fun provideFontScalingTileViewModel(
+            factory: QSTileViewModelFactory.Static<FontScalingTileModel>,
+            mapper: FontScalingTileMapper,
+            stateInteractor: FontScalingTileDataInteractor,
+            userActionInteractor: FontScalingTileUserActionInteractor
+        ): QSTileViewModel =
+            factory.create(
+                TileSpec.create(FONT_SCALING_TILE_SPEC),
+                userActionInteractor,
+                stateInteractor,
+                mapper,
+            )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
new file mode 100644
index 0000000..26069c7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 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 com.android.systemui.qs.tiles.impl.fontscaling.domain
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [FontScalingTileModel] to [QSTileState]. */
+class FontScalingTileMapper
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<FontScalingTileModel> {
+
+    override fun map(config: QSTileConfig, data: FontScalingTileModel): QSTileState =
+        QSTileState.build(resources, theme, config.uiConfig) {
+            val icon =
+                Icon.Loaded(
+                    resources.getDrawable(
+                        R.drawable.ic_qs_font_scaling,
+                        theme,
+                    ),
+                    contentDescription = null
+                )
+            this.icon = { icon }
+            contentDescription = label
+            activationState = QSTileState.ActivationState.ACTIVE
+            sideViewIcon = QSTileState.SideViewIcon.Chevron
+            supportedActions =
+                setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractor.kt
new file mode 100644
index 0000000..745e6a3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileDataInteractor.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 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 com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/** Provides [FontScalingTileModel]. */
+class FontScalingTileDataInteractor @Inject constructor() :
+    QSTileDataInteractor<FontScalingTileModel> {
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>
+    ): Flow<FontScalingTileModel> = flowOf(FontScalingTileModel)
+
+    override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileUserActionInteractor.kt
new file mode 100644
index 0000000..b6f4afb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileUserActionInteractor.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2024 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 com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.accessibility.fontscaling.FontScalingDialogDelegate
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import javax.inject.Inject
+import javax.inject.Provider
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
+
+/** Handles font scaling tile clicks. */
+class FontScalingTileUserActionInteractor
+@Inject
+constructor(
+    @Main private val coroutineContext: CoroutineContext,
+    private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+    private val fontScalingDialogDelegateProvider: Provider<FontScalingDialogDelegate>,
+    private val keyguardStateController: KeyguardStateController,
+    private val dialogLaunchAnimator: DialogLaunchAnimator,
+    private val activityStarter: ActivityStarter,
+) : QSTileUserActionInteractor<FontScalingTileModel> {
+
+    override suspend fun handleInput(input: QSTileInput<FontScalingTileModel>): Unit =
+        with(input) {
+            when (action) {
+                is QSTileUserAction.Click -> {
+                    // We animate from the touched view only if we are not on the keyguard
+                    val animateFromView: Boolean =
+                        action.view != null && !keyguardStateController.isShowing
+                    val runnable = Runnable {
+                        val dialog: SystemUIDialog =
+                            fontScalingDialogDelegateProvider.get().createDialog()
+                        if (animateFromView) {
+                            dialogLaunchAnimator.showFromView(
+                                dialog,
+                                action.view!!,
+                                DialogCuj(
+                                    InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+                                    INTERACTION_JANK_TAG
+                                )
+                            )
+                        } else {
+                            dialog.show()
+                        }
+                    }
+
+                    withContext(coroutineContext) {
+                        activityStarter.executeRunnableDismissingKeyguard(
+                            runnable,
+                            /* cancelAction= */ null,
+                            /* dismissShade= */ true,
+                            /* afterKeyguardGone= */ true,
+                            /* deferred= */ false
+                        )
+                    }
+                }
+                is QSTileUserAction.LongClick -> {
+                    qsTileIntentUserActionHandler.handle(
+                        action.view,
+                        Intent(Settings.ACTION_TEXT_READING_SETTINGS)
+                    )
+                }
+            }
+        }
+    companion object {
+        private const val INTERACTION_JANK_TAG = "font_scaling"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/model/FontScalingTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/model/FontScalingTileModel.kt
new file mode 100644
index 0000000..76042df
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/model/FontScalingTileModel.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 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 com.android.systemui.qs.tiles.impl.fontscaling.domain.model
+
+/** FontScaling tile model. No data needed as the tile just opens a dialog. */
+data object FontScalingTileModel
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/fontscaling/FontScalingTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/fontscaling/FontScalingTileKosmos.kt
new file mode 100644
index 0000000..9410ce6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/fontscaling/FontScalingTileKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 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 com.android.systemui.qs.tiles.impl.fontscaling
+
+import com.android.systemui.accessibility.qs.QSAccessibilityModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+
+val Kosmos.qsFontScalingTileConfig by
+    Kosmos.Fixture { QSAccessibilityModule.provideFontScalingTileConfig(qsEventLogger) }