Merge "Introduce Scale indication for TV Compose" into androidx-main
diff --git a/tv/tv-material/api/public_plus_experimental_current.txt b/tv/tv-material/api/public_plus_experimental_current.txt
index 6793ddd..8a14d13 100644
--- a/tv/tv-material/api/public_plus_experimental_current.txt
+++ b/tv/tv-material/api/public_plus_experimental_current.txt
@@ -37,44 +37,30 @@
   }
 
   @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ClickableSurfaceColor {
-    method public long getColor();
-    method public long getDisabledColor();
-    method public long getFocusedColor();
-    method public long getPressedColor();
-    property public final long color;
-    property public final long disabledColor;
-    property public final long focusedColor;
-    property public final long pressedColor;
   }
 
   @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ClickableSurfaceDefaults {
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.ClickableSurfaceColor color(optional long color, optional long focusedColor, optional long pressedColor, optional long disabledColor);
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.ClickableSurfaceColor contentColor(optional long color, optional long focusedColor, optional long pressedColor, optional long disabledColor);
     method public androidx.tv.material3.ClickableSurfaceGlow glow(optional androidx.tv.material3.Glow glow, optional androidx.tv.material3.Glow focusedGlow, optional androidx.tv.material3.Glow pressedGlow);
+    method public androidx.tv.material3.ClickableSurfaceScale scale(optional @FloatRange(from=0.0) float scale, optional @FloatRange(from=0.0) float focusedScale, optional @FloatRange(from=0.0) float pressedScale, optional @FloatRange(from=0.0) float disabledScale, optional @FloatRange(from=0.0) float focusedDisabledScale);
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.ClickableSurfaceShape shape(optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.ui.graphics.Shape focusedShape, optional androidx.compose.ui.graphics.Shape pressedShape, optional androidx.compose.ui.graphics.Shape disabledShape, optional androidx.compose.ui.graphics.Shape focusedDisabledShape);
     field public static final androidx.tv.material3.ClickableSurfaceDefaults INSTANCE;
   }
 
   @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ClickableSurfaceGlow {
-    method public androidx.tv.material3.Glow getFocusedGlow();
-    method public androidx.tv.material3.Glow getGlow();
-    method public androidx.tv.material3.Glow getPressedGlow();
-    property public final androidx.tv.material3.Glow focusedGlow;
-    property public final androidx.tv.material3.Glow glow;
-    property public final androidx.tv.material3.Glow pressedGlow;
+  }
+
+  @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ClickableSurfaceScale {
+    field public static final androidx.tv.material3.ClickableSurfaceScale.Companion Companion;
+  }
+
+  public static final class ClickableSurfaceScale.Companion {
+    method public androidx.tv.material3.ClickableSurfaceScale getNone();
+    property public final androidx.tv.material3.ClickableSurfaceScale None;
   }
 
   @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ClickableSurfaceShape {
-    method public androidx.compose.ui.graphics.Shape getDisabledShape();
-    method public androidx.compose.ui.graphics.Shape getFocusedDisabledShape();
-    method public androidx.compose.ui.graphics.Shape getFocusedShape();
-    method public androidx.compose.ui.graphics.Shape getPressedShape();
-    method public androidx.compose.ui.graphics.Shape getShape();
-    property public final androidx.compose.ui.graphics.Shape disabledShape;
-    property public final androidx.compose.ui.graphics.Shape focusedDisabledShape;
-    property public final androidx.compose.ui.graphics.Shape focusedShape;
-    property public final androidx.compose.ui.graphics.Shape pressedShape;
-    property public final androidx.compose.ui.graphics.Shape shape;
   }
 
   @androidx.compose.runtime.Stable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ColorScheme {
@@ -172,6 +158,7 @@
   }
 
   @androidx.compose.runtime.Stable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class GlowIndication implements androidx.compose.foundation.Indication {
+    ctor public GlowIndication(long color, androidx.compose.ui.graphics.Shape shape, float glowBlurRadius, float offsetX, float offsetY);
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.IndicationInstance rememberUpdatedInstance(androidx.compose.foundation.interaction.InteractionSource interactionSource);
   }
 
@@ -214,6 +201,11 @@
     method @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void MaterialTheme(optional androidx.tv.material3.ColorScheme colorScheme, optional androidx.tv.material3.Shapes shapes, optional androidx.tv.material3.Typography typography, kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
 
+  @androidx.compose.runtime.Stable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ScaleIndication implements androidx.compose.foundation.Indication {
+    ctor public ScaleIndication(float scale);
+    method @androidx.compose.runtime.Composable public androidx.compose.foundation.IndicationInstance rememberUpdatedInstance(androidx.compose.foundation.interaction.InteractionSource interactionSource);
+  }
+
   @androidx.tv.material3.ExperimentalTvMaterial3Api public sealed interface ScrollPauseHandle {
     method public void resumeAutoScroll();
   }
@@ -248,7 +240,7 @@
   }
 
   public final class SurfaceKt {
-    method @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void Surface(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional float tonalElevation, optional androidx.tv.material3.ClickableSurfaceShape shape, optional androidx.tv.material3.ClickableSurfaceColor color, optional androidx.tv.material3.ClickableSurfaceColor contentColor, optional androidx.tv.material3.ClickableSurfaceGlow glow, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void Surface(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional float tonalElevation, optional androidx.tv.material3.ClickableSurfaceShape shape, optional androidx.tv.material3.ClickableSurfaceColor color, optional androidx.tv.material3.ClickableSurfaceColor contentColor, optional androidx.tv.material3.ClickableSurfaceScale scale, optional androidx.tv.material3.ClickableSurfaceGlow glow, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.unit.Dp> getLocalAbsoluteTonalElevation();
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.unit.Dp> LocalAbsoluteTonalElevation;
   }
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/SurfaceTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/SurfaceTest.kt
index 6976d6d..b630f44 100644
--- a/tv/tv-material/src/androidTest/java/androidx/tv/material3/SurfaceTest.kt
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/SurfaceTest.kt
@@ -18,6 +18,7 @@
 
 import android.os.Build
 import androidx.annotation.RequiresApi
+import androidx.compose.foundation.background
 import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.interaction.FocusInteraction
 import androidx.compose.foundation.interaction.Interaction
@@ -34,6 +35,7 @@
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.testutils.assertContainsColor
+import androidx.compose.testutils.assertDoesNotContainColor
 import androidx.compose.testutils.assertShape
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
@@ -55,6 +57,7 @@
 import androidx.compose.ui.test.captureToImage
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onRoot
 import androidx.compose.ui.test.performKeyInput
 import androidx.compose.ui.test.performSemanticsAction
 import androidx.compose.ui.test.pressKey
@@ -432,4 +435,30 @@
             .captureToImage()
             .assertContainsColor(Color.Green)
     }
+
+    @RequiresApi(Build.VERSION_CODES.O)
+    @Test
+    fun clickableSurface_onFocus_changesScaleFactor() {
+        rule.setContent {
+            Box(
+                modifier = Modifier
+                    .background(Color.Blue)
+                    .size(50.toDp())
+            )
+            Surface(
+                onClick = {},
+                modifier = Modifier
+                    .size(50.toDp())
+                    .testTag("surface"),
+                scale = ClickableSurfaceDefaults.scale(
+                    focusedScale = 1.5f
+                )
+            ) {}
+        }
+        rule.onRoot().captureToImage().assertContainsColor(Color.Blue)
+
+        rule.onNodeWithTag("surface").performSemanticsAction(SemanticsActions.RequestFocus)
+
+        rule.onRoot().captureToImage().assertDoesNotContainColor(Color.Blue)
+    }
 }
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Indications.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Indications.kt
index 7202baa..0eb0b30 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/Indications.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Indications.kt
@@ -16,12 +16,20 @@
 
 package androidx.tv.material3
 
+import androidx.compose.animation.core.CubicBezierEasing
+import androidx.compose.animation.core.TweenSpec
 import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.tween
 import androidx.compose.foundation.Indication
 import androidx.compose.foundation.IndicationInstance
+import androidx.compose.foundation.interaction.FocusInteraction
+import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.InteractionSource
+import androidx.compose.foundation.interaction.PressInteraction
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
+import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
 import androidx.compose.ui.graphics.Color
@@ -31,6 +39,7 @@
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.drawscope.ContentDrawScope
 import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.graphics.drawscope.scale
 import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.unit.Density
@@ -44,7 +53,7 @@
  */
 @ExperimentalTvMaterial3Api
 @Stable
-class GlowIndication internal constructor(
+class GlowIndication(
     private val color: Color,
     private val shape: Shape,
     private val glowBlurRadius: Dp,
@@ -152,3 +161,60 @@
         offsetY = offsetX
     )
 }
+
+internal object ScaleIndicationTokens {
+    const val focusDuration: Int = 300
+    const val unFocusDuration: Int = 500
+    const val pressedDuration: Int = 120
+    const val releaseDuration: Int = 300
+    val enterEasing = CubicBezierEasing(0f, 0f, 0.2f, 1f)
+}
+
+/**
+ * ScaleIndication is an [Indication] that scales the composable by the provided factor. This
+ * indication by default will create a smooth animation between the state changes.
+ */
+@ExperimentalTvMaterial3Api
+@Stable
+class ScaleIndication(private val scale: Float) : Indication {
+    @Composable
+    override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance {
+        val interaction by interactionSource.interactions.collectAsState(
+            initial = FocusInteraction.Focus()
+        )
+
+        val animationSpec = defaultScaleAnimationSpec(interaction)
+
+        val animatedScale by animateFloatAsState(
+            targetValue = scale,
+            animationSpec = animationSpec
+        )
+
+        return remember(animatedScale, animationSpec) {
+            ScaleIndicationInstance(scale = animatedScale)
+        }
+    }
+
+    private fun defaultScaleAnimationSpec(interaction: Interaction): TweenSpec<Float> =
+        tween(
+            durationMillis = when (interaction) {
+                is FocusInteraction.Focus -> ScaleIndicationTokens.focusDuration
+                is FocusInteraction.Unfocus -> ScaleIndicationTokens.unFocusDuration
+                is PressInteraction.Press -> ScaleIndicationTokens.pressedDuration
+                is PressInteraction.Release -> ScaleIndicationTokens.releaseDuration
+                is PressInteraction.Cancel -> ScaleIndicationTokens.releaseDuration
+                else -> ScaleIndicationTokens.releaseDuration
+            },
+            easing = ScaleIndicationTokens.enterEasing
+        )
+}
+
+internal class ScaleIndicationInstance(
+    private val scale: Float
+) : IndicationInstance {
+    override fun ContentDrawScope.drawIndication() {
+        scale(scale) {
+            [email protected]()
+        }
+    }
+}
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Surface.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Surface.kt
index 3b7fdb4..7d2555f 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/Surface.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Surface.kt
@@ -40,7 +40,6 @@
 import androidx.compose.ui.draw.drawWithCache
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.drawOutline
 import androidx.compose.ui.graphics.graphicsLayer
@@ -54,7 +53,6 @@
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
-import androidx.compose.ui.zIndex
 import kotlinx.coroutines.launch
 
 /**
@@ -74,6 +72,7 @@
  * @param shape Defines the surface's shape.
  * @param color Color to be used on background of the Surface
  * @param contentColor The preferred content color provided by this Surface to its children.
+ * @param scale Defines size of the Surface relative to its original size.
  * @param glow Diffused shadow to be shown behind the Surface.
  * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
  * for this Surface. You can create and pass in your own remembered [MutableInteractionSource] if
@@ -92,6 +91,7 @@
     shape: ClickableSurfaceShape = ClickableSurfaceDefaults.shape(),
     color: ClickableSurfaceColor = ClickableSurfaceDefaults.color(),
     contentColor: ClickableSurfaceColor = ClickableSurfaceDefaults.contentColor(),
+    scale: ClickableSurfaceScale = ClickableSurfaceDefaults.scale(),
     glow: ClickableSurfaceGlow = ClickableSurfaceDefaults.glow(),
     interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
     content: @Composable (BoxScope.() -> Unit)
@@ -102,8 +102,11 @@
         modifier = modifier.tvClickable(
             enabled = enabled,
             onClick = onClick,
-            interactionSource = interactionSource
+            interactionSource = interactionSource,
+            value = false,
+            onValueChanged = null
         ),
+        selected = false,
         enabled = enabled,
         tonalElevation = tonalElevation,
         shape = ClickableSurfaceDefaults.shape(
@@ -124,6 +127,12 @@
             pressed = pressed,
             color = contentColor
         ),
+        scale = ClickableSurfaceDefaults.scale(
+            enabled = enabled,
+            focused = focused,
+            pressed = pressed,
+            scale = scale
+        ),
         glow = ClickableSurfaceDefaults.glow(
             enabled = enabled,
             focused = focused,
@@ -138,14 +147,15 @@
 @ExperimentalTvMaterial3Api
 @Composable
 private fun SurfaceImpl(
-    modifier: Modifier = Modifier,
-    selected: Boolean = false,
+    modifier: Modifier,
+    selected: Boolean,
     enabled: Boolean,
-    shape: Shape = RectangleShape,
+    shape: Shape,
     color: Color,
     contentColor: Color,
+    scale: Float,
     glow: Glow,
-    tonalElevation: Dp = 0.dp,
+    tonalElevation: Dp,
     interactionSource: MutableInteractionSource,
     content: @Composable (BoxScope.() -> Unit)
 ) {
@@ -178,6 +188,10 @@
             modifier = modifier
                 .indication(
                     interactionSource = interactionSource,
+                    indication = remember(scale) { ScaleIndication(scale = scale) }
+                )
+                .indication(
+                    interactionSource = interactionSource,
                     indication = rememberGlowIndication(
                         color = surfaceColorAtElevation(
                             color = glow.elevationColor,
@@ -235,9 +249,9 @@
  */
 private fun Modifier.tvClickable(
     enabled: Boolean,
-    onClick: (() -> Unit)? = null,
-    value: Boolean = false,
-    onValueChanged: ((Boolean) -> Unit)? = null,
+    onClick: (() -> Unit)?,
+    value: Boolean,
+    onValueChanged: ((Boolean) -> Unit)?,
     interactionSource: MutableInteractionSource
 ) = this
     .handleDPadEnter(
@@ -335,10 +349,10 @@
     selected: Boolean
 ): Float {
     return when {
-        enabled -> EnabledContentAlpha
         !enabled && pressed -> DisabledPressedStateAlpha
         !enabled && focused -> DisabledFocusedStateAlpha
         !enabled && selected -> DisabledSelectedStateAlpha
+        enabled -> EnabledContentAlpha
         else -> DisabledDefaultStateAlpha
     }
 }
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/SurfaceDefaults.kt b/tv/tv-material/src/main/java/androidx/tv/material3/SurfaceDefaults.kt
index 1a1ef5d..67f0697 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/SurfaceDefaults.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/SurfaceDefaults.kt
@@ -16,6 +16,7 @@
 
 package androidx.tv.material3
 
+import androidx.annotation.FloatRange
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.ReadOnlyComposable
@@ -131,6 +132,48 @@
         disabledColor = disabledColor
     )
 
+    internal fun scale(
+        enabled: Boolean,
+        focused: Boolean,
+        pressed: Boolean,
+        scale: ClickableSurfaceScale
+    ): Float {
+        return when {
+            pressed && enabled -> scale.pressedScale
+            focused && enabled -> scale.focusedScale
+            focused && !enabled -> scale.focusedDisabledScale
+            enabled -> scale.scale
+            else -> scale.disabledScale
+        }
+    }
+
+    /**
+     * Creates a [ClickableSurfaceScale] that represents the default scales used in a
+     * Surface. scales are used to modify the size of a composable in different [Interaction]
+     * states e.g. 1f (original) in default state, 1.2f (scaled up) in focused state,
+     * 0.8f (scaled down) in pressed state, etc.
+     *
+     * @param scale the scale to be used for this Surface when enabled
+     * @param focusedScale the scale to be used for this Surface when focused
+     * @param pressedScale the scale to be used for this Surface when pressed
+     * @param disabledScale the scale to be used for this Surface when disabled
+     * @param focusedDisabledScale the scale to be used for this Surface when disabled and
+     * focused
+     */
+    fun scale(
+        @FloatRange(from = 0.0) scale: Float = 1f,
+        @FloatRange(from = 0.0) focusedScale: Float = 1.1f,
+        @FloatRange(from = 0.0) pressedScale: Float = scale,
+        @FloatRange(from = 0.0) disabledScale: Float = scale,
+        @FloatRange(from = 0.0) focusedDisabledScale: Float = disabledScale
+    ) = ClickableSurfaceScale(
+        scale = scale,
+        focusedScale = focusedScale,
+        pressedScale = pressedScale,
+        disabledScale = disabledScale,
+        focusedDisabledScale = focusedDisabledScale
+    )
+
     internal fun glow(
         enabled: Boolean,
         focused: Boolean,
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/SurfaceStyles.kt b/tv/tv-material/src/main/java/androidx/tv/material3/SurfaceStyles.kt
index 60c1a7b..3cc05a5 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/SurfaceStyles.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/SurfaceStyles.kt
@@ -16,6 +16,7 @@
 
 package androidx.tv.material3
 
+import androidx.annotation.FloatRange
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.runtime.Immutable
 import androidx.compose.ui.graphics.Color
@@ -25,21 +26,15 @@
 
 /**
  * Defines [Shape] for all TV [Interaction] states of a Clickable Surface.
- * @param shape [Shape] to be applied when Clickable Surface is in the default state.
- * @param focusedShape [Shape] to be applied when Clickable Surface is focused.
- * @param pressedShape [Shape] to be applied when Clickable Surface is pressed.
- * @param disabledShape [Shape] to be applied when Clickable Surface is disabled.
- * @param focusedDisabledShape [Shape] to be applied when Clickable Surface is focused in the
- * default state.
  */
 @ExperimentalTvMaterial3Api
 @Immutable
 class ClickableSurfaceShape internal constructor(
-    val shape: Shape,
-    val focusedShape: Shape,
-    val pressedShape: Shape,
-    val disabledShape: Shape,
-    val focusedDisabledShape: Shape
+    internal val shape: Shape,
+    internal val focusedShape: Shape,
+    internal val pressedShape: Shape,
+    internal val disabledShape: Shape,
+    internal val focusedDisabledShape: Shape
 ) {
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
@@ -75,18 +70,14 @@
 
 /**
  * Defines [Color] for all TV [Interaction] states of a Clickable Surface.
- * @param color [Color] to be applied when Clickable Surface is in the default state.
- * @param focusedColor [Color] to be applied when Clickable Surface is focused.
- * @param pressedColor [Color] to be applied when Clickable Surface is pressed.
- * @param disabledColor [Color] to be applied when Clickable Surface is disabled.
  */
 @ExperimentalTvMaterial3Api
 @Immutable
 class ClickableSurfaceColor internal constructor(
-    val color: Color,
-    val focusedColor: Color,
-    val pressedColor: Color,
-    val disabledColor: Color
+    internal val color: Color,
+    internal val focusedColor: Color,
+    internal val pressedColor: Color,
+    internal val disabledColor: Color
 ) {
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
@@ -118,17 +109,73 @@
 }
 
 /**
+ * Defines the scale for all TV indication states of Surface. Note: This scale must be
+ * a non-negative float.
+ */
+@ExperimentalTvMaterial3Api
+@Immutable
+class ClickableSurfaceScale internal constructor(
+    @FloatRange(from = 0.0) internal val scale: Float,
+    @FloatRange(from = 0.0) internal val focusedScale: Float,
+    @FloatRange(from = 0.0) internal val pressedScale: Float,
+    @FloatRange(from = 0.0) internal val disabledScale: Float,
+    @FloatRange(from = 0.0) internal val focusedDisabledScale: Float
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || this::class != other::class) return false
+
+        other as ClickableSurfaceScale
+
+        if (scale != other.scale) return false
+        if (focusedScale != other.focusedScale) return false
+        if (pressedScale != other.pressedScale) return false
+        if (disabledScale != other.disabledScale) return false
+        if (focusedDisabledScale != other.focusedDisabledScale) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = scale.hashCode()
+        result = 31 * result + focusedScale.hashCode()
+        result = 31 * result + pressedScale.hashCode()
+        result = 31 * result + disabledScale.hashCode()
+        result = 31 * result + focusedDisabledScale.hashCode()
+
+        return result
+    }
+
+    override fun toString(): String {
+        return "ClickableSurfaceScale(scale=$scale, focusedScale=$focusedScale," +
+            "pressedScale=$pressedScale, disabledScale=$disabledScale, " +
+            "focusedDisabledScale=$focusedDisabledScale)"
+    }
+
+    companion object {
+        /**
+         * Signifies the absence of a scale in TV Components. Use this if you do not want to
+         * display a [ScaleIndication] in any of the Leanback TV Components.
+         */
+        val None = ClickableSurfaceScale(
+            scale = 1f,
+            focusedScale = 1f,
+            pressedScale = 1f,
+            disabledScale = 1f,
+            focusedDisabledScale = 1f
+        )
+    }
+}
+
+/**
  * Defines [Glow] for all TV states of [Surface].
- * @param glow [Glow] to be applied when [Surface] is in the default state.
- * @param focusedGlow [Glow] to be applied when [Surface] is focused.
- * @param pressedGlow [Glow] to be applied when [Surface] is pressed.
  */
 @ExperimentalTvMaterial3Api
 @Immutable
 class ClickableSurfaceGlow internal constructor(
-    val glow: Glow,
-    val focusedGlow: Glow,
-    val pressedGlow: Glow
+    internal val glow: Glow,
+    internal val focusedGlow: Glow,
+    internal val pressedGlow: Glow
 ) {
     override fun equals(other: Any?): Boolean {
         if (this === other) return true