Make colors in MaterialTheme of M2.5 immutable

Based on the recommendation from the API council, making the Colors of
M2.5 immutable which improves the performance.
Related CL for Wear Compose M3: https://android-review.googlesource.com/c/platform/frameworks/support/+/2871294
Results from the benchmarks added
* Before the change: 2.39 ms and 1013 allocs
* With the change: 2.145 ms and 906 allocs
We improve the initialisation by around 0.2 ms and 107 allocations.

Bug: 318814129
Test: Existing tests and new benchmarks
Relnote: """
Colors is now Immutable, making individual color updates less efficient, but making more common usage of colors more efficient.

The reasoning behind this change is that the majority of apps wouldn't have updating individual colors as a main use case.

This is still possible but it will recompose more than before, in turn we significantly decrease the amount of state subscriptions through all of material code and will impact initialization and runtime cost of more standard use cases.
"""

Change-Id: I6e2d4a58603a64304263e1f548cc703c5b129bf5
diff --git a/wear/compose/compose-material/api/current.txt b/wear/compose/compose-material/api/current.txt
index 2a95d95..460b7f6 100644
--- a/wear/compose/compose-material/api/current.txt
+++ b/wear/compose/compose-material/api/current.txt
@@ -136,7 +136,7 @@
     method @androidx.compose.runtime.Composable public static void OutlinedCompactChip(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? label, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? icon, optional androidx.wear.compose.material.ChipColors colors, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape, optional androidx.wear.compose.material.ChipBorder border);
   }
 
-  @androidx.compose.runtime.Stable public final class Colors {
+  @androidx.compose.runtime.Immutable @androidx.compose.runtime.Stable public final class Colors {
     ctor public Colors(optional long primary, optional long primaryVariant, optional long secondary, optional long secondaryVariant, optional long background, optional long surface, optional long error, optional long onPrimary, optional long onSecondary, optional long onBackground, optional long onSurface, optional long onSurfaceVariant, optional long onError);
     method public androidx.wear.compose.material.Colors copy(optional long primary, optional long primaryVariant, optional long secondary, optional long secondaryVariant, optional long background, optional long surface, optional long error, optional long onPrimary, optional long onSecondary, optional long onBackground, optional long onSurface, optional long onSurfaceVariant, optional long onError);
     method public long getBackground();
diff --git a/wear/compose/compose-material/api/restricted_current.txt b/wear/compose/compose-material/api/restricted_current.txt
index 2a95d95..460b7f6 100644
--- a/wear/compose/compose-material/api/restricted_current.txt
+++ b/wear/compose/compose-material/api/restricted_current.txt
@@ -136,7 +136,7 @@
     method @androidx.compose.runtime.Composable public static void OutlinedCompactChip(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? label, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? icon, optional androidx.wear.compose.material.ChipColors colors, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape, optional androidx.wear.compose.material.ChipBorder border);
   }
 
-  @androidx.compose.runtime.Stable public final class Colors {
+  @androidx.compose.runtime.Immutable @androidx.compose.runtime.Stable public final class Colors {
     ctor public Colors(optional long primary, optional long primaryVariant, optional long secondary, optional long secondaryVariant, optional long background, optional long surface, optional long error, optional long onPrimary, optional long onSecondary, optional long onBackground, optional long onSurface, optional long onSurfaceVariant, optional long onError);
     method public androidx.wear.compose.material.Colors copy(optional long primary, optional long primaryVariant, optional long secondary, optional long secondaryVariant, optional long background, optional long surface, optional long error, optional long onPrimary, optional long onSecondary, optional long onBackground, optional long onSurface, optional long onSurfaceVariant, optional long onError);
     method public long getBackground();
diff --git a/wear/compose/compose-material/benchmark/src/androidTest/java/androidx/wear/compose/material/benchmark/ColorsBenchmark.kt b/wear/compose/compose-material/benchmark/src/androidTest/java/androidx/wear/compose/material/benchmark/ColorsBenchmark.kt
new file mode 100644
index 0000000..63e8507
--- /dev/null
+++ b/wear/compose/compose-material/benchmark/src/androidTest/java/androidx/wear/compose/material/benchmark/ColorsBenchmark.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright 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 androidx.wear.compose.material.benchmark
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.testutils.LayeredComposeTestCase
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.testutils.benchmark.benchmarkToFirstPixel
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.wear.compose.material.MaterialTheme
+import androidx.wear.compose.material.contentColorFor
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class ColorsBenchmark {
+
+    @get:Rule
+    val benchmarkRule = ComposeBenchmarkRule()
+
+    private val colorsTestCaseFactory = { ColorsTestCase() }
+
+    @Test
+    fun firstPixel() {
+        benchmarkRule.benchmarkToFirstPixel(colorsTestCaseFactory)
+    }
+}
+
+private class ColorsTestCase : LayeredComposeTestCase() {
+
+    @Composable
+    override fun MeasuredContent() {
+        MaterialTheme {
+            Column {
+                // Primary
+                Box(modifier = Modifier.size(1.dp).background(MaterialTheme.colors.primary))
+                Box(
+                    modifier = Modifier.size(1.dp)
+                        .background(
+                            MaterialTheme.colors.contentColorFor(MaterialTheme.colors.primary)
+                    )
+                )
+
+                // Secondary
+                Box(modifier = Modifier.size(1.dp).background(MaterialTheme.colors.secondary))
+                Box(
+                    modifier = Modifier.size(1.dp)
+                        .background(
+                            MaterialTheme.colors.contentColorFor(MaterialTheme.colors.secondary)
+                        )
+                )
+
+                // Background
+                Box(modifier = Modifier.size(1.dp).background(MaterialTheme.colors.background))
+                Box(
+                    modifier = Modifier.size(1.dp)
+                        .background(
+                            MaterialTheme.colors.contentColorFor(MaterialTheme.colors.background)
+                        )
+                )
+
+                // Surface
+                Box(modifier = Modifier.size(1.dp).background(MaterialTheme.colors.surface))
+                Box(
+                    modifier = Modifier.size(1.dp)
+                        .background(
+                            MaterialTheme.colors.contentColorFor(MaterialTheme.colors.surface)
+                        )
+                )
+
+                // Error
+                Box(modifier = Modifier.size(1.dp).background(MaterialTheme.colors.error))
+                Box(
+                    modifier = Modifier.size(1.dp)
+                        .background(
+                            MaterialTheme.colors.contentColorFor(MaterialTheme.colors.error)
+                        )
+                )
+            }
+        }
+    }
+
+    @Composable
+    override fun ContentWrappers(content: @Composable () -> Unit) {
+        content()
+    }
+}
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Colors.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Colors.kt
index 5e0441a..7db6c7c 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Colors.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Colors.kt
@@ -16,64 +16,30 @@
 package androidx.wear.compose.material
 
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.ReadOnlyComposable
 import androidx.compose.runtime.Stable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
 import androidx.compose.runtime.staticCompositionLocalOf
-import androidx.compose.runtime.structuralEqualityPolicy
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.takeOrElse
 
+@Immutable
 @Stable
 public class Colors(
-    primary: Color = Color(0xFFAECBFA),
-    primaryVariant: Color = Color(0xFF8AB4F8),
-    secondary: Color = Color(0xFFFDE293),
-    secondaryVariant: Color = Color(0xFF594F33),
-    background: Color = Color.Black,
-    surface: Color = Color(0xFF303133),
-    error: Color = Color(0xFFEE675C),
-    onPrimary: Color = Color(0xFF303133),
-    onSecondary: Color = Color(0xFF303133),
-    onBackground: Color = Color.White,
-    onSurface: Color = Color.White,
-    onSurfaceVariant: Color = Color(0xFFDADCE0),
-    onError: Color = Color(0xFF000000)
+    val primary: Color = Color(0xFFAECBFA),
+    val primaryVariant: Color = Color(0xFF8AB4F8),
+    val secondary: Color = Color(0xFFFDE293),
+    val secondaryVariant: Color = Color(0xFF594F33),
+    val background: Color = Color.Black,
+    val surface: Color = Color(0xFF303133),
+    val error: Color = Color(0xFFEE675C),
+    val onPrimary: Color = Color(0xFF303133),
+    val onSecondary: Color = Color(0xFF303133),
+    val onBackground: Color = Color.White,
+    val onSurface: Color = Color.White,
+    val onSurfaceVariant: Color = Color(0xFFDADCE0),
+    val onError: Color = Color(0xFF000000)
 ) {
-    public var primary: Color by mutableStateOf(primary, structuralEqualityPolicy())
-        internal set
-    public var primaryVariant: Color by mutableStateOf(primaryVariant, structuralEqualityPolicy())
-        internal set
-    public var secondary: Color by mutableStateOf(secondary, structuralEqualityPolicy())
-        internal set
-    public var secondaryVariant: Color by mutableStateOf(
-        secondaryVariant,
-        structuralEqualityPolicy()
-    )
-        internal set
-    public var background: Color by mutableStateOf(background, structuralEqualityPolicy())
-        internal set
-    public var surface: Color by mutableStateOf(surface, structuralEqualityPolicy())
-        internal set
-    public var error: Color by mutableStateOf(error, structuralEqualityPolicy())
-        internal set
-    public var onPrimary: Color by mutableStateOf(onPrimary, structuralEqualityPolicy())
-        internal set
-    public var onSecondary: Color by mutableStateOf(onSecondary, structuralEqualityPolicy())
-        internal set
-    public var onBackground: Color by mutableStateOf(onBackground, structuralEqualityPolicy())
-        internal set
-    public var onSurface: Color by mutableStateOf(onSurface, structuralEqualityPolicy())
-        internal set
-    public var onSurfaceVariant: Color by mutableStateOf(
-        onSurfaceVariant,
-        structuralEqualityPolicy()
-    )
-        internal set
-    public var onError: Color by mutableStateOf(onError, structuralEqualityPolicy())
-        internal set
 
     /**
      * Returns a copy of this Colors, optionally overriding some of the values.
@@ -180,35 +146,6 @@
     MaterialTheme.colors.contentColorFor(backgroundColor).takeOrElse { LocalContentColor.current }
 
 /**
- * Updates the internal values of the given [Colors] with values from the [other] [Colors]. This
- * allows efficiently updating a subset of [Colors], without recomposing every composable that
- * consumes values from [LocalColors].
- *
- * Because [Colors] is very wide-reaching, and used by many expensive composables in the
- * hierarchy, providing a new value to [LocalColors] causes every composable consuming
- * [LocalColors] to recompose, which is prohibitively expensive in cases such as animating one
- * color in the theme. Instead, [Colors] is internally backed by [mutableStateOf], and this
- * function mutates the internal state of [this] to match values in [other]. This means that any
- * changes will mutate the internal state of [this], and only cause composables that are reading
- * the specific changed value to recompose.
- */
-internal fun Colors.updateColorsFrom(other: Colors) {
-    primary = other.primary
-    primaryVariant = other.primaryVariant
-    secondary = other.secondary
-    secondaryVariant = other.secondaryVariant
-    background = other.background
-    surface = other.surface
-    error = other.error
-    onPrimary = other.onPrimary
-    onSecondary = other.onSecondary
-    onBackground = other.onBackground
-    onSurface = other.onSurface
-    onSurfaceVariant = other.onSurfaceVariant
-    onError = other.onError
-}
-
-/**
  * Convert given color to disabled color.
  * @param disabledContentAlpha Alpha used to represent disabled content colors.
  */
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/MaterialTheme.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/MaterialTheme.kt
index 5017f99..ed866b9 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/MaterialTheme.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/MaterialTheme.kt
@@ -20,7 +20,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.ReadOnlyComposable
-import androidx.compose.runtime.remember
 import androidx.wear.compose.foundation.LocalSwipeToDismissBackgroundScrimColor
 import androidx.wear.compose.foundation.LocalSwipeToDismissContentScrimColor
 
@@ -62,16 +61,11 @@
     shapes: Shapes = MaterialTheme.shapes,
     content: @Composable () -> Unit
 ) {
-    val rememberedColors = remember {
-        // Explicitly creating a new object here so we don't mutate the initial [colors]
-        // provided, and overwrite the values set in it.
-        colors.copy()
-    }.apply { updateColorsFrom(colors) }
     val rippleIndication = rippleOrFallbackImplementation()
-    val selectionColors = rememberTextSelectionColors(rememberedColors)
+    val selectionColors = rememberTextSelectionColors(colors)
     @Suppress("DEPRECATION_ERROR")
     CompositionLocalProvider(
-        LocalColors provides rememberedColors,
+        LocalColors provides colors,
         LocalShapes provides shapes,
         LocalTypography provides typography,
         LocalContentAlpha provides ContentAlpha.high,
@@ -79,8 +73,8 @@
         // TODO: b/304985887 - remove after one stable release
         androidx.compose.material.ripple.LocalRippleTheme provides CompatRippleTheme,
         LocalTextSelectionColors provides selectionColors,
-        LocalSwipeToDismissBackgroundScrimColor provides rememberedColors.background,
-        LocalSwipeToDismissContentScrimColor provides rememberedColors.background
+        LocalSwipeToDismissBackgroundScrimColor provides colors.background,
+        LocalSwipeToDismissContentScrimColor provides colors.background
     ) {
         ProvideTextStyle(value = typography.body1, content = content)
     }