Clock font axis editor dialogue

Bug: 364673969
Test: Manual + Presumbits
Flag: com.android.systemui.shared.new_customization_picker_ui
Change-Id: I2eade33e285514423d042c3b99085ef35cd2a24a
diff --git a/res/drawable/clock_font_apply.xml b/res/drawable/clock_font_apply.xml
new file mode 100644
index 0000000..11c6f06
--- /dev/null
+++ b/res/drawable/clock_font_apply.xml
@@ -0,0 +1,24 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="72dp"
+    android:height="56dp"
+    android:viewportWidth="72"
+    android:viewportHeight="56">
+  <group>
+    <clip-path
+        android:pathData="M0,0h72v56h-72z"/>
+    <group>
+      <clip-path
+          android:pathData="M0,28C0,12.536 12.536,0 28,0H44C59.464,0 72,12.536 72,28C72,43.464 59.464,56 44,56H28C12.536,56 0,43.464 0,28Z"/>
+      <path
+          android:pathData="M0,28C0,12.536 12.536,0 28,0H44C59.464,0 72,12.536 72,28C72,43.464 59.464,56 44,56H28C12.536,56 0,43.464 0,28Z"
+          android:fillColor="@color/system_on_primary"/>
+      <group>
+        <clip-path
+            android:pathData="M24,16h24v24h-24z"/>
+        <path
+            android:pathData="M33.55,34L27.85,28.3L29.275,26.875L33.55,31.15L42.725,21.975L44.15,23.4L33.55,34Z"
+            android:fillColor="@color/system_primary"/>
+      </group>
+    </group>
+  </group>
+</vector>
diff --git a/res/drawable/clock_font_revert.xml b/res/drawable/clock_font_revert.xml
new file mode 100644
index 0000000..10a46ad
--- /dev/null
+++ b/res/drawable/clock_font_revert.xml
@@ -0,0 +1,24 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="72dp"
+    android:height="56dp"
+    android:viewportWidth="72"
+    android:viewportHeight="56">
+  <group>
+    <clip-path
+        android:pathData="M0,0h72v56h-72z"/>
+    <group>
+      <clip-path
+          android:pathData="M0,28C0,12.536 12.536,0 28,0H44C59.464,0 72,12.536 72,28C72,43.464 59.464,56 44,56H28C12.536,56 0,43.464 0,28Z"/>
+      <path
+          android:pathData="M0,28C0,12.536 12.536,0 28,0H44C59.464,0 72,12.536 72,28C72,43.464 59.464,56 44,56H28C12.536,56 0,43.464 0,28Z"
+          android:fillColor="@color/system_secondary_container"/>
+      <group>
+        <clip-path
+            android:pathData="M24,16h24v24h-24z"/>
+        <path
+            android:pathData="M30.4,35L29,33.6L34.6,28L29,22.4L30.4,21L36,26.6L41.6,21L43,22.4L37.4,28L43,33.6L41.6,35L36,29.4L30.4,35Z"
+            android:fillColor="@color/system_on_secondary_container"/>
+      </group>
+    </group>
+  </group>
+</vector>
diff --git a/res/drawable/clock_font_switch_divider.xml b/res/drawable/clock_font_switch_divider.xml
new file mode 100644
index 0000000..abaee24
--- /dev/null
+++ b/res/drawable/clock_font_switch_divider.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="6dp"
+    android:height="48dp"
+    android:viewportWidth="6"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M2,11C2,10.448 2.448,10 3,10C3.552,10 4,10.448 4,11V37C4,37.552 3.552,38 3,38C2.448,38 2,37.552 2,37V11Z"
+      android:fillColor="@color/system_outline"/>
+</vector>
diff --git a/res/layout/clock_font_axis_name.xml b/res/layout/clock_font_axis_name.xml
new file mode 100644
index 0000000..dd29c6b
--- /dev/null
+++ b/res/layout/clock_font_axis_name.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ 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.
+  -->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/clock_axis_name"
+    android:layout_width="@dimen/clock_font_axis_name_width"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center_vertical" />
\ No newline at end of file
diff --git a/res/layout/clock_font_axis_slider_row.xml b/res/layout/clock_font_axis_slider_row.xml
new file mode 100644
index 0000000..cb663b4
--- /dev/null
+++ b/res/layout/clock_font_axis_slider_row.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ 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.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingVertical="@dimen/clock_axis_control_row_vertical_padding"
+    android:orientation="horizontal">
+    <include layout="@layout/clock_font_axis_name" />
+
+    <SeekBar
+        android:id="@+id/clock_axis_slider"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:paddingStart="@dimen/clock_axis_control_padding_start"
+        android:minHeight="@dimen/touch_target_min_height"
+        android:thumb="@null"
+        android:background="@null"
+        android:splitTrack="false"
+        android:progressDrawable="@drawable/saturation_progress_drawable" />
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/clock_font_axis_switch.xml b/res/layout/clock_font_axis_switch.xml
new file mode 100644
index 0000000..385d105
--- /dev/null
+++ b/res/layout/clock_font_axis_switch.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ 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.
+  -->
+
+<Switch xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/clock_axis_switch"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:paddingStart="@dimen/clock_axis_control_padding_start"
+    style="@style/Switch.SettingsLib" />
\ No newline at end of file
diff --git a/res/layout/clock_font_axis_switch_row.xml b/res/layout/clock_font_axis_switch_row.xml
new file mode 100644
index 0000000..139e5e0
--- /dev/null
+++ b/res/layout/clock_font_axis_switch_row.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ 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.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingVertical="@dimen/clock_axis_control_row_vertical_padding"
+    android:orientation="horizontal">
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/clock_switch_one"
+        android:orientation="horizontal">
+
+        <include layout="@layout/clock_font_axis_name" />
+        <include layout="@layout/clock_font_axis_switch" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/clock_switch_two"
+        android:orientation="horizontal"
+        android:visibility="gone">
+
+        <ImageView
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:src="@drawable/clock_font_switch_divider"
+            android:layout_marginHorizontal="@dimen/clock_font_control_switch_padding_horizontal"
+            android:layout_gravity="center_vertical"/>
+
+        <include layout="@layout/clock_font_axis_name" />
+        <include layout="@layout/clock_font_axis_switch" />
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/floating_sheet_clock.xml b/res/layout/floating_sheet_clock.xml
index 7ff663f..75afce6 100644
--- a/res/layout/floating_sheet_clock.xml
+++ b/res/layout/floating_sheet_clock.xml
@@ -148,7 +148,6 @@
                     android:clipToPadding="false" />
             </FrameLayout>
 
-
             <SeekBar
                 android:id="@+id/clock_color_slider"
                 android:layout_width="match_parent"
@@ -162,6 +161,19 @@
                 android:progressDrawable="@drawable/saturation_progress_drawable"
                 android:splitTrack="false" />
         </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/clock_floating_sheet_font_content"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingHorizontal="@dimen/floating_sheet_content_horizontal_padding"
+            android:paddingVertical="@dimen/floating_sheet_content_vertical_padding"
+            android:orientation="vertical"
+            android:clipToPadding="false"
+            android:clipChildren="false">
+
+            <!-- Populated dynamically w/ clock axis information -->
+        </LinearLayout>
     </FrameLayout>
 
     <com.android.wallpaper.picker.customization.ui.view.FloatingToolbar
@@ -170,4 +182,27 @@
         android:layout_height="wrap_content"
         android:layout_gravity="center_horizontal"
         android:layout_marginVertical="@dimen/floating_sheet_tab_toolbar_vertical_margin" />
+
+    <LinearLayout
+        android:id="@+id/clock_font_toolbar"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:layout_gravity="center_horizontal"
+        android:layout_marginVertical="@dimen/floating_sheet_tab_toolbar_vertical_margin">
+
+        <ImageView
+            android:id="@+id/clock_font_revert"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/clock_font_revert"
+            android:contentDescription="@string/clock_font_editor_revert" />
+        <ImageView
+            android:id="@+id/clock_font_apply"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingStart="@dimen/clock_font_apply_padding_start"
+            android:src="@drawable/clock_font_apply"
+            android:contentDescription="@string/clock_font_editor_apply" />
+    </LinearLayout>
 </LinearLayout>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index c242fe9..d8d30d3 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -194,4 +194,11 @@
     <dimen name="floating_sheet_color_option_size">54dp</dimen>
     <dimen name="floating_sheet_color_option_stroke_width">3dp</dimen>
     <dimen name="customization_option_entry_shortcut_icon_size">20dp</dimen>
+
+    <!-- Clock font control dimensions -->
+    <dimen name="clock_font_axis_name_width">64dp</dimen>
+    <dimen name="clock_axis_control_padding_start">16dp</dimen>
+    <dimen name="clock_axis_control_row_vertical_padding">10dp</dimen>
+    <dimen name="clock_font_control_switch_padding_horizontal">38dp</dimen>
+    <dimen name="clock_font_apply_padding_start">8dp</dimen>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5643351..586117f 100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -96,6 +96,12 @@
     <!-- Description of a radio button to apply clock size small. [CHAR LIMIT=NONE] -->
     <string name="clock_size_small_description">A small clock shows in the corner of your screen</string>
 
+    <!-- Description for clock font editor axis apply button. [CHAR LIMIT=NONE] -->
+    <string name="clock_font_editor_apply">Apply clock font changes</string>
+
+    <!-- Description for clock font editor axis revert button. [CHAR LIMIT=NONE] -->
+    <string name="clock_font_editor_revert">Undo clock font changes</string>
+
     <!-- Title of a section of the customization picker where the user can select a Grid size for
         the home screen. [CHAR LIMIT=15] -->
     <string name="grid_title">App grid</string>
diff --git a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
index f3a2301..86920a3 100644
--- a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
+++ b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
@@ -22,6 +22,7 @@
 import androidx.annotation.IntRange
 import com.android.customization.picker.clock.shared.ClockSize
 import com.android.customization.picker.clock.shared.model.ClockMetadataModel
+import com.android.systemui.plugins.clocks.ClockFontAxis
 import com.android.systemui.plugins.clocks.ClockMetadata
 import com.android.systemui.shared.clocks.ClockRegistry
 import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
@@ -70,7 +71,7 @@
                                     description = clockConfig.description,
                                     thumbnail = clockConfig.thumbnail,
                                     isReactiveToTone = clockConfig.isReactiveToTone,
-                                    hasReactiveAxes = !clockConfig.axes.isEmpty(),
+                                    axes = clockConfig.axes,
                                 )
                             } else {
                                 null
@@ -116,7 +117,7 @@
                                     description = it.description,
                                     thumbnail = it.thumbnail,
                                     isReactiveToTone = it.isReactiveToTone,
-                                    hasReactiveAxes = !it.axes.isEmpty(),
+                                    axes = it.axes,
                                     selectedColorId = metadata?.getSelectedColorId(),
                                     colorTone =
                                         metadata?.getColorTone()
@@ -206,7 +207,7 @@
         description: String,
         thumbnail: Drawable,
         isReactiveToTone: Boolean,
-        hasReactiveAxes: Boolean,
+        axes: List<ClockFontAxis>,
         selectedColorId: String? = null,
         @IntRange(from = 0, to = 100) colorTone: Int = 0,
         @ColorInt seedColor: Int? = null,
@@ -217,7 +218,7 @@
             description = description,
             thumbnail = thumbnail,
             isReactiveToTone = isReactiveToTone,
-            hasReactiveAxes = hasReactiveAxes,
+            axes = axes,
             selectedColorId = selectedColorId,
             colorToneProgress = colorTone,
             seedColor = seedColor,
diff --git a/src/com/android/customization/picker/clock/shared/model/ClockMetadataModel.kt b/src/com/android/customization/picker/clock/shared/model/ClockMetadataModel.kt
index ff8d85f..dfedb5d 100644
--- a/src/com/android/customization/picker/clock/shared/model/ClockMetadataModel.kt
+++ b/src/com/android/customization/picker/clock/shared/model/ClockMetadataModel.kt
@@ -20,6 +20,7 @@
 import android.graphics.drawable.Drawable
 import androidx.annotation.ColorInt
 import androidx.annotation.IntRange
+import com.android.systemui.plugins.clocks.ClockFontAxis
 
 /** Model for clock metadata. */
 data class ClockMetadataModel(
@@ -28,7 +29,7 @@
     val description: String,
     val thumbnail: Drawable,
     val isReactiveToTone: Boolean,
-    val hasReactiveAxes: Boolean,
+    val axes: List<ClockFontAxis>,
     val selectedColorId: String?,
     @IntRange(from = 0, to = 100) val colorToneProgress: Int,
     @ColorInt val seedColor: Int?,
diff --git a/src/com/android/customization/picker/clock/ui/view/ThemePickerClockViewFactory.kt b/src/com/android/customization/picker/clock/ui/view/ThemePickerClockViewFactory.kt
index b68edd9..09da12a 100644
--- a/src/com/android/customization/picker/clock/ui/view/ThemePickerClockViewFactory.kt
+++ b/src/com/android/customization/picker/clock/ui/view/ThemePickerClockViewFactory.kt
@@ -26,6 +26,7 @@
 import androidx.lifecycle.LifecycleOwner
 import com.android.internal.policy.SystemBarUtils
 import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.ClockFontAxisSetting
 import com.android.systemui.plugins.clocks.WeatherData
 import com.android.systemui.shared.clocks.ClockRegistry
 import com.android.wallpaper.config.BaseFlags
@@ -135,6 +136,10 @@
         }
     }
 
+    override fun updateFontAxes(clockId: String, settings: List<ClockFontAxisSetting>) {
+        getController(clockId).let { it.events.onFontAxesChanged(settings) }
+    }
+
     override fun updateRegionDarkness() {
         val isRegionDark = isLockscreenWallpaperDark()
         clockControllers.values.forEach {
diff --git a/src/com/android/wallpaper/customization/ui/binder/ClockFloatingSheetBinder.kt b/src/com/android/wallpaper/customization/ui/binder/ClockFloatingSheetBinder.kt
index 983b897..3423617 100644
--- a/src/com/android/wallpaper/customization/ui/binder/ClockFloatingSheetBinder.kt
+++ b/src/com/android/wallpaper/customization/ui/binder/ClockFloatingSheetBinder.kt
@@ -19,11 +19,13 @@
 import android.animation.ValueAnimator
 import android.content.Context
 import android.content.res.Configuration
+import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import android.widget.ImageView
 import android.widget.SeekBar
 import android.widget.Switch
+import android.widget.TextView
 import androidx.core.view.doOnLayout
 import androidx.core.view.isVisible
 import androidx.lifecycle.Lifecycle
@@ -37,18 +39,22 @@
 import com.android.customization.picker.color.ui.view.ColorOptionIconView
 import com.android.customization.picker.color.ui.viewmodel.ColorOptionIconViewModel
 import com.android.customization.picker.common.ui.view.SingleRowListItemSpacing
+import com.android.systemui.plugins.clocks.AxisType
+import com.android.systemui.plugins.clocks.ClockFontAxis
+import com.android.systemui.plugins.clocks.ClockId
 import com.android.themepicker.R
 import com.android.wallpaper.customization.ui.util.ThemePickerCustomizationOptionUtil.ThemePickerLockCustomizationOption.CLOCK
 import com.android.wallpaper.customization.ui.viewmodel.ClockFloatingSheetHeightsViewModel
+import com.android.wallpaper.customization.ui.viewmodel.ClockPickerViewModel
 import com.android.wallpaper.customization.ui.viewmodel.ClockPickerViewModel.ClockStyleModel
-import com.android.wallpaper.customization.ui.viewmodel.ClockPickerViewModel.Tab.COLOR
-import com.android.wallpaper.customization.ui.viewmodel.ClockPickerViewModel.Tab.STYLE
+import com.android.wallpaper.customization.ui.viewmodel.ClockPickerViewModel.Tab
 import com.android.wallpaper.customization.ui.viewmodel.ThemePickerCustomizationOptionsViewModel
 import com.android.wallpaper.picker.customization.ui.view.FloatingToolbar
 import com.android.wallpaper.picker.customization.ui.view.adapter.FloatingToolbarTabAdapter
 import com.android.wallpaper.picker.customization.ui.viewmodel.ColorUpdateViewModel
 import com.android.wallpaper.picker.option.ui.adapter.OptionItemAdapter
 import java.lang.ref.WeakReference
+import kotlin.math.abs
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
@@ -94,6 +100,17 @@
                 initStyleList(appContext, clockStyleAdapter)
             }
 
+        // Clock font editor
+        val clockFontContent =
+            view.requireViewById<ViewGroup>(R.id.clock_floating_sheet_font_content)
+        val clockFontToolbar = view.requireViewById<ViewGroup>(R.id.clock_font_toolbar)
+        clockFontToolbar
+            .requireViewById<View>(R.id.clock_font_revert)
+            .setOnClickListener(View.OnClickListener { viewModel.revertFontAxes() })
+        clockFontToolbar
+            .requireViewById<View>(R.id.clock_font_apply)
+            .setOnClickListener(View.OnClickListener { viewModel.applyFontAxes() })
+
         // Clock color
         val clockColorContent = view.requireViewById<View>(R.id.clock_floating_sheet_color_content)
         val clockColorAdapter =
@@ -126,6 +143,7 @@
                     ClockFloatingSheetHeightsViewModel(
                         clockStyleContentHeight = clockStyleContent.height,
                         clockColorContentHeight = clockColorContent.height,
+                        clockFontContentHeight = clockFontContent.height,
                     )
             }
         }
@@ -135,33 +153,69 @@
                 launch { viewModel.tabs.collect { tabAdapter.submitList(it) } }
 
                 launch {
-                    combine(clockFloatingSheetHeights, viewModel.selectedTab) { heights, selectedTab
-                            ->
-                            heights to selectedTab
+                    combine(clockFloatingSheetHeights, viewModel.selectedTab, ::Pair).collect {
+                        (heights, selectedTab) ->
+                        heights ?: return@collect
+
+                        val targetHeight =
+                            when (selectedTab) {
+                                Tab.STYLE -> heights.clockStyleContentHeight
+                                Tab.COLOR -> heights.clockColorContentHeight
+                                Tab.FONT -> heights.clockFontContentHeight
+                            } +
+                                view.resources.getDimensionPixelSize(
+                                    R.dimen.floating_sheet_content_vertical_padding
+                                ) * 2
+
+                        val animationFloatingSheet =
+                            ValueAnimator.ofInt(floatingSheetContainer.height, targetHeight)
+                        animationFloatingSheet.addUpdateListener { valueAnimator ->
+                            val value = valueAnimator.animatedValue as Int
+                            floatingSheetContainer.layoutParams =
+                                floatingSheetContainer.layoutParams.apply { height = value }
                         }
-                        .collect { (heights, selectedTab) ->
-                            heights ?: return@collect
-                            val targetHeight =
-                                when (selectedTab) {
-                                    STYLE -> heights.clockStyleContentHeight
-                                    COLOR -> heights.clockColorContentHeight
-                                } +
-                                    view.resources.getDimensionPixelSize(
-                                        R.dimen.floating_sheet_content_vertical_padding
-                                    ) * 2
+                        animationFloatingSheet.setDuration(ANIMATION_DURATION)
+                        animationFloatingSheet.start()
 
-                            val animationFloatingSheet =
-                                ValueAnimator.ofInt(floatingSheetContainer.height, targetHeight)
-                            animationFloatingSheet.addUpdateListener { valueAnimator ->
-                                val value = valueAnimator.animatedValue as Int
-                                floatingSheetContainer.layoutParams =
-                                    floatingSheetContainer.layoutParams.apply { height = value }
+                        clockStyleContent.isVisible = selectedTab == Tab.STYLE
+                        clockColorContent.isVisible = selectedTab == Tab.COLOR
+                        clockFontContent.isVisible = selectedTab == Tab.FONT
+
+                        tabs.isVisible = selectedTab != Tab.FONT
+                        clockFontToolbar.isVisible = selectedTab == Tab.FONT
+                    }
+                }
+
+                launch {
+                    var boundClockId: ClockId? = null
+                    var boundEditorViews = mapOf<String, Pair<View, ClockFontAxis>>()
+                    combine(viewModel.previewingClock, viewModel.previewingFontAxes, ::Pair)
+                        .collect { pair ->
+                            val (clock, axisSettings) = pair
+                            if (clock == null) {
+                                boundClockId = null
+                                boundEditorViews = mapOf()
+                                clockFontContent.removeAllViews()
+                                return@collect
                             }
-                            animationFloatingSheet.setDuration(ANIMATION_DURATION)
-                            animationFloatingSheet.start()
 
-                            clockStyleContent.isVisible = selectedTab == STYLE
-                            clockColorContent.isVisible = selectedTab == COLOR
+                            if (boundClockId != clock.clockId) {
+                                boundEditorViews =
+                                    initClockFontEditor(clockFontContent, clock.axes, viewModel)
+                                boundClockId = clock.clockId
+                            }
+
+                            for ((key, value) in axisSettings) {
+                                boundEditorViews[key]?.let { pair ->
+                                    val (view, axis) = pair
+                                    view.findViewById<Switch>(R.id.clock_axis_switch)?.apply {
+                                        isChecked = abs(value - axis.maxValue) < 0.01f
+                                    }
+                                    view.findViewById<SeekBar>(R.id.clock_axis_slider)?.apply {
+                                        setProgress(value.toInt(), false)
+                                    }
+                                }
+                            }
                         }
                 }
 
@@ -221,6 +275,76 @@
         }
     }
 
+    private fun initClockFontEditor(
+        parent: ViewGroup,
+        axes: List<ClockFontAxis>,
+        viewModel: ClockPickerViewModel,
+    ): Map<String, Pair<View, ClockFontAxis>> {
+        parent.removeAllViews()
+        val inflater = LayoutInflater.from(parent.context)
+        val axisMap = mutableMapOf<String, Pair<View, ClockFontAxis>>()
+        var nextSwitch: View? = null
+        for (axis in axes) {
+            val view =
+                when (axis.type) {
+                    AxisType.Float -> {
+                        val id = R.layout.clock_font_axis_slider_row
+                        val row = inflater.inflate(id, parent, false)
+                        parent.addView(row)
+                        row
+                    }
+                    AxisType.Boolean ->
+                        nextSwitch?.also { nextSwitch = null }
+                            ?: run {
+                                val id = R.layout.clock_font_axis_switch_row
+                                val row = inflater.inflate(id, parent, false)
+                                parent.addView(row)
+
+                                nextSwitch = row.requireViewById(R.id.clock_switch_two)
+                                row.requireViewById(R.id.clock_switch_one)
+                            }
+                }
+
+            view.visibility = View.VISIBLE
+            axisMap[axis.key] = Pair(view, axis)
+            view.contentDescription = axis.description
+            view.requireViewById<TextView>(R.id.clock_axis_name).text = axis.name
+
+            view.findViewById<Switch>(R.id.clock_axis_switch)?.apply {
+                isChecked = abs(axis.currentValue - axis.maxValue) < 0.01f
+                setOnCheckedChangeListener { v, _ ->
+                    val value = if (v.isChecked) axis.maxValue else axis.minValue
+                    viewModel.updatePreviewFontAxis(axis.key, value)
+                }
+            }
+
+            view.findViewById<SeekBar>(R.id.clock_axis_slider)?.apply {
+                setMax(axis.maxValue.toInt())
+                setMin(axis.minValue.toInt())
+                setProgress(axis.currentValue.toInt(), false)
+
+                setOnSeekBarChangeListener(
+                    object : SeekBar.OnSeekBarChangeListener {
+                        override fun onProgressChanged(
+                            seekBar: SeekBar?,
+                            progress: Int,
+                            fromUser: Boolean,
+                        ) {
+                            if (fromUser) {
+                                viewModel.updatePreviewFontAxis(axis.key, progress.toFloat())
+                            }
+                        }
+
+                        override fun onStartTrackingTouch(seekBar: SeekBar?) {}
+
+                        override fun onStopTrackingTouch(seekBar: SeekBar?) {}
+                    }
+                )
+            }
+        }
+        return axisMap
+    }
+
     private fun createClockStyleOptionItemAdapter(
         lifecycleOwner: LifecycleOwner
     ): OptionItemAdapter<ClockStyleModel> =
@@ -230,6 +354,7 @@
             bindIcon = { view: View, style: ClockStyleModel ->
                 (view.findViewById(R.id.clock_icon) as ImageView).setImageDrawable(style.thumbnail)
                 (view.findViewById(R.id.edit_icon) as ImageView).setVisibility(
+                    // TODO(b/364673969): Route isSelected here and hide when unselected
                     if (style.isEditable) View.VISIBLE else View.GONE
                 )
             },
diff --git a/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt b/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt
index a92471c..528c61a 100644
--- a/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt
+++ b/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt
@@ -30,6 +30,7 @@
 import com.android.customization.picker.clock.ui.view.ClockHostView2
 import com.android.customization.picker.clock.ui.view.ClockViewFactory
 import com.android.customization.picker.grid.ui.binder.GridIconViewBinder
+import com.android.systemui.plugins.clocks.ClockFontAxisSetting
 import com.android.themepicker.R
 import com.android.wallpaper.config.BaseFlags
 import com.android.wallpaper.customization.ui.util.ThemePickerCustomizationOptionUtil.ThemePickerHomeCustomizationOption
@@ -276,11 +277,15 @@
                     combine(
                             clockPickerViewModel.previewingSeedColor,
                             clockPickerViewModel.previewingClock,
-                        ) { color, clock ->
-                            color to clock
-                        }
-                        .collect { (color, clock) ->
+                            clockPickerViewModel.previewingFontAxes,
+                            ::Triple,
+                        )
+                        .collect { triple ->
+                            val (color, clock, axisMap) = triple
                             clockViewFactory.updateColor(clock.clockId, color)
+
+                            val axisList = axisMap.map { ClockFontAxisSetting(it.key, it.value) }
+                            clockViewFactory.updateFontAxes(clock.clockId, axisList)
                         }
                 }
             }
diff --git a/src/com/android/wallpaper/customization/ui/viewmodel/ClockFloatingSheetHeightsViewModel.kt b/src/com/android/wallpaper/customization/ui/viewmodel/ClockFloatingSheetHeightsViewModel.kt
index 913ff11..1f86533 100644
--- a/src/com/android/wallpaper/customization/ui/viewmodel/ClockFloatingSheetHeightsViewModel.kt
+++ b/src/com/android/wallpaper/customization/ui/viewmodel/ClockFloatingSheetHeightsViewModel.kt
@@ -19,4 +19,5 @@
 data class ClockFloatingSheetHeightsViewModel(
     val clockStyleContentHeight: Int,
     val clockColorContentHeight: Int,
+    val clockFontContentHeight: Int,
 )
diff --git a/src/com/android/wallpaper/customization/ui/viewmodel/ClockPickerViewModel.kt b/src/com/android/wallpaper/customization/ui/viewmodel/ClockPickerViewModel.kt
index 504b766..fe8a718 100644
--- a/src/com/android/wallpaper/customization/ui/viewmodel/ClockPickerViewModel.kt
+++ b/src/com/android/wallpaper/customization/ui/viewmodel/ClockPickerViewModel.kt
@@ -40,6 +40,7 @@
 import dagger.assisted.AssistedInject
 import dagger.hilt.android.qualifiers.ApplicationContext
 import dagger.hilt.android.scopes.ViewModelScoped
+import kotlin.collections.map
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -72,6 +73,7 @@
     enum class Tab {
         STYLE,
         COLOR,
+        FONT,
     }
 
     private val colorMap = ClockColorViewModel.getPresetColorMap(context.resources)
@@ -137,7 +139,7 @@
                         payload =
                             ClockStyleModel(
                                 clockModel.thumbnail,
-                                isEditable = clockModel.hasReactiveAxes,
+                                isEditable = !clockModel.axes.isEmpty(),
                             ),
                         text = Text.Loaded(contentDescription),
                         isTextUserVisible = false,
@@ -145,9 +147,14 @@
                         onClicked =
                             isSelectedFlow.map { isSelected ->
                                 if (isSelected) {
-                                    null
+                                    fun() {
+                                        _selectedTab.value = Tab.FONT
+                                    }
                                 } else {
-                                    { overridingClock.value = clockModel }
+                                    fun() {
+                                        overridingClock.value = clockModel
+                                        overrideFontAxes.value = null
+                                    }
                                 }
                             },
                     )
@@ -159,6 +166,29 @@
             .flowOn(backgroundDispatcher.limitedParallelism(1))
             .stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
 
+    // Clock Font Axis Editor
+    private val overrideFontAxes = MutableStateFlow<Map<String, Float>?>(null)
+    val previewingFontAxes =
+        combine(overrideFontAxes, previewingClock) { overrideAxes, previewingClock ->
+                overrideAxes ?: previewingClock.axes.associate { it.key to it.currentValue }
+            }
+            .stateIn(viewModelScope, SharingStarted.Eagerly, emptyMap())
+
+    fun updatePreviewFontAxis(key: String, value: Float) {
+        val fontAxes = previewingFontAxes.value.toMutableMap()
+        fontAxes[key] = value
+        overrideFontAxes.value = fontAxes
+    }
+
+    fun applyFontAxes() {
+        _selectedTab.value = Tab.STYLE
+    }
+
+    fun revertFontAxes() {
+        overrideFontAxes.value = null
+        _selectedTab.value = Tab.STYLE
+    }
+
     // Clock size
     private val overridingClockSize = MutableStateFlow<ClockSize?>(null)
     val previewingClockSize =
@@ -360,6 +390,7 @@
         overridingClockSize.value = null
         overridingClockColorId.value = null
         overridingSliderProgress.value = null
+        overrideFontAxes.value = null
         _selectedTab.value = Tab.STYLE
     }
 
diff --git a/tests/robotests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt b/tests/robotests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt
index 729241f..c1a4a65 100644
--- a/tests/robotests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt
+++ b/tests/robotests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt
@@ -22,6 +22,8 @@
 import com.android.customization.picker.clock.data.repository.FakeClockPickerRepository.Companion.fakeClocks
 import com.android.customization.picker.clock.shared.ClockSize
 import com.android.customization.picker.clock.shared.model.ClockMetadataModel
+import com.android.systemui.plugins.clocks.AxisType
+import com.android.systemui.plugins.clocks.ClockFontAxis
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
@@ -50,7 +52,7 @@
                 description = "description",
                 thumbnail = ColorDrawable(0),
                 isReactiveToTone = selectedClock.isReactiveToTone,
-                hasReactiveAxes = false,
+                axes = fakeAxisList,
                 selectedColorId = selectedColor,
                 colorToneProgress = colorTone,
                 seedColor = seedColor,
@@ -79,6 +81,19 @@
     }
 
     companion object {
+        private val fakeAxisList =
+            listOf(
+                ClockFontAxis(
+                    key = "key",
+                    type = AxisType.Float,
+                    maxValue = 0f,
+                    minValue = 100f,
+                    currentValue = 50f,
+                    name = "FakeAxis",
+                    description = "Axis Description",
+                )
+            )
+
         const val CLOCK_ID_0 = "clock0"
         const val CLOCK_ID_1 = "clock1"
         const val CLOCK_ID_2 = "clock2"
@@ -91,7 +106,7 @@
                     "description0",
                     ColorDrawable(0),
                     true,
-                    false,
+                    fakeAxisList,
                     null,
                     50,
                     null,
@@ -102,7 +117,7 @@
                     "description1",
                     ColorDrawable(0),
                     true,
-                    false,
+                    fakeAxisList,
                     null,
                     50,
                     null,
@@ -113,7 +128,7 @@
                     "description2",
                     ColorDrawable(0),
                     true,
-                    false,
+                    fakeAxisList,
                     null,
                     50,
                     null,
@@ -124,7 +139,7 @@
                     "description3",
                     ColorDrawable(0),
                     false,
-                    false,
+                    fakeAxisList,
                     null,
                     50,
                     null,
diff --git a/tests/robotests/src/com/android/customization/picker/clock/ui/FakeClockViewFactory.kt b/tests/robotests/src/com/android/customization/picker/clock/ui/FakeClockViewFactory.kt
index d2acb16..bb2ad4c 100644
--- a/tests/robotests/src/com/android/customization/picker/clock/ui/FakeClockViewFactory.kt
+++ b/tests/robotests/src/com/android/customization/picker/clock/ui/FakeClockViewFactory.kt
@@ -8,6 +8,7 @@
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockEvents
 import com.android.systemui.plugins.clocks.ClockFaceController
+import com.android.systemui.plugins.clocks.ClockFontAxisSetting
 import java.io.PrintWriter
 import javax.inject.Inject
 
@@ -56,6 +57,10 @@
         TODO("Not yet implemented")
     }
 
+    override fun updateFontAxes(clockId: String, settings: List<ClockFontAxisSetting>) {
+        TODO("Not yet implemented")
+    }
+
     override fun updateRegionDarkness() {
         TODO("Not yet implemented")
     }
diff --git a/tests/robotests/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModelTest.kt b/tests/robotests/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModelTest.kt
index cf1cc55..166855f 100644
--- a/tests/robotests/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModelTest.kt
+++ b/tests/robotests/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModelTest.kt
@@ -58,7 +58,7 @@
                     description = "description",
                     thumbnail = ColorDrawable(0),
                     isReactiveToTone = true,
-                    hasReactiveAxes = false,
+                    axes = listOf(),
                     selectedColorId = null,
                     colorToneProgress = ClockMetadataModel.DEFAULT_COLOR_TONE_PROGRESS,
                     seedColor = null,
diff --git a/tests/robotests/src/com/android/wallpaper/customization/ui/viewmodel/ClockPickerViewModelTest.kt b/tests/robotests/src/com/android/wallpaper/customization/ui/viewmodel/ClockPickerViewModelTest.kt
index 6bec3df..8b87368 100644
--- a/tests/robotests/src/com/android/wallpaper/customization/ui/viewmodel/ClockPickerViewModelTest.kt
+++ b/tests/robotests/src/com/android/wallpaper/customization/ui/viewmodel/ClockPickerViewModelTest.kt
@@ -138,6 +138,22 @@
     }
 
     @Test
+    fun selectedTab_fontEditorWhenClickSelectedClock() = runTest {
+        val clockStyleOptions = collectLastValue(underTest.clockStyleOptions)
+        val selectedTab = collectLastValue(underTest.selectedTab)
+        // Advance CLOCKS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from clockStyleOptions
+        advanceTimeBy(ClockPickerViewModel.CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
+        assertThat(selectedTab()).isEqualTo(Tab.STYLE)
+
+        val firstClock = clockStyleOptions()!![0]
+        val onClicked = collectLastValue(firstClock.onClicked)
+        if (!firstClock.isSelected.value) onClicked()?.invoke()
+        onClicked()?.invoke()
+
+        assertThat(selectedTab()).isEqualTo(Tab.FONT)
+    }
+
+    @Test
     fun previewingClock_whenClickOnStyleOptions() = runTest {
         val previewingClock = collectLastValue(underTest.previewingClock)
         val clockStyleOptions = collectLastValue(underTest.clockStyleOptions)
@@ -165,7 +181,7 @@
         val option1OnClicked = collectLastValue(clockStyleOptions()!![1].onClicked)
 
         assertThat(option0IsSelected()).isTrue()
-        assertThat(option0OnClicked()).isNull()
+        assertThat(option0OnClicked()).isNotNull()
 
         option1OnClicked()?.invoke()
         // Advance CLOCKS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from clockColorOptions
@@ -173,7 +189,7 @@
 
         assertThat(option0IsSelected()).isFalse()
         assertThat(option1IsSelected()).isTrue()
-        assertThat(option1OnClicked()).isNull()
+        assertThat(option1OnClicked()).isNotNull()
     }
 
     @Test
@@ -190,6 +206,76 @@
     }
 
     @Test
+    fun previewingFontAxes_defaultWhenNoOverrides() = runTest {
+        val previewingFontAxes = collectLastValue(underTest.previewingFontAxes)
+        assertThat(previewingFontAxes()).isEqualTo(mapOf("key" to 50f))
+    }
+
+    @Test
+    fun previewingFontAxes_updateAxisChangesSetting() = runTest {
+        val previewingFontAxes = collectLastValue(underTest.previewingFontAxes)
+        assertThat(previewingFontAxes()).isEqualTo(mapOf("key" to 50f))
+
+        underTest.updatePreviewFontAxis("key", 100f)
+        assertThat(previewingFontAxes()).isEqualTo(mapOf("key" to 100f))
+
+        underTest.updatePreviewFontAxis("extra", 10f)
+        assertThat(previewingFontAxes()).isEqualTo(mapOf("key" to 100f, "extra" to 10f))
+    }
+
+    @Test
+    fun previewingFontAxes_applyFontEditorExitsTab_keepsPreviewAxis() = runTest {
+        val previewingFontAxes = collectLastValue(underTest.previewingFontAxes)
+        val clockStyleOptions = collectLastValue(underTest.clockStyleOptions)
+        val selectedTab = collectLastValue(underTest.selectedTab)
+        // Advance CLOCKS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from clockStyleOptions
+        advanceTimeBy(ClockPickerViewModel.CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
+
+        assertThat(previewingFontAxes()).isEqualTo(mapOf("key" to 50f))
+        assertThat(selectedTab()).isEqualTo(Tab.STYLE)
+
+        val firstClock = clockStyleOptions()!![0]
+        val onClicked = collectLastValue(firstClock.onClicked)
+        if (!firstClock.isSelected.value) onClicked()?.invoke()
+        onClicked()?.invoke()
+        underTest.updatePreviewFontAxis("key", 100f)
+
+        assertThat(selectedTab()).isEqualTo(Tab.FONT)
+        assertThat(previewingFontAxes()).isEqualTo(mapOf("key" to 100f))
+
+        underTest.applyFontAxes()
+
+        assertThat(selectedTab()).isEqualTo(Tab.STYLE)
+        assertThat(previewingFontAxes()).isEqualTo(mapOf("key" to 100f))
+    }
+
+    @Test
+    fun previewingFontAxes_revertFontEditorExitsTab_revertsPreviewAxis() = runTest {
+        val previewingFontAxes = collectLastValue(underTest.previewingFontAxes)
+        val clockStyleOptions = collectLastValue(underTest.clockStyleOptions)
+        val selectedTab = collectLastValue(underTest.selectedTab)
+        // Advance CLOCKS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from clockStyleOptions
+        advanceTimeBy(ClockPickerViewModel.CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
+
+        assertThat(previewingFontAxes()).isEqualTo(mapOf("key" to 50f))
+        assertThat(selectedTab()).isEqualTo(Tab.STYLE)
+
+        val firstClock = clockStyleOptions()!![0]
+        val onClicked = collectLastValue(firstClock.onClicked)
+        if (!firstClock.isSelected.value) onClicked()?.invoke()
+        onClicked()?.invoke()
+        underTest.updatePreviewFontAxis("key", 100f)
+
+        assertThat(selectedTab()).isEqualTo(Tab.FONT)
+        assertThat(previewingFontAxes()).isEqualTo(mapOf("key" to 100f))
+
+        underTest.revertFontAxes()
+
+        assertThat(selectedTab()).isEqualTo(Tab.STYLE)
+        assertThat(previewingFontAxes()).isEqualTo(mapOf("key" to 50f))
+    }
+
+    @Test
     fun sliderProgress_whenOnSliderProgressChanged() = runTest {
         val sliderProgress = collectLastValue(underTest.previewingSliderProgress)