Integrating ColorContrast in Picker

This CL aims at adding changes for integrating ColorContrast
functionality in WallpaperPicker App.

Bug: b/326108993
Test: Tested by building and installing picker on local and checking if
option appears or not, and testing dark/light theme
Flag: N/A

Change-Id: I8a9ec55d4aef621d2ea74be5373fc5137aa49b3b
diff --git a/res/drawable-night/ic_contrast_high.xml b/res/drawable-night/ic_contrast_high.xml
new file mode 100644
index 0000000..3134ab8
--- /dev/null
+++ b/res/drawable-night/ic_contrast_high.xml
@@ -0,0 +1,28 @@
+<!--
+  ~ 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="48dp"
+    android:height="48dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/system_accent1_200"
+      android:pathData="M12,4C7,4 2.73,7.11 1,11.5C2.73,15.89 7,19 12,19s9.27,-3.11 11,-7.5C21.27,7.11 17,4 12,4zM12,16c-2.48,0 -4.5,-2.02 -4.5,-4.5S9.52,7 12,7s4.5,2.02 4.5,4.5S14.48,16 12,16z"/>
+  <path
+      android:fillColor="@android:color/system_accent1_50"
+      android:pathData="M12,11.5m-2.7,0a2.7,2.7 0,1 1,5.4 0a2.7,2.7 0,1 1,-5.4 0"/>
+</vector>
diff --git a/res/drawable-night/ic_contrast_medium.xml b/res/drawable-night/ic_contrast_medium.xml
new file mode 100644
index 0000000..3928306
--- /dev/null
+++ b/res/drawable-night/ic_contrast_medium.xml
@@ -0,0 +1,28 @@
+<!--
+  ~ 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="48dp"
+    android:height="48dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/system_accent1_400"
+      android:pathData="M12,4C7,4 2.73,7.11 1,11.5C2.73,15.89 7,19 12,19s9.27,-3.11 11,-7.5C21.27,7.11 17,4 12,4zM12,16c-2.48,0 -4.5,-2.02 -4.5,-4.5S9.52,7 12,7s4.5,2.02 4.5,4.5S14.48,16 12,16z"/>
+  <path
+      android:fillColor="@android:color/system_accent1_100"
+      android:pathData="M12,11.5m-2.7,0a2.7,2.7 0,1 1,5.4 0a2.7,2.7 0,1 1,-5.4 0"/>
+</vector>
diff --git a/res/drawable-night/ic_contrast_standard.xml b/res/drawable-night/ic_contrast_standard.xml
new file mode 100644
index 0000000..39e6137
--- /dev/null
+++ b/res/drawable-night/ic_contrast_standard.xml
@@ -0,0 +1,28 @@
+<!--
+  ~ 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="48dp"
+    android:height="48dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/system_accent1_700"
+      android:pathData="M12,4C7,4 2.73,7.11 1,11.5C2.73,15.89 7,19 12,19s9.27,-3.11 11,-7.5C21.27,7.11 17,4 12,4zM12,16c-2.48,0 -4.5,-2.02 -4.5,-4.5S9.52,7 12,7s4.5,2.02 4.5,4.5S14.48,16 12,16z"/>
+  <path
+      android:fillColor="@android:color/system_accent1_200"
+      android:pathData="M12,11.5m-2.7,0a2.7,2.7 0,1 1,5.4 0a2.7,2.7 0,1 1,-5.4 0"/>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_contrast_high.xml b/res/drawable/ic_contrast_high.xml
new file mode 100644
index 0000000..147efb0
--- /dev/null
+++ b/res/drawable/ic_contrast_high.xml
@@ -0,0 +1,28 @@
+<!--
+  ~ 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="48dp"
+    android:height="48dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/system_accent1_700"
+      android:pathData="M12,4C7,4 2.73,7.11 1,11.5C2.73,15.89 7,19 12,19s9.27,-3.11 11,-7.5C21.27,7.11 17,4 12,4zM12,16c-2.48,0 -4.5,-2.02 -4.5,-4.5S9.52,7 12,7s4.5,2.02 4.5,4.5S14.48,16 12,16z"/>
+  <path
+      android:fillColor="@android:color/system_accent1_900"
+      android:pathData="M12,11.5m-2.7,0a2.7,2.7 0,1 1,5.4 0a2.7,2.7 0,1 1,-5.4 0"/>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_contrast_medium.xml b/res/drawable/ic_contrast_medium.xml
new file mode 100644
index 0000000..e839e0b
--- /dev/null
+++ b/res/drawable/ic_contrast_medium.xml
@@ -0,0 +1,28 @@
+<!--
+  ~ 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="48dp"
+    android:height="48dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/system_accent1_500"
+      android:pathData="M12,4C7,4 2.73,7.11 1,11.5C2.73,15.89 7,19 12,19s9.27,-3.11 11,-7.5C21.27,7.11 17,4 12,4zM12,16c-2.48,0 -4.5,-2.02 -4.5,-4.5S9.52,7 12,7s4.5,2.02 4.5,4.5S14.48,16 12,16z"/>
+  <path
+      android:fillColor="@android:color/system_accent1_700"
+      android:pathData="M12,11.5m-2.7,0a2.7,2.7 0,1 1,5.4 0a2.7,2.7 0,1 1,-5.4 0"/>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_contrast_standard.xml b/res/drawable/ic_contrast_standard.xml
new file mode 100644
index 0000000..75516d8
--- /dev/null
+++ b/res/drawable/ic_contrast_standard.xml
@@ -0,0 +1,28 @@
+<!--
+  ~ 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="48dp"
+    android:height="48dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/system_accent1_100"
+      android:pathData="M12,4C7,4 2.73,7.11 1,11.5C2.73,15.89 7,19 12,19s9.27,-3.11 11,-7.5C21.27,7.11 17,4 12,4zM12,16c-2.48,0 -4.5,-2.02 -4.5,-4.5S9.52,7 12,7s4.5,2.02 4.5,4.5S14.48,16 12,16z"/>
+  <path
+      android:fillColor="@android:color/system_accent1_600"
+      android:pathData="M12,11.5m-2.7,0a2.7,2.7 0,1 1,5.4 0a2.7,2.7 0,1 1,-5.4 0"/>
+</vector>
\ No newline at end of file
diff --git a/res/layout/color_contrast_section_view.xml b/res/layout/color_contrast_section_view.xml
new file mode 100644
index 0000000..ee6bd5b
--- /dev/null
+++ b/res/layout/color_contrast_section_view.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 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.
+-->
+<com.android.customization.picker.settings.ui.view.ColorContrastSectionView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:background="?selectableItemBackground"
+    android:clickable="true"
+    android:paddingVertical="@dimen/option_section_vertical_padding"
+    android:paddingHorizontal="@dimen/section_horizontal_padding"
+    android:orientation="horizontal">
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:orientation="vertical"
+        android:layout_gravity="center">
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/color_contrast_section_title"
+            style="@style/SectionTitleTextStyle" />
+
+        <TextView
+            android:id="@+id/color_contrast_section_description"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            style="@style/SectionSubtitleTextStyle"/>
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="@dimen/option_tile_width"
+        android:layout_height="@dimen/option_tile_width"
+        android:orientation="horizontal"
+        android:background="@drawable/picker_section_icon_background"
+        android:importantForAccessibility="noHideDescendants"
+        android:gravity="center"
+        android:divider="@drawable/horizontal_divider_14dp"
+        android:layout_gravity="center"
+        android:showDividers="middle">
+
+        <ImageView
+            android:id="@+id/icon_1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:visibility="gone" />
+    </LinearLayout>
+
+</com.android.customization.picker.settings.ui.view.ColorContrastSectionView>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d1f6035..271a74c 100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -413,6 +413,32 @@
     <string name="keyguard_quick_affordance_section_title">Shortcuts</string>
 
     <!--
+    Label for a menu item on a settings screen that helps the user open a new screen where they can
+    configure the lock screen shortcut buttons that appear on the device without unlocking.
+    [CHAR LIMIT=16].
+    -->
+    <string name="color_contrast_section_title">Color contrast</string>
+
+    <!--
+    Label for color contrast settings button with default contrast setting.
+    [CHAR LIMIT=16].
+    -->
+    <string name="color_contrast_default_title">Default</string>
+
+    <!--
+    Label for color contrast settings button with medium contrast setting.
+    [CHAR LIMIT=16].
+    -->
+
+    <string name="color_contrast_medium_title">Medium</string>
+
+    <!--
+    Label for color contrast settings button with maximum contrast setting.
+    [CHAR LIMIT=16].
+    -->
+    <string name="color_contrast_high_title">High</string>
+
+    <!--
     Template for text that shows the names of two currently-selected lock screen shortcuts on the
     lock screen. For example, it may say "Camera, Wallet", if the first selected shortcut opens the
     camera app and the second one opens the tap-to-pay wallet experience.
diff --git a/src/com/android/customization/module/DefaultCustomizationSections.java b/src/com/android/customization/module/DefaultCustomizationSections.java
index 8347d03..ee579fe 100644
--- a/src/com/android/customization/module/DefaultCustomizationSections.java
+++ b/src/com/android/customization/module/DefaultCustomizationSections.java
@@ -26,7 +26,9 @@
 import com.android.customization.picker.preview.ui.section.PreviewWithThemeSectionController;
 import com.android.customization.picker.quickaffordance.ui.section.KeyguardQuickAffordanceSectionController;
 import com.android.customization.picker.quickaffordance.ui.viewmodel.KeyguardQuickAffordancePickerViewModel;
+import com.android.customization.picker.settings.ui.section.ColorContrastSectionController;
 import com.android.customization.picker.settings.ui.section.MoreSettingsSectionController;
+import com.android.customization.picker.settings.ui.viewmodel.ColorContrastSectionViewModel;
 import com.android.wallpaper.config.BaseFlags;
 import com.android.wallpaper.model.CustomizationSectionController;
 import com.android.wallpaper.model.CustomizationSectionController.CustomizationSectionNavigationController;
@@ -50,6 +52,8 @@
     private final ColorPickerViewModel.Factory mColorPickerViewModelFactory;
     private final KeyguardQuickAffordancePickerViewModel.Factory
             mKeyguardQuickAffordancePickerViewModelFactory;
+    private final ColorContrastSectionViewModel.Factory
+            mColorContrastSectionViewModelFactory;
     private final NotificationSectionViewModel.Factory mNotificationSectionViewModelFactory;
     private final BaseFlags mFlags;
     private final ClockCarouselViewModel.Factory mClockCarouselViewModelFactory;
@@ -63,6 +67,7 @@
             ColorPickerViewModel.Factory colorPickerViewModelFactory,
             KeyguardQuickAffordancePickerViewModel.Factory
                     keyguardQuickAffordancePickerViewModelFactory,
+            ColorContrastSectionViewModel.Factory colorContrastSectionViewModelFactory,
             NotificationSectionViewModel.Factory notificationSectionViewModelFactory,
             BaseFlags flags,
             ClockCarouselViewModel.Factory clockCarouselViewModelFactory,
@@ -82,6 +87,7 @@
         mThemedIconInteractor = themedIconInteractor;
         mColorPickerInteractor = colorPickerInteractor;
         mThemesUserEventLogger = themesUserEventLogger;
+        mColorContrastSectionViewModelFactory = colorContrastSectionViewModelFactory;
     }
 
     @Override
@@ -191,6 +197,13 @@
                                 mThemedIconSnapshotRestorer,
                                 mThemesUserEventLogger));
 
+                // Color contrast section
+                if (mFlags.isColorContrastControlEnabled()) {
+                    sectionControllers.add(
+                            new ColorContrastSectionController(new ViewModelProvider(activity,
+                                    mColorContrastSectionViewModelFactory)
+                                    .get(ColorContrastSectionViewModel.class), lifecycleOwner));
+                }
                 // App grid section.
                 sectionControllers.add(
                         new GridSectionController(
diff --git a/src/com/android/customization/module/ThemePickerInjector.kt b/src/com/android/customization/module/ThemePickerInjector.kt
index a2f7e56..b65414b 100644
--- a/src/com/android/customization/module/ThemePickerInjector.kt
+++ b/src/com/android/customization/module/ThemePickerInjector.kt
@@ -62,6 +62,9 @@
 import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordancePickerInteractor
 import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordanceSnapshotRestorer
 import com.android.customization.picker.quickaffordance.ui.viewmodel.KeyguardQuickAffordancePickerViewModel
+import com.android.customization.picker.settings.data.repository.ColorContrastSectionRepository
+import com.android.customization.picker.settings.domain.interactor.ColorContrastSectionInteractor
+import com.android.customization.picker.settings.ui.viewmodel.ColorContrastSectionViewModel
 import com.android.systemui.shared.clocks.ClockRegistry
 import com.android.systemui.shared.customization.data.content.CustomizationProviderClient
 import com.android.systemui.shared.customization.data.content.CustomizationProviderClientImpl
@@ -125,6 +128,9 @@
     private var gridSnapshotRestorer: GridSnapshotRestorer? = null
     private var gridScreenViewModelFactory: GridScreenViewModel.Factory? = null
     private var clockRegistryProvider: ClockRegistryProvider? = null
+    private var colorContrastSectionViewModelFactory: ColorContrastSectionViewModel.Factory? = null
+    private var colorContrastSectionInteractor: ColorContrastSectionInteractor? = null
+
     @Inject lateinit var themesUserEventLogger: Lazy<ThemesUserEventLogger>
 
     override fun getCustomizationSections(activity: ComponentActivity): CustomizationSections {
@@ -138,6 +144,7 @@
                         wallpaperColorsRepository = getWallpaperColorsRepository(),
                     ),
                     getKeyguardQuickAffordancePickerViewModelFactory(appContext),
+                    getColorContrastSectionViewModelFactory(appContext),
                     getNotificationSectionViewModelFactory(appContext),
                     getFlags(),
                     getClockCarouselViewModelFactory(
@@ -229,6 +236,29 @@
                 .also { wallpaperInteractor = it }
     }
 
+    private fun getColorContrastSectionInteractorImpl(
+        context: Context
+    ): ColorContrastSectionInteractor {
+        return ColorContrastSectionInteractor(
+            ColorContrastSectionRepository(context, bgDispatcher),
+        )
+    }
+
+    fun getColorContrastSectionInteractor(context: Context): ColorContrastSectionInteractor {
+        return colorContrastSectionInteractor
+            ?: getColorContrastSectionInteractorImpl(context).also {
+                colorContrastSectionInteractor = it
+            }
+    }
+
+    fun getColorContrastSectionViewModelFactory(
+        context: Context
+    ): ColorContrastSectionViewModel.Factory {
+        return colorContrastSectionViewModelFactory
+            ?: ColorContrastSectionViewModel.Factory(getColorContrastSectionInteractor(context))
+                .also { colorContrastSectionViewModelFactory = it }
+    }
+
     override fun getKeyguardQuickAffordancePickerInteractor(
         context: Context
     ): KeyguardQuickAffordancePickerInteractor {
diff --git a/src/com/android/customization/picker/settings/data/repository/ColorContrastSectionRepository.kt b/src/com/android/customization/picker/settings/data/repository/ColorContrastSectionRepository.kt
new file mode 100644
index 0000000..3986c78
--- /dev/null
+++ b/src/com/android/customization/picker/settings/data/repository/ColorContrastSectionRepository.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.customization.picker.settings.data.repository
+
+import android.app.UiModeManager
+import android.content.Context
+import android.content.Context.UI_MODE_SERVICE
+import java.util.concurrent.Executor
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+
+class ColorContrastSectionRepository(
+    private val context: Context,
+    private val bgDispatcher: CoroutineDispatcher
+) {
+    var uiModeManager =
+        context.applicationContext.getSystemService(UI_MODE_SERVICE) as UiModeManager?
+    var contrast: Flow<Float> = callbackFlow {
+        val executor: Executor = bgDispatcher.asExecutor()
+        val listener =
+            UiModeManager.ContrastChangeListener { contrast ->
+                // Emit the new contrast value whenever it changes
+                trySend(contrast)
+            }
+
+        // Emit the current contrast value immediately
+        uiModeManager?.contrast?.let { currentContrast -> trySend(currentContrast) }
+
+        // Register the listener with the UiModeManager
+        uiModeManager?.addContrastChangeListener(executor, listener)
+
+        // Await close signals to unregister the listener to prevent memory leaks
+        awaitClose {
+            // Unregister the listener when the flow collection is cancelled or no longer in use
+            uiModeManager?.removeContrastChangeListener(listener)
+        }
+    }
+}
diff --git a/src/com/android/customization/picker/settings/domain/interactor/ColorContrastSectionInteractor.kt b/src/com/android/customization/picker/settings/domain/interactor/ColorContrastSectionInteractor.kt
new file mode 100644
index 0000000..96ec5b8
--- /dev/null
+++ b/src/com/android/customization/picker/settings/domain/interactor/ColorContrastSectionInteractor.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.customization.picker.settings.domain.interactor
+
+import com.android.customization.picker.settings.data.repository.ColorContrastSectionRepository
+import kotlinx.coroutines.flow.Flow
+
+class ColorContrastSectionInteractor(
+    private val colorContrastSectionRepository: ColorContrastSectionRepository
+) {
+    val contrast: Flow<Float> = colorContrastSectionRepository.contrast
+}
diff --git a/src/com/android/customization/picker/settings/ui/binder/ColorContrastSectionViewBinder.kt b/src/com/android/customization/picker/settings/ui/binder/ColorContrastSectionViewBinder.kt
new file mode 100644
index 0000000..e3d2427
--- /dev/null
+++ b/src/com/android/customization/picker/settings/ui/binder/ColorContrastSectionViewBinder.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.customization.picker.settings.ui.binder
+
+import android.view.View
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.flowWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import com.android.customization.picker.settings.ui.viewmodel.ColorContrastSectionViewModel
+import com.android.themepicker.R
+import com.android.wallpaper.picker.common.icon.ui.viewbinder.IconViewBinder
+import com.android.wallpaper.picker.common.text.ui.viewbinder.TextViewBinder
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+object ColorContrastSectionViewBinder {
+    fun bind(
+        view: View,
+        viewModel: ColorContrastSectionViewModel,
+        lifecycleOwner: LifecycleOwner,
+        onClicked: () -> Unit,
+    ) {
+        view.setOnClickListener { onClicked() }
+
+        val descriptionView: TextView =
+            view.requireViewById(R.id.color_contrast_section_description)
+        val icon: ImageView = view.requireViewById(R.id.icon_1)
+
+        lifecycleOwner.lifecycleScope.launch {
+            viewModel.summary
+                .flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED)
+                .collectLatest { summary ->
+                    TextViewBinder.bind(
+                        view = descriptionView,
+                        viewModel = summary.description,
+                    )
+                    if (summary.icon != null) {
+                        IconViewBinder.bind(
+                            view = icon,
+                            viewModel = summary.icon,
+                        )
+                    }
+                    icon.isVisible = summary.icon != null
+                }
+        }
+    }
+}
diff --git a/src/com/android/customization/picker/settings/ui/section/ColorContrastSectionController.kt b/src/com/android/customization/picker/settings/ui/section/ColorContrastSectionController.kt
new file mode 100644
index 0000000..cd5f65d
--- /dev/null
+++ b/src/com/android/customization/picker/settings/ui/section/ColorContrastSectionController.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.customization.picker.settings.ui.section
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.Intent
+import android.provider.Settings
+import android.view.LayoutInflater
+import androidx.lifecycle.LifecycleOwner
+import com.android.customization.picker.settings.ui.binder.ColorContrastSectionViewBinder
+import com.android.customization.picker.settings.ui.view.ColorContrastSectionView
+import com.android.customization.picker.settings.ui.viewmodel.ColorContrastSectionViewModel
+import com.android.themepicker.R
+import com.android.wallpaper.model.CustomizationSectionController
+
+class ColorContrastSectionController(
+    private val viewModel: ColorContrastSectionViewModel,
+    private val lifecycleOwner: LifecycleOwner,
+) : CustomizationSectionController<ColorContrastSectionView> {
+
+    // TODO (b/330381229): Check for whether the color contrast activity intent
+    // resolves to something or not before marking the feature as available
+    override fun isAvailable(context: Context): Boolean {
+        return true
+    }
+
+    @SuppressLint("InflateParams") // We're okay not providing a parent view.
+    override fun createView(context: Context): ColorContrastSectionView {
+        val view =
+            LayoutInflater.from(context)
+                .inflate(
+                    R.layout.color_contrast_section_view,
+                    null,
+                ) as ColorContrastSectionView
+        ColorContrastSectionViewBinder.bind(
+            view = view,
+            lifecycleOwner = lifecycleOwner,
+            viewModel = viewModel
+        ) {
+            context.startActivity(Intent(Settings.ACTION_ACCESSIBILITY_COLOR_CONTRAST_SETTINGS))
+        }
+        return view
+    }
+}
diff --git a/src/com/android/customization/picker/settings/ui/view/ColorContrastSectionView.kt b/src/com/android/customization/picker/settings/ui/view/ColorContrastSectionView.kt
new file mode 100644
index 0000000..6aa603b
--- /dev/null
+++ b/src/com/android/customization/picker/settings/ui/view/ColorContrastSectionView.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.customization.picker.settings.ui.view
+
+import android.content.Context
+import android.util.AttributeSet
+import com.android.wallpaper.picker.SectionView
+
+class ColorContrastSectionView(
+    context: Context,
+    attrs: AttributeSet?,
+) :
+    SectionView(
+        context,
+        attrs,
+    )
diff --git a/src/com/android/customization/picker/settings/ui/viewmodel/ColorContrastSectionDataViewModel.kt b/src/com/android/customization/picker/settings/ui/viewmodel/ColorContrastSectionDataViewModel.kt
new file mode 100644
index 0000000..871dc43
--- /dev/null
+++ b/src/com/android/customization/picker/settings/ui/viewmodel/ColorContrastSectionDataViewModel.kt
@@ -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.
+ */
+
+package com.android.customization.picker.settings.ui.viewmodel
+
+import com.android.wallpaper.picker.common.icon.ui.viewmodel.Icon
+import com.android.wallpaper.picker.common.text.ui.viewmodel.Text
+
+data class ColorContrastSectionDataViewModel(
+    val description: Text,
+    val icon: Icon?,
+)
diff --git a/src/com/android/customization/picker/settings/ui/viewmodel/ColorContrastSectionViewModel.kt b/src/com/android/customization/picker/settings/ui/viewmodel/ColorContrastSectionViewModel.kt
new file mode 100644
index 0000000..9b804a3
--- /dev/null
+++ b/src/com/android/customization/picker/settings/ui/viewmodel/ColorContrastSectionViewModel.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.customization.picker.settings.ui.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.android.customization.picker.settings.domain.interactor.ColorContrastSectionInteractor
+import com.android.themepicker.R
+import com.android.wallpaper.picker.common.icon.ui.viewmodel.Icon
+import com.android.wallpaper.picker.common.text.ui.viewmodel.Text
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+class ColorContrastSectionViewModel
+private constructor(
+    colorContrastSectionInteractor: ColorContrastSectionInteractor,
+) : ViewModel() {
+
+    val summary: Flow<ColorContrastSectionDataViewModel> =
+        colorContrastSectionInteractor.contrast.map { contrastValue ->
+            when (contrastValue) {
+                ContrastValue.STANDARD.value ->
+                    ColorContrastSectionDataViewModel(
+                        Text.Resource(R.string.color_contrast_default_title),
+                        Icon.Resource(
+                            res = R.drawable.ic_contrast_standard,
+                            contentDescription = null,
+                        )
+                    )
+                ContrastValue.MEDIUM.value ->
+                    ColorContrastSectionDataViewModel(
+                        Text.Resource(R.string.color_contrast_medium_title),
+                        Icon.Resource(
+                            res = R.drawable.ic_contrast_medium,
+                            contentDescription = null,
+                        )
+                    )
+                ContrastValue.HIGH.value ->
+                    ColorContrastSectionDataViewModel(
+                        Text.Resource(R.string.color_contrast_high_title),
+                        Icon.Resource(
+                            res = R.drawable.ic_contrast_high,
+                            contentDescription = null,
+                        )
+                    )
+                else -> {
+                    println("Invalid contrast value: $contrastValue")
+                    throw IllegalArgumentException("Invalid contrast value")
+                }
+            }
+        }
+
+    enum class ContrastValue(val value: Float) {
+        STANDARD(0f),
+        MEDIUM(0.5f),
+        HIGH(1f)
+    }
+
+    class Factory(
+        private val colorContrastSectionInteractor: ColorContrastSectionInteractor,
+    ) : ViewModelProvider.Factory {
+        override fun <T : ViewModel> create(modelClass: Class<T>): T {
+            @Suppress("UNCHECKED_CAST")
+            return ColorContrastSectionViewModel(
+                colorContrastSectionInteractor = colorContrastSectionInteractor,
+            )
+                as T
+        }
+    }
+}
diff --git a/tests/robotests/Android.bp b/tests/robotests/Android.bp
index 4fecaef..1377e22 100644
--- a/tests/robotests/Android.bp
+++ b/tests/robotests/Android.bp
@@ -27,6 +27,8 @@
         "truth",
         "SystemUICustomizationTestUtils",
         "ThemePickerApplicationLib",
+        "mockito-robolectric-prebuilt",
+        "mockito-kotlin2",
     ],
 
     libs: [
diff --git a/tests/robotests/src/com/android/customization/model/picker/settings/data/repository/ColorContrastSectionRepositoryTest.kt b/tests/robotests/src/com/android/customization/model/picker/settings/data/repository/ColorContrastSectionRepositoryTest.kt
new file mode 100644
index 0000000..01c0a62
--- /dev/null
+++ b/tests/robotests/src/com/android/customization/model/picker/settings/data/repository/ColorContrastSectionRepositoryTest.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.customization.model.picker.settings.data.repository
+
+import android.app.UiModeManager
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.SmallTest
+import com.android.customization.picker.settings.data.repository.ColorContrastSectionRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestCoroutineDispatcher
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.robolectric.RobolectricTestRunner
+
+@SmallTest
+@RunWith(RobolectricTestRunner::class)
+class ColorContrastSectionRepositoryTest {
+    private lateinit var underTest: ColorContrastSectionRepository
+
+    private lateinit var context: Context
+    private lateinit var bgDispatcher: TestCoroutineDispatcher
+
+    @Before
+    fun setUp() {
+        context = ApplicationProvider.getApplicationContext<Context>()
+        bgDispatcher = TestCoroutineDispatcher()
+        underTest = ColorContrastSectionRepository(context, bgDispatcher)
+    }
+
+    @Test
+    fun creationSucceeds() {
+        assertThat(underTest).isNotNull()
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    fun contrastFlowEmitsValues() = runBlockingTest {
+        val mockUiModeManager = mock(UiModeManager::class.java)
+        val contrastValues = listOf(0.5f, 0.7f, 0.8f)
+
+        // Stub the initial contrast value before the flow starts collecting
+        `when`(mockUiModeManager.contrast).thenReturn(contrastValues[0])
+
+        // Assign the mockUiModeManager to the repository's uiModeManager
+        underTest.uiModeManager = mockUiModeManager
+
+        // Create a collector for the flow
+        val flowCollector = mutableListOf<Float>()
+
+        // Start collecting values from the flow
+        val job = launch { underTest.contrast.collect { flowCollector.add(it) } }
+
+        // Capture the ContrastChangeListener
+        val listenerCaptor = argumentCaptor<UiModeManager.ContrastChangeListener>()
+        verify(mockUiModeManager).addContrastChangeListener(any(), listenerCaptor.capture())
+
+        // Simulate contrast changes after the initial value has been emitted
+        contrastValues.drop(1).forEach { newValue ->
+            listenerCaptor.firstValue.onContrastChanged(newValue)
+        }
+
+        assertThat(flowCollector).containsExactlyElementsIn(contrastValues)
+
+        job.cancel()
+    }
+
+    @After
+    fun tearDown() {
+        bgDispatcher.cleanupTestCoroutines()
+    }
+}