Refactor handleProp methods out to a separate file.

Relnote: None
Test: Unit tests
Bug: 383308699
Change-Id: Id8cbc136de90b74f1eb84bb9ebafa84388a50c62
diff --git a/wear/protolayout/protolayout-renderer/build.gradle b/wear/protolayout/protolayout-renderer/build.gradle
index 5ba8d45..3fff1ce 100644
--- a/wear/protolayout/protolayout-renderer/build.gradle
+++ b/wear/protolayout/protolayout-renderer/build.gradle
@@ -26,10 +26,12 @@
 plugins {
     id("AndroidXPlugin")
     id("com.android.library")
+    id("org.jetbrains.kotlin.android")
 }
 
 dependencies {
     api(libs.jspecify)
+    api(libs.kotlinStdlib)
     annotationProcessor(libs.nullaway)
     api("androidx.annotation:annotation:1.8.1")
     api("androidx.core:core:1.7.0")
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/PropHelpers.kt b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/PropHelpers.kt
new file mode 100644
index 0000000..35ec5a4
--- /dev/null
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/PropHelpers.kt
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2025 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.protolayout.renderer.inflater
+
+import android.util.Log
+import androidx.wear.protolayout.proto.ColorProto.ColorProp
+import androidx.wear.protolayout.proto.DimensionProto.DegreesProp
+import androidx.wear.protolayout.proto.DimensionProto.DpProp
+import androidx.wear.protolayout.proto.TypesProto.BoolProp
+import androidx.wear.protolayout.proto.TypesProto.FloatProp
+import androidx.wear.protolayout.proto.TypesProto.StringProp
+import androidx.wear.protolayout.renderer.dynamicdata.ProtoLayoutDynamicDataPipeline.PipelineMaker
+import java.util.Locale
+import java.util.Optional
+import java.util.function.Consumer
+
+/** Helpers for handling Prop classes' static and dynamic values. */
+internal object PropHelpers {
+    const val TAG = "ProtolayoutPropHelpers"
+
+    /** Handles a StringProp. */
+    @JvmStatic
+    fun handleProp(
+        stringProp: StringProp,
+        locale: Locale,
+        consumer: Consumer<String>,
+        posId: String,
+        pipelineMaker: Optional<PipelineMaker>,
+    ) {
+        if (stringProp.hasDynamicValue() && pipelineMaker.isPresent()) {
+            try {
+                pipelineMaker
+                    .get()
+                    .addPipelineFor(
+                        stringProp.dynamicValue,
+                        stringProp.value,
+                        locale,
+                        posId,
+                        consumer
+                    )
+            } catch (ex: RuntimeException) {
+                Log.e(TAG, "Error building pipeline", ex)
+                consumer.accept(stringProp.value)
+            }
+        } else {
+            consumer.accept(stringProp.value)
+        }
+    }
+
+    /** Handles a DegreesProp. */
+    @JvmStatic
+    fun handleProp(
+        degreesProp: DegreesProp,
+        consumer: Consumer<Float>,
+        posId: String,
+        pipelineMaker: Optional<PipelineMaker>,
+    ) {
+        if (degreesProp.hasDynamicValue() && pipelineMaker.isPresent()) {
+            try {
+                pipelineMaker.get().addPipelineFor(degreesProp, degreesProp.value, posId, consumer)
+            } catch (ex: RuntimeException) {
+                Log.e(TAG, "Error building pipeline", ex)
+                consumer.accept(degreesProp.value)
+            }
+        } else {
+            consumer.accept(degreesProp.value)
+        }
+    }
+
+    /** Handles a DpProp. */
+    @JvmStatic
+    fun handleProp(
+        dpProp: DpProp,
+        consumer: Consumer<Float>,
+        posId: String,
+        pipelineMaker: Optional<PipelineMaker>,
+    ) = handleProp(dpProp, consumer, consumer, posId, pipelineMaker)
+
+    /** Handles a DpProp. */
+    @JvmStatic
+    fun handleProp(
+        dpProp: DpProp,
+        staticValueConsumer: Consumer<Float>,
+        dynamicValueConsumer: Consumer<Float>,
+        posId: String,
+        pipelineMaker: Optional<PipelineMaker>,
+    ) {
+        if (dpProp.hasDynamicValue() && pipelineMaker.isPresent()) {
+            try {
+                pipelineMaker
+                    .get()
+                    .addPipelineFor(dpProp, dpProp.value, posId, dynamicValueConsumer)
+            } catch (ex: RuntimeException) {
+                Log.e(TAG, "Error building pipeline", ex)
+                staticValueConsumer.accept(dpProp.value)
+            }
+        } else {
+            staticValueConsumer.accept(dpProp.value)
+        }
+    }
+
+    /** Handles a ColorProp. */
+    @JvmStatic
+    fun handleProp(
+        colorProp: ColorProp,
+        consumer: Consumer<Int>,
+        posId: String,
+        pipelineMaker: Optional<PipelineMaker>,
+    ) {
+        if (colorProp.hasDynamicValue() && pipelineMaker.isPresent()) {
+            try {
+                pipelineMaker.get().addPipelineFor(colorProp, colorProp.argb, posId, consumer)
+            } catch (ex: RuntimeException) {
+                Log.e(TAG, "Error building pipeline", ex)
+                consumer.accept(colorProp.argb)
+            }
+        } else {
+            consumer.accept(colorProp.argb)
+        }
+    }
+
+    /** Handles a BoolProp. */
+    @JvmStatic
+    fun handleProp(
+        boolProp: BoolProp,
+        consumer: Consumer<Boolean>,
+        posId: String,
+        pipelineMaker: Optional<PipelineMaker>,
+    ) {
+        if (boolProp.hasDynamicValue() && pipelineMaker.isPresent()) {
+            try {
+                pipelineMaker.get().addPipelineFor(boolProp, boolProp.value, posId, consumer)
+            } catch (ex: RuntimeException) {
+                Log.e(TAG, "Error building pipeline", ex)
+                consumer.accept(boolProp.value)
+            }
+        } else {
+            consumer.accept(boolProp.value)
+        }
+    }
+
+    /** Handles a FloatProp. */
+    @JvmStatic
+    fun handleProp(
+        floatProp: FloatProp,
+        consumer: Consumer<Float>,
+        posId: String,
+        pipelineMaker: Optional<PipelineMaker>,
+    ) = handleProp(floatProp, consumer, consumer, posId, pipelineMaker)
+
+    /** Handles a FloatProp. */
+    @JvmStatic
+    fun handleProp(
+        floatProp: FloatProp,
+        staticValueConsumer: Consumer<Float>,
+        dynamicValueconsumer: Consumer<Float>,
+        posId: String,
+        pipelineMaker: Optional<PipelineMaker>,
+    ) {
+        if (floatProp.hasDynamicValue() && pipelineMaker.isPresent()) {
+            try {
+                pipelineMaker
+                    .get()
+                    .addPipelineFor(
+                        floatProp.dynamicValue,
+                        floatProp.value,
+                        posId,
+                        dynamicValueconsumer
+                    )
+            } catch (ex: RuntimeException) {
+                Log.e(TAG, "Error building pipeline", ex)
+                staticValueConsumer.accept(floatProp.value)
+            }
+        } else {
+            staticValueConsumer.accept(floatProp.value)
+        }
+    }
+}
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
index 012a095..41612f8 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
@@ -26,6 +26,7 @@
 import static androidx.wear.protolayout.renderer.common.ProtoLayoutDiffer.FIRST_CHILD_INDEX;
 import static androidx.wear.protolayout.renderer.common.ProtoLayoutDiffer.ROOT_NODE_ID;
 import static androidx.wear.protolayout.renderer.common.ProtoLayoutDiffer.getParentNodePosId;
+import static androidx.wear.protolayout.renderer.inflater.PropHelpers.handleProp;
 
 import static com.google.common.util.concurrent.Futures.immediateFailedFuture;
 import static com.google.common.util.concurrent.Futures.immediateFuture;
@@ -105,7 +106,6 @@
 import androidx.wear.protolayout.proto.AlignmentProto.TextAlignment;
 import androidx.wear.protolayout.proto.AlignmentProto.VerticalAlignment;
 import androidx.wear.protolayout.proto.AlignmentProto.VerticalAlignmentProp;
-import androidx.wear.protolayout.proto.ColorProto.ColorProp;
 import androidx.wear.protolayout.proto.DimensionProto.AngularDimension;
 import androidx.wear.protolayout.proto.DimensionProto.ArcLineLength;
 import androidx.wear.protolayout.proto.DimensionProto.ContainerDimension;
@@ -2829,6 +2829,7 @@
 
         handleProp(
                 text.getText(),
+                mUiContext.getResources().getConfiguration().getLocales().get(0),
                 t -> {
                     // Underlines are applied using a Spannable here, rather than setting paint bits
                     // (or
@@ -4238,152 +4239,6 @@
     }
 
     /**
-     * Either yield the constant value stored in stringProp, or register for updates if it is
-     * dynamic property.
-     *
-     * <p>If both are set, this routine will yield the constant value if and only if this renderer
-     * has a dynamic pipeline (i.e. {code mDataPipeline} is non-null), otherwise it will only
-     * subscribe for dynamic updates. If the dynamic pipeline ever yields an invalid value (via
-     * {@code onStateInvalid}), then stringProp's static valid will be used instead.
-     */
-    private void handleProp(
-            StringProp stringProp,
-            Consumer<String> consumer,
-            String posId,
-            Optional<PipelineMaker> pipelineMaker) {
-        if (stringProp.hasDynamicValue() && pipelineMaker.isPresent()) {
-            try {
-                pipelineMaker
-                        .get()
-                        .addPipelineFor(
-                                stringProp.getDynamicValue(),
-                                stringProp.getValue(),
-                                mUiContext.getResources().getConfiguration().getLocales().get(0),
-                                posId,
-                                consumer);
-            } catch (RuntimeException ex) {
-                Log.e(TAG, "Error building pipeline", ex);
-                consumer.accept(stringProp.getValue());
-            }
-        } else {
-            consumer.accept(stringProp.getValue());
-        }
-    }
-
-    private void handleProp(
-            DegreesProp degreesProp,
-            Consumer<Float> consumer,
-            String posId,
-            Optional<PipelineMaker> pipelineMaker) {
-        if (degreesProp.hasDynamicValue() && pipelineMaker.isPresent()) {
-            try {
-                pipelineMaker
-                        .get()
-                        .addPipelineFor(degreesProp, degreesProp.getValue(), posId, consumer);
-            } catch (RuntimeException ex) {
-                Log.e(TAG, "Error building pipeline", ex);
-                consumer.accept(degreesProp.getValue());
-            }
-        } else {
-            consumer.accept(degreesProp.getValue());
-        }
-    }
-
-    private void handleProp(
-            DpProp dpProp,
-            Consumer<Float> consumer,
-            String posId,
-            Optional<PipelineMaker> pipelineMaker) {
-        handleProp(dpProp, consumer, consumer, posId, pipelineMaker);
-    }
-
-    private void handleProp(
-            DpProp dpProp,
-            Consumer<Float> staticValueConsumer,
-            Consumer<Float> dynamicValueConsumer,
-            String posId,
-            Optional<PipelineMaker> pipelineMaker) {
-        if (dpProp.hasDynamicValue() && pipelineMaker.isPresent()) {
-            try {
-                pipelineMaker
-                        .get()
-                        .addPipelineFor(dpProp, dpProp.getValue(), posId, dynamicValueConsumer);
-            } catch (RuntimeException ex) {
-                Log.e(TAG, "Error building pipeline", ex);
-                staticValueConsumer.accept(dpProp.getValue());
-            }
-        } else {
-            staticValueConsumer.accept(dpProp.getValue());
-        }
-    }
-
-    private void handleProp(
-            ColorProp colorProp,
-            Consumer<Integer> consumer,
-            String posId,
-            Optional<PipelineMaker> pipelineMaker) {
-        if (colorProp.hasDynamicValue() && pipelineMaker.isPresent()) {
-            try {
-                pipelineMaker.get().addPipelineFor(colorProp, colorProp.getArgb(), posId, consumer);
-            } catch (RuntimeException ex) {
-                Log.e(TAG, "Error building pipeline", ex);
-                consumer.accept(colorProp.getArgb());
-            }
-        } else {
-            consumer.accept(colorProp.getArgb());
-        }
-    }
-
-    private void handleProp(
-            BoolProp boolProp,
-            Consumer<Boolean> consumer,
-            String posId,
-            Optional<PipelineMaker> pipelineMaker) {
-        if (boolProp.hasDynamicValue() && pipelineMaker.isPresent()) {
-            try {
-                pipelineMaker.get().addPipelineFor(boolProp, boolProp.getValue(), posId, consumer);
-            } catch (RuntimeException ex) {
-                Log.e(TAG, "Error building pipeline", ex);
-                consumer.accept(boolProp.getValue());
-            }
-        } else {
-            consumer.accept(boolProp.getValue());
-        }
-    }
-
-    private void handleProp(
-            FloatProp floatProp,
-            Consumer<Float> consumer,
-            String posId,
-            Optional<PipelineMaker> pipelineMaker) {
-        handleProp(floatProp, consumer, consumer, posId, pipelineMaker);
-    }
-
-    private void handleProp(
-            FloatProp floatProp,
-            Consumer<Float> staticValueConsumer,
-            Consumer<Float> dynamicValueconsumer,
-            String posId,
-            Optional<PipelineMaker> pipelineMaker) {
-        if (floatProp.hasDynamicValue() && pipelineMaker.isPresent()) {
-            try {
-                pipelineMaker
-                        .get()
-                        .addPipelineFor(
-                                floatProp.getDynamicValue(),
-                                floatProp.getValue(),
-                                posId,
-                                dynamicValueconsumer);
-            } catch (RuntimeException ex) {
-                Log.e(TAG, "Error building pipeline", ex);
-                staticValueConsumer.accept(floatProp.getValue());
-            }
-        } else {
-            staticValueConsumer.accept(floatProp.getValue());
-        }
-    }
-
-    /**
      * Resolves the value for layout to be used in a Size Wrapper for elements containing dynamic
      * values. Returns null if no size wrapper is needed.
      */
@@ -4959,6 +4814,7 @@
         if (semantics.hasContentDescription()) {
             handleProp(
                     semantics.getContentDescription(),
+                    mUiContext.getResources().getConfiguration().getLocales().get(0),
                     view::setContentDescription,
                     posId,
                     pipelineMaker);
@@ -4970,6 +4826,7 @@
         if (semantics.hasStateDescription()) {
             handleProp(
                     semantics.getStateDescription(),
+                    mUiContext.getResources().getConfiguration().getLocales().get(0),
                     (state) -> ViewCompat.setStateDescription(view, state),
                     posId,
                     pipelineMaker);