Clean up TypeConverters

Bug: 275590120
Test: Unit tests
Change-Id: I097ec3516840034334f5d62167b1479e43db2e2c
diff --git a/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateCall.kt b/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateCall.kt
index 5f6f6dc..ea5fa95 100644
--- a/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateCall.kt
+++ b/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateCall.kt
@@ -48,7 +48,7 @@
             "call.callFormat",
             { property -> Optional.ofNullable(property.callFormat) },
             CreateCall.Argument.Builder::setCallFormat,
-            TypeConverters::toCallFormat,
+            TypeConverters.CALL_FORMAT_PARAM_VALUE_CONVERTER,
             TypeConverters::toEntity
         )
         .bindRepeatedParameter(
diff --git a/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateMessage.kt b/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateMessage.kt
index bee768f..bf78f96 100644
--- a/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateMessage.kt
+++ b/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateMessage.kt
@@ -56,7 +56,7 @@
             "message.text",
             { property -> Optional.ofNullable(property.messageText) },
             CreateMessage.Argument.Builder::setMessageText,
-            TypeConverters::toStringValue,
+            TypeConverters.STRING_PARAM_VALUE_CONVERTER,
             PropertyConverter::stringValueToProto
         )
         .bindOptionalOutput(
diff --git a/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/ParticipantValue.kt b/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/ParticipantValue.kt
index 5e3dcf4..9ef64a1 100644
--- a/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/ParticipantValue.kt
+++ b/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/ParticipantValue.kt
@@ -46,8 +46,6 @@
             )
             .build()
 
-        internal val FROM_PARAM_VALUE = ParamValueConverter {
-            TYPE_SPEC.fromStruct(it.getStructValue())
-        }
+        internal val FROM_PARAM_VALUE = ParamValueConverter.of(TYPE_SPEC)
     }
 }
diff --git a/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/RecipientValue.kt b/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/RecipientValue.kt
index 637f6b5..a0a3ae1 100644
--- a/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/RecipientValue.kt
+++ b/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/RecipientValue.kt
@@ -47,8 +47,6 @@
             )
             .build()
 
-        internal val FROM_PARAM_VALUE = ParamValueConverter {
-            TYPE_SPEC.fromStruct(it.getStructValue())
-        }
+        internal val FROM_PARAM_VALUE = ParamValueConverter.of(TYPE_SPEC)
     }
 }
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/ParamValueConverter.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/ParamValueConverter.java
deleted file mode 100644
index 21d5e41..0000000
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/ParamValueConverter.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2023 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.appactions.interaction.capabilities.core.impl.converters;
-
-import androidx.annotation.NonNull;
-import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException;
-import androidx.appactions.interaction.proto.ParamValue;
-
-/**
- * A function that converts a single ParamValue to some generic object visible to the SDK user.
- *
- * @param <T>
- */
-@FunctionalInterface
-public interface ParamValueConverter<T> {
-    @NonNull
-    T convert(@NonNull ParamValue paramValue) throws StructConversionException;
-}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/ParamValueConverter.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/ParamValueConverter.kt
new file mode 100644
index 0000000..ca1bd43
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/ParamValueConverter.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2023 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.appactions.interaction.capabilities.core.impl.converters
+
+import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException
+import androidx.appactions.interaction.proto.ParamValue
+
+/**
+ * Converts to and from ParamValue (Assistant protocol for a slot).
+ *
+ * @param T the type of slot. This will usually be a type or property from the BIT library.
+ */
+interface ParamValueConverter<T> {
+
+    /** Convert ParamValue sent from Assistant (e.g. BII Argument). */
+    @Throws(StructConversionException::class)
+    fun fromParamValue(paramValue: ParamValue): T
+
+    /** Convert to ParamValue to send to Assistant (e.g. BIO). */
+    fun toParamValue(value: T): ParamValue
+
+    companion object {
+        fun <T> of(typeSpec: TypeSpec<T>) = object : ParamValueConverter<T> {
+
+            override fun fromParamValue(paramValue: ParamValue): T {
+                return typeSpec.fromStruct(paramValue.structValue)
+            }
+
+            override fun toParamValue(value: T): ParamValue {
+                val builder = ParamValue.newBuilder()
+                    .setStructValue(typeSpec.toStruct(value))
+                typeSpec.getIdentifier(value)?.let { builder.setIdentifier(it) }
+                return builder.build()
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/SlotTypeConverter.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/SlotTypeConverter.java
index d9ae329..d1af888 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/SlotTypeConverter.java
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/SlotTypeConverter.java
@@ -37,7 +37,7 @@
         return (paramValues) -> {
             List<T> results = new ArrayList<>();
             for (ParamValue paramValue : paramValues) {
-                results.add(singularConverter.convert(paramValue));
+                results.add(singularConverter.fromParamValue(paramValue));
             }
             return results;
         };
@@ -47,7 +47,7 @@
     @NonNull
     static <T> SlotTypeConverter<T> ofSingular(
             @NonNull ParamValueConverter<T> singularConverter) {
-        return (paramValues) -> singularConverter.convert(paramValues.get(0));
+        return (paramValues) -> singularConverter.fromParamValue(paramValues.get(0));
     }
 
     T convert(@NonNull List<ParamValue> protoList) throws StructConversionException;
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeConverters.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeConverters.java
index 5b542b0..9ebbd8e 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeConverters.java
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeConverters.java
@@ -17,8 +17,6 @@
 package androidx.appactions.interaction.capabilities.core.impl.converters;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appactions.interaction.capabilities.core.ExecutionResult;
 import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException;
 import androidx.appactions.interaction.capabilities.core.values.Alarm;
 import androidx.appactions.interaction.capabilities.core.values.CalendarEvent;
@@ -39,7 +37,6 @@
 import androidx.appactions.interaction.capabilities.core.values.properties.Participant;
 import androidx.appactions.interaction.capabilities.core.values.properties.Recipient;
 import androidx.appactions.interaction.proto.Entity;
-import androidx.appactions.interaction.proto.FulfillmentResponse;
 import androidx.appactions.interaction.proto.ParamValue;
 import androidx.appactions.interaction.protobuf.ListValue;
 import androidx.appactions.interaction.protobuf.Struct;
@@ -181,145 +178,230 @@
     private static final String FIELD_NAME_CALL_FORMAT = "callFormat";
     private static final String FIELD_NAME_PARTICIPANT = "participant";
     private static final String FIELD_NAME_TYPE_CALL = "Call";
-    private static final String FIELD_NAME_TYPE_PERSON = "Person";
     private static final String FIELD_NAME_TYPE_MESSAGE = "Message";
     private static final String FIELD_NAME_RECIPIENT = "recipient";
     private static final String FIELD_NAME_TEXT = "text";
 
+    public static final ParamValueConverter<Integer> INTEGER_PARAM_VALUE_CONVERTER =
+            new ParamValueConverter<Integer>() {
+                @NonNull
+                @Override
+                public ParamValue toParamValue(Integer value) {
+                    return ParamValue.newBuilder().setNumberValue(value * 1.0).build();
+                }
+
+                @Override
+                public Integer fromParamValue(@NonNull ParamValue paramValue)
+                        throws StructConversionException {
+                    if (paramValue.hasNumberValue()) {
+                        return (int) paramValue.getNumberValue();
+                    }
+                    throw new StructConversionException(
+                            "Cannot parse integer because number_value"
+                                    + " is missing from ParamValue.");
+                }
+            };
+    public static final ParamValueConverter<Boolean> BOOLEAN_PARAM_VALUE_CONVERTER =
+            new ParamValueConverter<Boolean>() {
+                @NonNull
+                @Override
+                public ParamValue toParamValue(Boolean value) {
+                    return ParamValue.newBuilder().setBoolValue(value).build();
+                }
+
+                @Override
+                public Boolean fromParamValue(@NonNull ParamValue paramValue)
+                        throws StructConversionException {
+                    if (paramValue.hasBoolValue()) {
+                        return paramValue.getBoolValue();
+                    }
+                    throw new StructConversionException(
+                            "Cannot parse boolean because bool_value is missing from ParamValue.");
+                }
+            };
+    public static final ParamValueConverter<EntityValue> ENTITY_PARAM_VALUE_CONVERTER =
+            new ParamValueConverter<EntityValue>() {
+                @NonNull
+                @Override
+                public ParamValue toParamValue(EntityValue value) {
+                    throw new IllegalStateException("EntityValue should never be sent back to "
+                            + "Assistant.");
+                }
+
+                @Override
+                public EntityValue fromParamValue(@NonNull ParamValue paramValue) {
+                    EntityValue.Builder value = EntityValue.newBuilder();
+                    if (paramValue.hasIdentifier()) {
+                        value.setId(paramValue.getIdentifier());
+                    }
+                    value.setValue(paramValue.getStringValue());
+                    return value.build();
+                }
+            };
+    public static final ParamValueConverter<String> STRING_PARAM_VALUE_CONVERTER =
+            new ParamValueConverter<String>() {
+                @NonNull
+                @Override
+                public ParamValue toParamValue(String value) {
+                    return ParamValue.newBuilder().setStringValue(value).build();
+                }
+
+                @Override
+                public String fromParamValue(@NonNull ParamValue paramValue) {
+                    if (paramValue.hasIdentifier()) {
+                        return paramValue.getIdentifier();
+                    }
+                    return paramValue.getStringValue();
+                }
+            };
+    public static final ParamValueConverter<LocalDate> LOCAL_DATE_PARAM_VALUE_CONVERTER =
+            new ParamValueConverter<LocalDate>() {
+                @NonNull
+                @Override
+                public ParamValue toParamValue(LocalDate value) {
+                    // TODO(b/275456249): Implement backwards conversion.
+                    return ParamValue.getDefaultInstance();
+                }
+
+                @Override
+                public LocalDate fromParamValue(@NonNull ParamValue paramValue)
+                        throws StructConversionException {
+                    if (paramValue.hasStringValue()) {
+                        try {
+                            return LocalDate.parse(paramValue.getStringValue());
+                        } catch (DateTimeParseException e) {
+                            throw new StructConversionException(
+                                    "Failed to parse ISO 8601 string to LocalDate", e);
+                        }
+                    }
+                    throw new StructConversionException(
+                            "Cannot parse date because string_value is missing from ParamValue.");
+                }
+            };
+    public static final ParamValueConverter<LocalTime> LOCAL_TIME_PARAM_VALUE_CONVERTER =
+            new ParamValueConverter<LocalTime>() {
+                @NonNull
+                @Override
+                public ParamValue toParamValue(LocalTime value) {
+                    // TODO(b/275456249)): Implement backwards conversion.
+                    return ParamValue.getDefaultInstance();
+                }
+
+                @Override
+                public LocalTime fromParamValue(@NonNull ParamValue paramValue)
+                        throws StructConversionException {
+                    if (paramValue.hasStringValue()) {
+                        try {
+                            return LocalTime.parse(paramValue.getStringValue());
+                        } catch (DateTimeParseException e) {
+                            throw new StructConversionException(
+                                    "Failed to parse ISO 8601 string to LocalTime", e);
+                        }
+                    }
+                    throw new StructConversionException(
+                            "Cannot parse time because string_value is missing from ParamValue.");
+                }
+            };
+    public static final ParamValueConverter<ZoneId> ZONE_ID_PARAM_VALUE_CONVERTER =
+            new ParamValueConverter<ZoneId>() {
+                @NonNull
+                @Override
+                public ParamValue toParamValue(ZoneId value) {
+                    // TODO(b/275456249)): Implement backwards conversion.
+                    return ParamValue.getDefaultInstance();
+                }
+
+                @Override
+                public ZoneId fromParamValue(@NonNull ParamValue paramValue)
+                        throws StructConversionException {
+                    if (paramValue.hasStringValue()) {
+                        try {
+                            return ZoneId.of(paramValue.getStringValue());
+                        } catch (DateTimeParseException e) {
+                            throw new StructConversionException(
+                                    "Failed to parse ISO 8601 string to ZoneId", e);
+                        }
+                    }
+                    throw new StructConversionException(
+                            "Cannot parse ZoneId because string_value is missing from ParamValue.");
+                }
+            };
+    public static final ParamValueConverter<ZonedDateTime> ZONED_DATETIME_PARAM_VALUE_CONVERTER =
+            new ParamValueConverter<ZonedDateTime>() {
+                @NonNull
+                @Override
+                public ParamValue toParamValue(ZonedDateTime value) {
+                    // TODO(b/275456249)): Implement backwards conversion.
+                    return ParamValue.getDefaultInstance();
+                }
+
+                @Override
+                public ZonedDateTime fromParamValue(@NonNull ParamValue paramValue)
+                        throws StructConversionException {
+                    if (paramValue.hasStringValue()) {
+                        try {
+                            return ZonedDateTime.parse(paramValue.getStringValue());
+                        } catch (DateTimeParseException e) {
+                            throw new StructConversionException(
+                                    "Failed to parse ISO 8601 string to ZonedDateTime", e);
+                        }
+                    }
+                    throw new StructConversionException(
+                            "Cannot parse datetime because string_value"
+                                    + " is missing from ParamValue.");
+                }
+            };
+    public static final ParamValueConverter<Duration> DURATION_PARAM_VALUE_CONVERTER =
+            new ParamValueConverter<Duration>() {
+                @NonNull
+                @Override
+                public ParamValue toParamValue(Duration value) {
+                    // TODO(b/275456249)): Implement backwards conversion.
+                    return ParamValue.getDefaultInstance();
+                }
+
+                @Override
+                public Duration fromParamValue(@NonNull ParamValue paramValue)
+                        throws StructConversionException {
+                    if (!paramValue.hasStringValue()) {
+                        throw new StructConversionException(
+                                "Cannot parse duration because string_value"
+                                        + " is missing from ParamValue.");
+                    }
+                    try {
+                        return Duration.parse(paramValue.getStringValue());
+                    } catch (DateTimeParseException e) {
+                        throw new StructConversionException(
+                                "Failed to parse ISO 8601 string to Duration", e);
+                    }
+                }
+            };
+    public static final ParamValueConverter<Call.CallFormat> CALL_FORMAT_PARAM_VALUE_CONVERTER =
+            new ParamValueConverter<Call.CallFormat>() {
+                @NonNull
+                @Override
+                public ParamValue toParamValue(Call.CallFormat value) {
+                    // TODO(b/275456249)): Implement backwards conversion.
+                    return ParamValue.getDefaultInstance();
+                }
+
+                @Override
+                public Call.CallFormat fromParamValue(@NonNull ParamValue paramValue)
+                        throws StructConversionException {
+                    String identifier = paramValue.getIdentifier();
+                    if (identifier.equals(Call.CallFormat.AUDIO.toString())) {
+                        return Call.CallFormat.AUDIO;
+                    } else if (identifier.equals(Call.CallFormat.VIDEO.toString())) {
+                        return Call.CallFormat.VIDEO;
+                    }
+                    throw new StructConversionException(
+                            String.format("Unknown enum format '%s'.", identifier));
+                }
+            };
+
     private TypeConverters() {}
 
     /**
-     * @param paramValue
-     * @return
-     */
-    @NonNull
-    public static Call.CallFormat toCallFormat(@NonNull ParamValue paramValue)
-            throws StructConversionException {
-        String identifier = paramValue.getIdentifier();
-        if (identifier.equals(Call.CallFormat.AUDIO.toString())) {
-            return Call.CallFormat.AUDIO;
-        } else if (identifier.equals(Call.CallFormat.VIDEO.toString())) {
-            return Call.CallFormat.VIDEO;
-        }
-        throw new StructConversionException(String.format("Unknown enum format '%s'.", identifier));
-    }
-
-    /**
-     * @param paramValue
-     * @return
-     */
-    @NonNull
-    public static EntityValue toEntityValue(@NonNull ParamValue paramValue) {
-        EntityValue.Builder value = EntityValue.newBuilder();
-        if (paramValue.hasIdentifier()) {
-            value.setId(paramValue.getIdentifier());
-        }
-        value.setValue(paramValue.getStringValue());
-        return value.build();
-    }
-
-    /**
-     * @param paramValue
-     * @return
-     */
-    public static int toIntegerValue(@NonNull ParamValue paramValue) {
-        return (int) paramValue.getNumberValue();
-    }
-
-    /**
-     * Converts a ParamValue to a Boolean object.
-     *
-     * @param paramValue
-     * @return
-     * @throws StructConversionException
-     */
-    @NonNull
-    public static Boolean toBooleanValue(@NonNull ParamValue paramValue)
-            throws StructConversionException {
-        if (paramValue.hasBoolValue()) {
-            return paramValue.getBoolValue();
-        }
-
-        throw new StructConversionException(
-                "Cannot parse boolean because bool_value is missing from ParamValue.");
-    }
-
-    /**
-     * @param paramValue
-     * @return
-     * @throws StructConversionException
-     */
-    @NonNull
-    public static LocalDate toLocalDate(@NonNull ParamValue paramValue)
-            throws StructConversionException {
-        if (paramValue.hasStringValue()) {
-            try {
-                return LocalDate.parse(paramValue.getStringValue());
-            } catch (DateTimeParseException e) {
-                throw new StructConversionException(
-                        "Failed to parse ISO 8601 string to LocalDate", e);
-            }
-        }
-        throw new StructConversionException(
-                "Cannot parse date because string_value is missing from ParamValue.");
-    }
-
-    /**
-     * @param paramValue
-     * @return
-     * @throws StructConversionException
-     */
-    @NonNull
-    public static LocalTime toLocalTime(@NonNull ParamValue paramValue)
-            throws StructConversionException {
-        if (paramValue.hasStringValue()) {
-            try {
-                return LocalTime.parse(paramValue.getStringValue());
-            } catch (DateTimeParseException e) {
-                throw new StructConversionException(
-                        "Failed to parse ISO 8601 string to LocalTime", e);
-            }
-        }
-        throw new StructConversionException(
-                "Cannot parse time because string_value is missing from ParamValue.");
-    }
-
-    /**
-     * @param paramValue
-     * @return
-     * @throws StructConversionException
-     */
-    @NonNull
-    public static ZoneId toZoneId(@NonNull ParamValue paramValue) throws StructConversionException {
-        if (paramValue.hasStringValue()) {
-            try {
-                return ZoneId.of(paramValue.getStringValue());
-            } catch (DateTimeParseException e) {
-                throw new StructConversionException("Failed to parse ISO 8601 string to ZoneId", e);
-            }
-        }
-        throw new StructConversionException(
-                "Cannot parse ZoneId because string_value is missing from ParamValue.");
-    }
-
-    /**
-     * Gets String value for a string property.
-     *
-     * <p>If identifier is present, it's the String value, otherwise it is {@code
-     * paramValue.getStringValue()}
-     *
-     * @param paramValue
-     * @return
-     */
-    @NonNull
-    public static String toStringValue(@NonNull ParamValue paramValue) {
-        if (paramValue.hasIdentifier()) {
-            return paramValue.getIdentifier();
-        }
-        return paramValue.getStringValue();
-    }
-
-    /**
      * @param entityValue
      * @return
      */
@@ -371,118 +453,6 @@
     }
 
     /**
-     * Converts a ParamValue to a single ItemList object.
-     *
-     * @param paramValue
-     * @return
-     * @throws StructConversionException
-     */
-    @NonNull
-    public static ItemList toItemList(@NonNull ParamValue paramValue)
-            throws StructConversionException {
-        return ITEM_LIST_TYPE_SPEC.fromStruct(paramValue.getStructValue());
-    }
-
-    /**
-     * Converts a ParamValue to a single ListItem object.
-     *
-     * @param paramValue
-     * @return
-     * @throws StructConversionException
-     */
-    @NonNull
-    public static ListItem toListItem(@NonNull ParamValue paramValue)
-            throws StructConversionException {
-        return LIST_ITEM_TYPE_SPEC.fromStruct(paramValue.getStructValue());
-    }
-
-    /**
-     * Converts a ParamValue to a single Order object.
-     *
-     * @param paramValue
-     * @return
-     * @throws StructConversionException
-     */
-    @NonNull
-    public static Order toOrder(@NonNull ParamValue paramValue) throws StructConversionException {
-        return ORDER_TYPE_SPEC.fromStruct(paramValue.getStructValue());
-    }
-
-    /**
-     * Converts a ParamValue to a Timer object.
-     *
-     * @param paramValue
-     * @return
-     * @throws StructConversionException
-     */
-    @NonNull
-    public static Timer toTimer(@NonNull ParamValue paramValue) throws StructConversionException {
-        return TIMER_TYPE_SPEC.fromStruct(paramValue.getStructValue());
-    }
-
-    /**
-     * Converts a ParamValue to a single Alarm object.
-     *
-     * @param paramValue
-     * @return
-     */
-    @NonNull
-    public static Alarm toAssistantAlarm(@NonNull ParamValue paramValue) {
-        return Alarm.newBuilder().setId(paramValue.getIdentifier()).build();
-    }
-
-    /**
-     * @param executionResult
-     * @return
-     */
-    @NonNull
-    public static FulfillmentResponse toFulfillmentResponseProto(
-            @NonNull ExecutionResult<Void> executionResult) {
-        return FulfillmentResponse.newBuilder()
-                .setStartDictation(executionResult.getStartDictation())
-                .build();
-    }
-
-    /**
-     * @param paramValue
-     * @return
-     * @throws StructConversionException
-     */
-    @NonNull
-    public static ZonedDateTime toZonedDateTime(@NonNull ParamValue paramValue)
-            throws StructConversionException {
-        if (paramValue.hasStringValue()) {
-            try {
-                return ZonedDateTime.parse(paramValue.getStringValue());
-            } catch (DateTimeParseException e) {
-                throw new StructConversionException(
-                        "Failed to parse ISO 8601 string to ZonedDateTime", e);
-            }
-        }
-        throw new StructConversionException(
-                "Cannot parse datetime because string_value is missing from ParamValue.");
-    }
-
-    /**
-     * @param paramValue
-     * @return
-     * @throws StructConversionException
-     */
-    @NonNull
-    public static Duration toDuration(@NonNull ParamValue paramValue)
-            throws StructConversionException {
-        if (!paramValue.hasStringValue()) {
-            throw new StructConversionException(
-                    "Cannot parse duration because string_value is missing from ParamValue.");
-        }
-        try {
-            return Duration.parse(paramValue.getStringValue());
-        } catch (DateTimeParseException e) {
-            throw new StructConversionException("Failed to parse ISO 8601 string to Duration", e);
-        }
-    }
-
-    /**
      * @param nestedTypeSpec
      * @param <T>
      * @return
@@ -502,26 +472,6 @@
                 .build();
     }
 
-    /** Converts a ParamValue to a single Participant object. */
-    @NonNull
-    public static Participant toParticipant(@NonNull ParamValue paramValue)
-            throws StructConversionException {
-        if (FIELD_NAME_TYPE_PERSON.equals(getStructType(paramValue.getStructValue()))) {
-            return new Participant(PERSON_TYPE_SPEC.fromStruct(paramValue.getStructValue()));
-        }
-        throw new StructConversionException("The type is not expected.");
-    }
-
-    /** Converts a ParamValue to a single Recipient object. */
-    @NonNull
-    public static Recipient toRecipient(@NonNull ParamValue paramValue)
-            throws StructConversionException {
-        if (FIELD_NAME_TYPE_PERSON.equals(getStructType(paramValue.getStructValue()))) {
-            return new Recipient(PERSON_TYPE_SPEC.fromStruct(paramValue.getStructValue()));
-        }
-        throw new StructConversionException("The type is not expected.");
-    }
-
     /** Given some class with a corresponding TypeSpec, create a SearchActionConverter instance. */
     @NonNull
     public static <T> SearchActionConverter<T> createSearchActionConverter(
@@ -530,89 +480,6 @@
         return (paramValue) -> typeSpec.fromStruct(paramValue.getStructValue());
     }
 
-    /** Converts a string to a ParamValue. */
-    @NonNull
-    public static ParamValue toParamValue(@NonNull String value) {
-        return ParamValue.newBuilder().setStringValue(value).build();
-    }
-
-    /** Converts an EntityValue to a ParamValue. */
-    @NonNull
-    public static ParamValue toParamValue(@NonNull EntityValue value) {
-        ParamValue.Builder builder = ParamValue.newBuilder().setStringValue(value.getValue());
-        value.getId().ifPresent(builder::setIdentifier);
-        return builder.build();
-    }
-
-    /** Converts a ItemList to a ParamValue. */
-    @NonNull
-    public static ParamValue toParamValue(@NonNull ItemList value) {
-        ParamValue.Builder builder =
-                ParamValue.newBuilder().setStructValue(ITEM_LIST_TYPE_SPEC.toStruct(value));
-        value.getId().ifPresent(builder::setIdentifier);
-        return builder.build();
-    }
-
-    /** Converts a ListItem to a ParamValue. */
-    @NonNull
-    public static ParamValue toParamValue(@NonNull ListItem value) {
-        ParamValue.Builder builder =
-                ParamValue.newBuilder().setStructValue(LIST_ITEM_TYPE_SPEC.toStruct(value));
-        value.getId().ifPresent(builder::setIdentifier);
-        return builder.build();
-    }
-
-    /** Converts an Order to a ParamValue. */
-    @NonNull
-    public static ParamValue toParamValue(@NonNull Order value) {
-        ParamValue.Builder builder =
-                ParamValue.newBuilder().setStructValue(ORDER_TYPE_SPEC.toStruct(value));
-        value.getId().ifPresent(builder::setIdentifier);
-        return builder.build();
-    }
-
-    /** Converts an Alarm to a ParamValue. */
-    @NonNull
-    public static ParamValue toParamValue(@NonNull Alarm value) {
-        ParamValue.Builder builder =
-                ParamValue.newBuilder().setStructValue(ALARM_TYPE_SPEC.toStruct(value));
-        value.getId().ifPresent(builder::setIdentifier);
-        return builder.build();
-    }
-
-    /** Converts an SafetyCheck to a ParamValue. */
-    @NonNull
-    public static ParamValue toParamValue(@NonNull SafetyCheck value) {
-        ParamValue.Builder builder =
-                ParamValue.newBuilder().setStructValue(SAFETY_CHECK_TYPE_SPEC.toStruct(value));
-        value.getId().ifPresent(builder::setIdentifier);
-        return builder.build();
-    }
-
-    /** Converts a Participant to a ParamValue. */
-    @NonNull
-    public static ParamValue toParamValue(@NonNull Participant value) {
-        ParamValue.Builder builder =
-                ParamValue.newBuilder().setStructValue(PARTICIPANT_TYPE_SPEC.toStruct(value));
-        @Nullable String identifier = PARTICIPANT_TYPE_SPEC.getIdentifier(value);
-        if (identifier != null) {
-            builder.setIdentifier(identifier);
-        }
-        return builder.build();
-    }
-
-    /** Converts a Recipient to a ParamValue. */
-    @NonNull
-    public static ParamValue toParamValue(@NonNull Recipient value) {
-        ParamValue.Builder builder =
-                ParamValue.newBuilder().setStructValue(RECIPIENT_TYPE_SPEC.toStruct(value));
-        @Nullable String identifier = RECIPIENT_TYPE_SPEC.getIdentifier(value);
-        if (identifier != null) {
-            builder.setIdentifier(identifier);
-        }
-        return builder.build();
-    }
-
     /** Converts a Call to a ParamValue. */
     @NonNull
     public static ParamValue toParamValue(@NonNull Call value) {
@@ -677,13 +544,4 @@
         value.getId().ifPresent(builder::setIdentifier);
         return builder.setStructValue(Struct.newBuilder().putAllFields(fieldsMap).build()).build();
     }
-
-    static String getStructType(Struct struct) throws StructConversionException {
-        Map<String, Value> fieldsMap = struct.getFieldsMap();
-        if (!fieldsMap.containsKey(FIELD_NAME_TYPE)
-                || fieldsMap.get(FIELD_NAME_TYPE).getStringValue().isEmpty()) {
-            throw new StructConversionException("There is no type specified.");
-        }
-        return fieldsMap.get(FIELD_NAME_TYPE).getStringValue();
-    }
 }
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilityTest.kt b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilityTest.kt
index 6d6ddf2..c68b8e1 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilityTest.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilityTest.kt
@@ -200,13 +200,13 @@
                     "optionalString",
                     Property::optionalStringField,
                     Argument.Builder::setOptionalStringField,
-                    TypeConverters::toStringValue,
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER,
                     PropertyConverter::stringValueToProto
                 )
                 .bindOptionalOutput(
                     "optionalStringOutput",
                     Output::optionalStringField,
-                    TypeConverters::toParamValue,
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER::toParamValue,
                 )
                 .build()
     }
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeConvertersTest.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeConvertersTest.java
index d0f5b52..dbff3ac 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeConvertersTest.java
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeConvertersTest.java
@@ -16,16 +16,21 @@
 
 package androidx.appactions.interaction.capabilities.core.impl.converters;
 
+import static androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters.BOOLEAN_PARAM_VALUE_CONVERTER;
+import static androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters.INTEGER_PARAM_VALUE_CONVERTER;
 import static androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters.ITEM_LIST_TYPE_SPEC;
 import static androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters.LIST_ITEM_TYPE_SPEC;
 import static androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters.ORDER_TYPE_SPEC;
+import static androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters.PARTICIPANT_TYPE_SPEC;
+import static androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters.RECIPIENT_TYPE_SPEC;
+import static androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters.SAFETY_CHECK_TYPE_SPEC;
+import static androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters.TIMER_TYPE_SPEC;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertThrows;
 
 import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException;
-import androidx.appactions.interaction.capabilities.core.values.Alarm;
 import androidx.appactions.interaction.capabilities.core.values.CalendarEvent;
 import androidx.appactions.interaction.capabilities.core.values.Call;
 import androidx.appactions.interaction.capabilities.core.values.EntityValue;
@@ -279,18 +284,24 @@
                                 .setStringValue("string-val")
                                 .build());
 
-        assertThat(SlotTypeConverter.ofSingular(TypeConverters::toEntityValue).convert(input))
+        assertThat(
+                        SlotTypeConverter.ofSingular(TypeConverters.ENTITY_PARAM_VALUE_CONVERTER)
+                                .convert(input))
                 .isEqualTo(
                         EntityValue.newBuilder().setId("entity-id").setValue("string-val").build());
     }
 
     @Test
     public void toIntegerValue() throws Exception {
+        ParamValue paramValue = ParamValue.newBuilder().setNumberValue(5).build();
         List<ParamValue> input =
-                Collections.singletonList(ParamValue.newBuilder().setNumberValue(5).build());
+                Collections.singletonList(paramValue);
 
-        assertThat(SlotTypeConverter.ofSingular(TypeConverters::toIntegerValue).convert(input))
+        assertThat(SlotTypeConverter.ofSingular(INTEGER_PARAM_VALUE_CONVERTER).convert(input))
                 .isEqualTo(5);
+
+        assertThat(INTEGER_PARAM_VALUE_CONVERTER.toParamValue(5)).isEqualTo(paramValue);
+        assertThat(INTEGER_PARAM_VALUE_CONVERTER.fromParamValue(paramValue)).isEqualTo(5);
     }
 
     @Test
@@ -299,7 +310,9 @@
                 Collections.singletonList(
                         ParamValue.newBuilder().setStringValue("hello world").build());
 
-        assertThat(SlotTypeConverter.ofSingular(TypeConverters::toStringValue).convert(input))
+        assertThat(
+                        SlotTypeConverter.ofSingular(TypeConverters.STRING_PARAM_VALUE_CONVERTER)
+                                .convert(input))
                 .isEqualTo("hello world");
     }
 
@@ -312,25 +325,18 @@
                                 .setStringValue("hello world")
                                 .build());
 
-        assertThat(SlotTypeConverter.ofSingular(TypeConverters::toStringValue).convert(input))
+        assertThat(
+                        SlotTypeConverter.ofSingular(TypeConverters.STRING_PARAM_VALUE_CONVERTER)
+                                .convert(input))
                 .isEqualTo("id1");
     }
 
     @Test
-    public void toStringValue_fromSingleParam() {
+    public void toStringValue_fromSingleParam() throws Exception {
         ParamValue input = ParamValue.newBuilder().setStringValue("hello world").build();
 
-        assertThat(TypeConverters.toStringValue(input)).isEqualTo("hello world");
-    }
-
-    @Test
-    public void alarm_conversions_matchesExpected() throws Exception {
-        Alarm alarm = Alarm.newBuilder().setId("id").build();
-
-        assertThat(
-                        TypeConverters.toAssistantAlarm(
-                                ParamValue.newBuilder().setIdentifier("id").build()))
-                .isEqualTo(alarm);
+        assertThat(TypeConverters.STRING_PARAM_VALUE_CONVERTER.fromParamValue(input))
+                .isEqualTo("hello world");
     }
 
     @Test
@@ -348,7 +354,9 @@
 
         assertThat(EntityConverter.Companion.of(LIST_ITEM_TYPE_SPEC).convert(listItem))
                 .isEqualTo(listItemProto);
-        assertThat(TypeConverters.toListItem(toParamValue(listItemStruct, "itemId")))
+        assertThat(
+                        ParamValueConverter.Companion.of(LIST_ITEM_TYPE_SPEC)
+                                .fromParamValue(toParamValue(listItemStruct, "itemId")))
                 .isEqualTo(listItem);
     }
 
@@ -435,23 +443,29 @@
 
         assertThat(EntityConverter.Companion.of(ITEM_LIST_TYPE_SPEC).convert(itemList))
                 .isEqualTo(itemListProto);
-        assertThat(TypeConverters.toItemList(toParamValue(itemListStruct, "testList")))
+        assertThat(
+                        ParamValueConverter.Companion.of(ITEM_LIST_TYPE_SPEC)
+                                .fromParamValue(toParamValue(itemListStruct, "testList")))
                 .isEqualTo(itemList);
     }
 
     @Test
     public void order_conversions_matchesExpected() throws Exception {
         EntityConverter<Order> entityConverter = EntityConverter.Companion.of(ORDER_TYPE_SPEC);
+        ParamValueConverter<Order> paramValueConverter =
+                ParamValueConverter.Companion.of(ORDER_TYPE_SPEC);
 
-        assertThat(TypeConverters.toParamValue(ORDER_JAVA_THING))
+        assertThat(paramValueConverter.toParamValue(ORDER_JAVA_THING))
                 .isEqualTo(toParamValue(ORDER_STRUCT, "id"));
-        assertThat(TypeConverters.toOrder(toParamValue(ORDER_STRUCT, "id")))
+        assertThat(paramValueConverter.fromParamValue(toParamValue(ORDER_STRUCT, "id")))
                 .isEqualTo(ORDER_JAVA_THING);
         assertThat(entityConverter.convert(ORDER_JAVA_THING)).isEqualTo(toEntity(ORDER_STRUCT));
     }
 
     @Test
     public void participant_conversions_matchesExpected() throws Exception {
+        ParamValueConverter<Participant> paramValueConverter =
+                ParamValueConverter.Companion.of(PARTICIPANT_TYPE_SPEC);
         ParamValue paramValue =
                 ParamValue.newBuilder()
                         .setIdentifier(PERSON_JAVA_THING.getId().orElse("id"))
@@ -459,8 +473,8 @@
                         .build();
         Participant participant = new Participant(PERSON_JAVA_THING);
 
-        assertThat(TypeConverters.toParamValue(participant)).isEqualTo(paramValue);
-        assertThat(TypeConverters.toParticipant(paramValue)).isEqualTo(participant);
+        assertThat(paramValueConverter.toParamValue(participant)).isEqualTo(paramValue);
+        assertThat(paramValueConverter.fromParamValue(paramValue)).isEqualTo(participant);
     }
 
     @Test
@@ -473,6 +487,8 @@
 
     @Test
     public void recipient_conversions_matchesExpected() throws Exception {
+        ParamValueConverter<Recipient> paramValueConverter =
+                ParamValueConverter.Companion.of(RECIPIENT_TYPE_SPEC);
         ParamValue paramValue =
                 ParamValue.newBuilder()
                         .setIdentifier(PERSON_JAVA_THING.getId().orElse("id"))
@@ -480,12 +496,14 @@
                         .build();
         Recipient recipient = new Recipient(PERSON_JAVA_THING);
 
-        assertThat(TypeConverters.toParamValue(recipient)).isEqualTo(paramValue);
-        assertThat(TypeConverters.toRecipient(paramValue)).isEqualTo(recipient);
+        assertThat(paramValueConverter.toParamValue(recipient)).isEqualTo(paramValue);
+        assertThat(paramValueConverter.fromParamValue(paramValue)).isEqualTo(recipient);
     }
 
     @Test
     public void toParticipant_unexpectedType_throwsException() {
+        ParamValueConverter<Participant> paramValueConverter =
+                ParamValueConverter.Companion.of(PARTICIPANT_TYPE_SPEC);
         Struct malformedStruct =
                 Struct.newBuilder()
                         .putFields("@type", Value.newBuilder().setStringValue("Malformed").build())
@@ -493,11 +511,13 @@
 
         assertThrows(
                 StructConversionException.class,
-                () -> TypeConverters.toParticipant(toParamValue(malformedStruct, "id")));
+                () -> paramValueConverter.fromParamValue(toParamValue(malformedStruct, "id")));
     }
 
     @Test
     public void toRecipient_unexpectedType_throwsException() {
+        ParamValueConverter<Recipient> paramValueConverter =
+                ParamValueConverter.Companion.of(RECIPIENT_TYPE_SPEC);
         Struct malformedStruct =
                 Struct.newBuilder()
                         .putFields("@type", Value.newBuilder().setStringValue("Malformed").build())
@@ -505,11 +525,13 @@
 
         assertThrows(
                 StructConversionException.class,
-                () -> TypeConverters.toRecipient(toParamValue(malformedStruct, "id")));
+                () -> paramValueConverter.fromParamValue(toParamValue(malformedStruct, "id")));
     }
 
     @Test
     public void itemList_malformedStruct_throwsException() {
+        ParamValueConverter<ItemList> paramValueConverter =
+                ParamValueConverter.Companion.of(ITEM_LIST_TYPE_SPEC);
         Struct malformedStruct =
                 Struct.newBuilder()
                         .putFields("@type", Value.newBuilder().setStringValue("Malformed").build())
@@ -519,11 +541,13 @@
 
         assertThrows(
                 StructConversionException.class,
-                () -> TypeConverters.toItemList(toParamValue(malformedStruct, "list1")));
+                () -> paramValueConverter.fromParamValue(toParamValue(malformedStruct, "list1")));
     }
 
     @Test
     public void listItem_malformedStruct_throwsException() throws Exception {
+        ParamValueConverter<ListItem> paramValueConverter =
+                ParamValueConverter.Companion.of(LIST_ITEM_TYPE_SPEC);
         Struct malformedStruct =
                 Struct.newBuilder()
                         .putFields("@type", Value.newBuilder().setStringValue("Malformed").build())
@@ -533,7 +557,7 @@
 
         assertThrows(
                 StructConversionException.class,
-                () -> TypeConverters.toListItem(toParamValue(malformedStruct, "item1")));
+                () -> paramValueConverter.fromParamValue(toParamValue(malformedStruct, "item1")));
     }
 
     @Test
@@ -541,7 +565,7 @@
         List<ParamValue> input =
                 Collections.singletonList(ParamValue.newBuilder().setBoolValue(false).build());
 
-        assertThat(SlotTypeConverter.ofSingular(TypeConverters::toBooleanValue).convert(input))
+        assertThat(SlotTypeConverter.ofSingular(BOOLEAN_PARAM_VALUE_CONVERTER).convert(input))
                 .isFalse();
     }
 
@@ -553,7 +577,7 @@
                 assertThrows(
                         StructConversionException.class,
                         () ->
-                                SlotTypeConverter.ofSingular(TypeConverters::toBooleanValue)
+                                SlotTypeConverter.ofSingular(BOOLEAN_PARAM_VALUE_CONVERTER)
                                         .convert(input));
         assertThat(thrown)
                 .hasMessageThat()
@@ -561,12 +585,31 @@
     }
 
     @Test
+    public void toInteger_throwsException() {
+        List<ParamValue> input = Collections.singletonList(ParamValue.getDefaultInstance());
+
+        StructConversionException thrown =
+                assertThrows(
+                        StructConversionException.class,
+                        () ->
+                                SlotTypeConverter.ofSingular(INTEGER_PARAM_VALUE_CONVERTER)
+                                        .convert(input));
+        assertThat(thrown)
+                .hasMessageThat()
+                .isEqualTo("Cannot parse integer because number_value is missing from ParamValue.");
+    }
+
+
+    @Test
     public void toLocalDate_success() throws Exception {
         List<ParamValue> input =
                 Collections.singletonList(
                         ParamValue.newBuilder().setStringValue("2018-06-17").build());
 
-        assertThat(SlotTypeConverter.ofSingular(TypeConverters::toLocalDate).convert(input))
+        assertThat(
+                        SlotTypeConverter.ofSingular(
+                                        TypeConverters.LOCAL_DATE_PARAM_VALUE_CONVERTER)
+                                .convert(input))
                 .isEqualTo(LocalDate.of(2018, 6, 17));
     }
 
@@ -580,7 +623,8 @@
                 assertThrows(
                         StructConversionException.class,
                         () ->
-                                SlotTypeConverter.ofSingular(TypeConverters::toLocalDate)
+                                SlotTypeConverter.ofSingular(
+                                                TypeConverters.LOCAL_DATE_PARAM_VALUE_CONVERTER)
                                         .convert(input));
         assertThat(thrown)
                 .hasMessageThat()
@@ -595,7 +639,8 @@
                 assertThrows(
                         StructConversionException.class,
                         () ->
-                                SlotTypeConverter.ofSingular(TypeConverters::toLocalDate)
+                                SlotTypeConverter.ofSingular(
+                                                TypeConverters.LOCAL_DATE_PARAM_VALUE_CONVERTER)
                                         .convert(input));
         assertThat(thrown)
                 .hasMessageThat()
@@ -608,7 +653,10 @@
                 Collections.singletonList(
                         ParamValue.newBuilder().setStringValue("15:10:05").build());
 
-        assertThat(SlotTypeConverter.ofSingular(TypeConverters::toLocalTime).convert(input))
+        assertThat(
+                        SlotTypeConverter.ofSingular(
+                                        TypeConverters.LOCAL_TIME_PARAM_VALUE_CONVERTER)
+                                .convert(input))
                 .isEqualTo(LocalTime.of(15, 10, 5));
     }
 
@@ -621,7 +669,8 @@
                 assertThrows(
                         StructConversionException.class,
                         () ->
-                                SlotTypeConverter.ofSingular(TypeConverters::toLocalTime)
+                                SlotTypeConverter.ofSingular(
+                                                TypeConverters.LOCAL_TIME_PARAM_VALUE_CONVERTER)
                                         .convert(input));
         assertThat(thrown)
                 .hasMessageThat()
@@ -636,7 +685,8 @@
                 assertThrows(
                         StructConversionException.class,
                         () ->
-                                SlotTypeConverter.ofSingular(TypeConverters::toLocalTime)
+                                SlotTypeConverter.ofSingular(
+                                                TypeConverters.LOCAL_TIME_PARAM_VALUE_CONVERTER)
                                         .convert(input));
         assertThat(thrown)
                 .hasMessageThat()
@@ -649,7 +699,9 @@
                 Collections.singletonList(
                         ParamValue.newBuilder().setStringValue("America/New_York").build());
 
-        assertThat(SlotTypeConverter.ofSingular(TypeConverters::toZoneId).convert(input))
+        assertThat(
+                        SlotTypeConverter.ofSingular(TypeConverters.ZONE_ID_PARAM_VALUE_CONVERTER)
+                                .convert(input))
                 .isEqualTo(ZoneId.of("America/New_York"));
     }
 
@@ -663,7 +715,8 @@
                 assertThrows(
                         ZoneRulesException.class,
                         () ->
-                                SlotTypeConverter.ofSingular(TypeConverters::toZoneId)
+                                SlotTypeConverter.ofSingular(
+                                                TypeConverters.ZONE_ID_PARAM_VALUE_CONVERTER)
                                         .convert(input));
         assertThat(thrown).hasMessageThat().isEqualTo("Unknown time-zone ID: America/New_Yo");
     }
@@ -676,7 +729,8 @@
                 assertThrows(
                         StructConversionException.class,
                         () ->
-                                SlotTypeConverter.ofSingular(TypeConverters::toZoneId)
+                                SlotTypeConverter.ofSingular(
+                                                TypeConverters.ZONE_ID_PARAM_VALUE_CONVERTER)
                                         .convert(input));
         assertThat(thrown)
                 .hasMessageThat()
@@ -689,7 +743,10 @@
                 Collections.singletonList(
                         ParamValue.newBuilder().setStringValue("2018-06-17T15:10:05Z").build());
 
-        assertThat(SlotTypeConverter.ofSingular(TypeConverters::toZonedDateTime).convert(input))
+        assertThat(
+                        SlotTypeConverter.ofSingular(
+                                        TypeConverters.ZONED_DATETIME_PARAM_VALUE_CONVERTER)
+                                .convert(input))
                 .isEqualTo(ZonedDateTime.of(2018, 6, 17, 15, 10, 5, 0, ZoneOffset.UTC));
     }
 
@@ -705,7 +762,8 @@
                 assertThrows(
                         StructConversionException.class,
                         () ->
-                                SlotTypeConverter.ofSingular(TypeConverters::toZonedDateTime)
+                                SlotTypeConverter.ofSingular(
+                                                TypeConverters.ZONED_DATETIME_PARAM_VALUE_CONVERTER)
                                         .convert(input));
         assertThat(thrown)
                 .hasMessageThat()
@@ -721,7 +779,8 @@
                 assertThrows(
                         StructConversionException.class,
                         () ->
-                                SlotTypeConverter.ofSingular(TypeConverters::toZonedDateTime)
+                                SlotTypeConverter.ofSingular(
+                                                TypeConverters.ZONED_DATETIME_PARAM_VALUE_CONVERTER)
                                         .convert(input));
         assertThat(thrown)
                 .hasMessageThat()
@@ -735,7 +794,8 @@
                 Collections.singletonList(ParamValue.newBuilder().setStringValue("PT5M").build());
 
         Duration convertedDuration =
-                SlotTypeConverter.ofSingular(TypeConverters::toDuration).convert(input);
+                SlotTypeConverter.ofSingular(TypeConverters.DURATION_PARAM_VALUE_CONVERTER)
+                        .convert(input);
 
         assertThat(convertedDuration).isEqualTo(Duration.ofMinutes(5));
     }
@@ -749,7 +809,8 @@
                 assertThrows(
                         StructConversionException.class,
                         () ->
-                                SlotTypeConverter.ofSingular(TypeConverters::toDuration)
+                                SlotTypeConverter.ofSingular(
+                                                TypeConverters.DURATION_PARAM_VALUE_CONVERTER)
                                         .convert(input));
         assertThat(thrown)
                 .hasMessageThat()
@@ -816,17 +877,19 @@
 
     @Test
     public void toParamValues_string_success() {
-        ParamValue output = TypeConverters.toParamValue("grocery");
+        ParamValue output = TypeConverters.STRING_PARAM_VALUE_CONVERTER.toParamValue("grocery");
 
         assertThat(output).isEqualTo(ParamValue.newBuilder().setStringValue("grocery").build());
     }
 
     @Test
     public void toTimer_success() throws Exception {
+        ParamValueConverter<Timer> paramValueConverter =
+                ParamValueConverter.Companion.of(TIMER_TYPE_SPEC);
         Timer timer = Timer.newBuilder().setId("abc").build();
 
         assertThat(
-                        TypeConverters.toTimer(
+                        paramValueConverter.fromParamValue(
                                 ParamValue.newBuilder()
                                         .setStructValue(
                                                 Struct.newBuilder()
@@ -866,7 +929,9 @@
 
     @Test
     public void toParamValues_safetyCheck_success() {
-        assertThat(TypeConverters.toParamValue(SAFETY_CHECK_JAVA_THING))
+        assertThat(
+                        ParamValueConverter.Companion.of(SAFETY_CHECK_TYPE_SPEC)
+                                .toParamValue(SAFETY_CHECK_JAVA_THING))
                 .isEqualTo(
                         ParamValue.newBuilder()
                                 .setStructValue(SAFETY_CHECK_STRUCT)
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecTest.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecTest.java
index 13f5ff0..7fa3427 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecTest.java
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecTest.java
@@ -56,49 +56,60 @@
                             "requiredEntity",
                             Property::requiredEntityField,
                             Argument.Builder::setRequiredEntityField,
-                            TypeConverters::toEntityValue,
+                            TypeConverters.ENTITY_PARAM_VALUE_CONVERTER,
                             PropertyConverter::entityToProto)
                     .bindOptionalParameter(
                             "optionalEntity",
                             Property::optionalEntityField,
                             Argument.Builder::setOptionalEntityField,
-                            TypeConverters::toEntityValue,
+                            TypeConverters.ENTITY_PARAM_VALUE_CONVERTER,
                             PropertyConverter::entityToProto)
                     .bindRepeatedParameter(
                             "repeatedEntity",
                             Property::repeatedEntityField,
                             Argument.Builder::setRepeatedEntityField,
-                            TypeConverters::toEntityValue,
+                            TypeConverters.ENTITY_PARAM_VALUE_CONVERTER,
                             PropertyConverter::entityToProto)
                     .bindParameter(
                             "requiredString",
                             Property::requiredStringField,
                             Argument.Builder::setRequiredStringField,
-                            TypeConverters::toStringValue,
+                            TypeConverters.STRING_PARAM_VALUE_CONVERTER,
                             PropertyConverter::stringValueToProto)
                     .bindOptionalParameter(
                             "optionalString",
                             Property::optionalStringField,
                             Argument.Builder::setOptionalStringField,
-                            TypeConverters::toStringValue,
+                            TypeConverters.STRING_PARAM_VALUE_CONVERTER,
                             PropertyConverter::stringValueToProto)
                     .bindRepeatedParameter(
                             "repeatedString",
                             Property::repeatedStringField,
                             Argument.Builder::setRepeatedStringField,
-                            TypeConverters::toStringValue,
+                            TypeConverters.STRING_PARAM_VALUE_CONVERTER,
                             PropertyConverter::stringValueToProto)
                     .bindOptionalOutput(
                             "optionalStringOutput",
                             Output::optionalStringField,
-                            TypeConverters::toParamValue)
+                            TypeConverters.STRING_PARAM_VALUE_CONVERTER::toParamValue)
                     .bindRepeatedOutput(
                             "repeatedStringOutput",
                             Output::repeatedStringField,
-                            TypeConverters::toParamValue)
+                            TypeConverters.STRING_PARAM_VALUE_CONVERTER::toParamValue)
                     .build();
     private static final ParamValueConverter<String> STRING_PARAM_VALUE_CONVERTER =
-            (paramValue) -> "test";
+            new ParamValueConverter<String>() {
+                @NonNull
+                @Override
+                public ParamValue toParamValue(String type) {
+                    return ParamValue.newBuilder().setStringValue(type).build();
+                }
+
+                @Override
+                public String fromParamValue(@NonNull ParamValue paramValue) {
+                    return "test";
+                }
+            };
     private static final EntityConverter<String> STRING_ENTITY_CONVERTER =
             (theString) ->
                     androidx.appactions.interaction.proto.Entity.newBuilder()
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityImplTest.kt b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityImplTest.kt
index 6138765..113a7e9 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityImplTest.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityImplTest.kt
@@ -26,6 +26,7 @@
 import androidx.appactions.interaction.capabilities.core.impl.ErrorStatusInternal
 import androidx.appactions.interaction.capabilities.core.impl.concurrent.Futures
 import androidx.appactions.interaction.capabilities.core.impl.converters.EntityConverter
+import androidx.appactions.interaction.capabilities.core.impl.converters.ParamValueConverter
 import androidx.appactions.interaction.capabilities.core.impl.converters.PropertyConverter
 import androidx.appactions.interaction.capabilities.core.impl.converters.SearchActionConverter
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
@@ -187,7 +188,10 @@
         fun setRequiredEntityValue(entityValue: EntityValue) {
             super.updateParamValues(
                 mapOf(
-                    "required" to listOf(TypeConverters.toParamValue(entityValue)),
+                    "required" to
+                        listOf(
+                            TypeConverters.ENTITY_PARAM_VALUE_CONVERTER.toParamValue(entityValue)
+                        ),
                 ),
             )
         }
@@ -292,12 +296,12 @@
                     .registerValueTaskParam(
                         "slotA",
                         AUTO_ACCEPT_ENTITY_VALUE,
-                        TypeConverters::toEntityValue,
+                        TypeConverters.ENTITY_PARAM_VALUE_CONVERTER,
                     )
                     .registerValueTaskParam(
                         "slotB",
                         AUTO_ACCEPT_ENTITY_VALUE,
-                        TypeConverters::toEntityValue,
+                        TypeConverters.ENTITY_PARAM_VALUE_CONVERTER,
                     )
                     .build()
             }
@@ -390,12 +394,12 @@
                     .registerValueTaskParam(
                         "slotA",
                         AUTO_ACCEPT_ENTITY_VALUE,
-                        TypeConverters::toEntityValue,
+                        TypeConverters.ENTITY_PARAM_VALUE_CONVERTER,
                     )
                     .registerValueTaskParam(
                         "slotB",
                         AUTO_REJECT_ENTITY_VALUE,
-                        TypeConverters::toEntityValue,
+                        TypeConverters.ENTITY_PARAM_VALUE_CONVERTER,
                     )
                     .build()
             }
@@ -552,7 +556,7 @@
                             builder.registerAppEntityTaskParam(
                                 "required",
                                 listener,
-                                TypeConverters::toEntityValue,
+                                TypeConverters.ENTITY_PARAM_VALUE_CONVERTER,
                                 TypeConverters::toEntity,
                                 getTrivialSearchActionConverter(),
                             )
@@ -708,7 +712,7 @@
                     .registerAppEntityTaskParam(
                         "listItem",
                         session.getListItemListener(),
-                        TypeConverters::toListItem,
+                        ParamValueConverter.of(LIST_ITEM_TYPE_SPEC),
                         EntityConverter.of(LIST_ITEM_TYPE_SPEC)::convert,
                         getTrivialSearchActionConverter(),
                     )
@@ -944,6 +948,16 @@
         }
 
         private const val CAPABILITY_NAME = "actions.intent.TEST"
+        private val ENUM_CONVERTER: ParamValueConverter<TestEnum> =
+            object : ParamValueConverter<TestEnum> {
+                override fun fromParamValue(paramValue: ParamValue): TestEnum {
+                    return TestEnum.VALUE_1
+                }
+
+                override fun toParamValue(value: TestEnum): ParamValue {
+                    return ParamValue.newBuilder().build()
+                }
+            }
         private val ACTION_SPEC: ActionSpec<Property, Argument, Output> =
             ActionSpecBuilder.ofCapabilityNamed(
                     CAPABILITY_NAME,
@@ -955,39 +969,39 @@
                     "required",
                     Property::requiredEntityField,
                     Argument.Builder::setRequiredEntityField,
-                    TypeConverters::toEntityValue,
+                    TypeConverters.ENTITY_PARAM_VALUE_CONVERTER,
                     PropertyConverter::entityToProto
                 )
                 .bindOptionalParameter(
                     "optional",
                     Property::optionalStringField,
                     Argument.Builder::setOptionalStringField,
-                    TypeConverters::toStringValue,
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER,
                     PropertyConverter::stringValueToProto
                 )
                 .bindOptionalParameter(
                     "optionalEnum",
                     Property::enumField,
                     Argument.Builder::setEnumField,
-                    { TestEnum.VALUE_1 },
+                    ENUM_CONVERTER,
                     { Entity.newBuilder().setIdentifier(it.toString()).build() }
                 )
                 .bindRepeatedParameter(
                     "repeated",
                     Property::repeatedStringField,
                     Argument.Builder::setRepeatedStringField,
-                    TypeConverters::toStringValue,
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER,
                     PropertyConverter::stringValueToProto
                 )
                 .bindOptionalOutput(
                     "optionalStringOutput",
                     Output::optionalStringField,
-                    TypeConverters::toParamValue,
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER::toParamValue,
                 )
                 .bindRepeatedOutput(
                     "repeatedStringOutput",
                     Output::repeatedStringField,
-                    TypeConverters::toParamValue,
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER::toParamValue,
                 )
                 .build()
 
@@ -1002,10 +1016,6 @@
                 )
                 .build()
 
-        private fun groundingPredicate(paramValue: ParamValue): Boolean {
-            return !paramValue.hasIdentifier()
-        }
-
         private fun getCurrentValues(
             argName: String,
             appDialogState: AppDialogState
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskSlotProcessorTest.kt b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskSlotProcessorTest.kt
index 83e3f69..3f0a3a5 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskSlotProcessorTest.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskSlotProcessorTest.kt
@@ -128,7 +128,7 @@
                 "singularValue",
                 { false },
                 createValueResolver(newAccepted()),
-                { TypeConverters.toStringValue(it) },
+                TypeConverters.STRING_PARAM_VALUE_CONVERTER,
                 null,
                 null
             )
@@ -159,7 +159,7 @@
                 "singularValue",
                 { false },
                 createValueResolver(newRejected()),
-                { TypeConverters.toStringValue(it) },
+                TypeConverters.STRING_PARAM_VALUE_CONVERTER,
                 null,
                 null
             )
@@ -191,7 +191,7 @@
                 "repeatedValue",
                 { false },
                 createValueListResolver(newAccepted()) { lastReceivedArgs.set(it) },
-                { TypeConverters.toStringValue(it) },
+                TypeConverters.STRING_PARAM_VALUE_CONVERTER,
                 null,
                 null
             )
@@ -232,7 +232,7 @@
                 "repeatedValue",
                 { false },
                 createValueListResolver(newRejected()) { lastReceivedArgs.set(it) },
-                { TypeConverters.toStringValue(it) },
+                TypeConverters.STRING_PARAM_VALUE_CONVERTER,
                 null,
                 null
             )
@@ -277,7 +277,7 @@
                     createAssistantDisambigResolver(newAccepted(), { onReceivedCb.set(it) }) {
                         renderCb.set(it)
                     },
-                    { TypeConverters.toStringValue(it) },
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER,
                     null,
                     null
                 )
@@ -348,7 +348,7 @@
                 "appDrivenSlot",
                 { true }, // always invoke app-grounding in all cases
                 resolver,
-                { TypeConverters.toStringValue(it) }, // Not invoked
+                TypeConverters.STRING_PARAM_VALUE_CONVERTER, // Not invoked
                 { Entity.getDefaultInstance() }
             ) {
                 SearchAction.newBuilder<String>().setQuery("A").setObject("nested").build()
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityStructFill.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityStructFill.java
index 6959881..8e50edf 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityStructFill.java
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityStructFill.java
@@ -22,6 +22,7 @@
 import androidx.appactions.interaction.capabilities.core.BaseSession;
 import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
 import androidx.appactions.interaction.capabilities.core.impl.converters.EntityConverter;
+import androidx.appactions.interaction.capabilities.core.impl.converters.ParamValueConverter;
 import androidx.appactions.interaction.capabilities.core.impl.converters.PropertyConverter;
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters;
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpec;
@@ -47,13 +48,13 @@
                             "listItem",
                             Property::listItem,
                             Argument.Builder::setListItem,
-                            TypeConverters::toListItem,
+                            ParamValueConverter.Companion.of(LIST_ITEM_TYPE_SPEC),
                             EntityConverter.Companion.of(LIST_ITEM_TYPE_SPEC)::convert)
                     .bindOptionalParameter(
                             "string",
                             Property::anyString,
                             Argument.Builder::setAnyString,
-                            TypeConverters::toStringValue,
+                            TypeConverters.STRING_PARAM_VALUE_CONVERTER,
                             PropertyConverter::stringValueToProto)
                     .build();
 
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoEntityValues.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoEntityValues.java
index f1e0d43..f6e18af 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoEntityValues.java
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoEntityValues.java
@@ -42,13 +42,13 @@
                             "slotA",
                             Property::slotA,
                             Argument.Builder::setSlotA,
-                            TypeConverters::toEntityValue,
+                            TypeConverters.ENTITY_PARAM_VALUE_CONVERTER,
                             PropertyConverter::entityToProto)
                     .bindOptionalParameter(
                             "slotB",
                             Property::slotB,
                             Argument.Builder::setSlotB,
-                            TypeConverters::toEntityValue,
+                            TypeConverters.ENTITY_PARAM_VALUE_CONVERTER,
                             PropertyConverter::entityToProto)
                     .build();
 
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoStrings.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoStrings.java
index 21b737f..41f2a2f 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoStrings.java
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoStrings.java
@@ -40,13 +40,13 @@
                             "stringSlotA",
                             Property::stringSlotA,
                             Argument.Builder::setStringSlotA,
-                            TypeConverters::toStringValue,
+                            TypeConverters.STRING_PARAM_VALUE_CONVERTER,
                             PropertyConverter::stringValueToProto)
                     .bindOptionalParameter(
                             "stringSlotB",
                             Property::stringSlotB,
                             Argument.Builder::setStringSlotB,
-                            TypeConverters::toStringValue,
+                            TypeConverters.STRING_PARAM_VALUE_CONVERTER,
                             PropertyConverter::stringValueToProto)
                     .build();
 
diff --git a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetExerciseObservation.kt b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetExerciseObservation.kt
index 1f61dfe..ab06bf6 100644
--- a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetExerciseObservation.kt
+++ b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetExerciseObservation.kt
@@ -44,14 +44,14 @@
             "healthObservation.startTime",
             { property -> Optional.ofNullable(property.startTime) },
             GetExerciseObservation.Argument.Builder::setStartTime,
-            TypeConverters::toLocalTime,
+            TypeConverters.LOCAL_TIME_PARAM_VALUE_CONVERTER,
             TypeConverters::toEntity
         )
         .bindOptionalParameter(
             "healthObservation.endTime",
             { property -> Optional.ofNullable(property.endTime) },
             GetExerciseObservation.Argument.Builder::setEndTime,
-            TypeConverters::toLocalTime,
+            TypeConverters.LOCAL_TIME_PARAM_VALUE_CONVERTER,
             TypeConverters::toEntity
         )
         .build()
diff --git a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetHealthObservation.kt b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetHealthObservation.kt
index ac98ea1..d8853bb 100644
--- a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetHealthObservation.kt
+++ b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetHealthObservation.kt
@@ -44,14 +44,14 @@
             "exerciseObservation.startTime",
             { property -> Optional.ofNullable(property.startTime) },
             GetHealthObservation.Argument.Builder::setStartTime,
-            TypeConverters::toLocalTime,
+            TypeConverters.LOCAL_TIME_PARAM_VALUE_CONVERTER,
             TypeConverters::toEntity
         )
         .bindOptionalParameter(
             "exerciseObservation.endTime",
             { property -> Optional.ofNullable(property.endTime) },
             GetHealthObservation.Argument.Builder::setEndTime,
-            TypeConverters::toLocalTime,
+            TypeConverters.LOCAL_TIME_PARAM_VALUE_CONVERTER,
             TypeConverters::toEntity
         )
         .build()
diff --git a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/PauseExercise.kt b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/PauseExercise.kt
index aed3db4..0c1ef08 100644
--- a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/PauseExercise.kt
+++ b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/PauseExercise.kt
@@ -42,7 +42,7 @@
             "exercise.name",
             { property -> Optional.ofNullable(property.name) },
             PauseExercise.Argument.Builder::setName,
-            TypeConverters::toStringValue,
+            TypeConverters.STRING_PARAM_VALUE_CONVERTER,
             PropertyConverter::stringValueToProto
         )
         .build()
diff --git a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/ResumeExercise.kt b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/ResumeExercise.kt
index 629896e..934ef9b 100644
--- a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/ResumeExercise.kt
+++ b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/ResumeExercise.kt
@@ -42,7 +42,7 @@
             "exercise.name",
             { property -> Optional.ofNullable(property.name) },
             ResumeExercise.Argument.Builder::setName,
-            TypeConverters::toStringValue,
+            TypeConverters.STRING_PARAM_VALUE_CONVERTER,
             PropertyConverter::stringValueToProto
         )
         .build()
diff --git a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StartExercise.kt b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StartExercise.kt
index 0ecf95b..d4a0c1f 100644
--- a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StartExercise.kt
+++ b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StartExercise.kt
@@ -43,14 +43,14 @@
             "exercise.duration",
             { property -> Optional.ofNullable(property.duration) },
             StartExercise.Argument.Builder::setDuration,
-            TypeConverters::toDuration,
+            TypeConverters.DURATION_PARAM_VALUE_CONVERTER,
             TypeConverters::toEntity
         )
         .bindOptionalParameter(
             "exercise.name",
             { property -> Optional.ofNullable(property.name) },
             StartExercise.Argument.Builder::setName,
-            TypeConverters::toStringValue,
+            TypeConverters.STRING_PARAM_VALUE_CONVERTER,
             PropertyConverter::stringValueToProto
         )
         .build()
diff --git a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StopExercise.kt b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StopExercise.kt
index bc9de14..a60f8bf 100644
--- a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StopExercise.kt
+++ b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StopExercise.kt
@@ -42,7 +42,7 @@
             "exercise.name",
             { property -> Optional.ofNullable(property.name) },
             StopExercise.Argument.Builder::setName,
-            TypeConverters::toStringValue,
+            TypeConverters.STRING_PARAM_VALUE_CONVERTER,
             PropertyConverter::stringValueToProto
         )
         .build()
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StartTimer.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StartTimer.kt
index 8a561ee..59b4271 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StartTimer.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StartTimer.kt
@@ -48,21 +48,21 @@
             "timer.identifier",
             { property -> Optional.ofNullable(property.identifier) },
             StartTimer.Argument.Builder::setIdentifier,
-            TypeConverters::toStringValue,
+            TypeConverters.STRING_PARAM_VALUE_CONVERTER,
             PropertyConverter::stringValueToProto
         )
         .bindOptionalParameter(
             "timer.name",
             { property -> Optional.ofNullable(property.name) },
             StartTimer.Argument.Builder::setName,
-            TypeConverters::toStringValue,
+            TypeConverters.STRING_PARAM_VALUE_CONVERTER,
             PropertyConverter::stringValueToProto
         )
         .bindOptionalParameter(
             "timer.duration",
             { property -> Optional.ofNullable(property.duration) },
             StartTimer.Argument.Builder::setDuration,
-            TypeConverters::toDuration,
+            TypeConverters.DURATION_PARAM_VALUE_CONVERTER,
             TypeConverters::toEntity
         )
         .bindOptionalOutput(
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/TimerValue.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/TimerValue.kt
index 44cbb94..76046cd 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/TimerValue.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/TimerValue.kt
@@ -66,9 +66,7 @@
                 )
                 .build()
 
-        internal val FROM_PARAM_VALUE = ParamValueConverter {
-            TYPE_SPEC.fromStruct(it.structValue)
-        }
+        internal val FROM_PARAM_VALUE = ParamValueConverter.of(TYPE_SPEC)
 
         internal val TO_ENTITY_VALUE =
             EntityConverter<TimerValue> { it ->
diff --git a/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StartSafetyCheck.kt b/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StartSafetyCheck.kt
index c126d96..7eef37e 100644
--- a/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StartSafetyCheck.kt
+++ b/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StartSafetyCheck.kt
@@ -16,11 +16,13 @@
 
 package androidx.appactions.interaction.capabilities.safety
 
-import androidx.appactions.interaction.capabilities.core.CapabilityBuilderBase
 import androidx.appactions.interaction.capabilities.core.ActionCapability
 import androidx.appactions.interaction.capabilities.core.BaseSession
+import androidx.appactions.interaction.capabilities.core.CapabilityBuilderBase
 import androidx.appactions.interaction.capabilities.core.impl.BuilderOf
+import androidx.appactions.interaction.capabilities.core.impl.converters.ParamValueConverter
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
+import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters.SAFETY_CHECK_TYPE_SPEC
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
 import androidx.appactions.interaction.capabilities.core.properties.TypeProperty
 import androidx.appactions.interaction.capabilities.core.task.impl.AbstractTaskUpdater
@@ -51,20 +53,20 @@
             "safetyCheck.duration",
             { property -> Optional.ofNullable(property.duration) },
             StartSafetyCheck.Argument.Builder::setDuration,
-            TypeConverters::toDuration,
+            TypeConverters.DURATION_PARAM_VALUE_CONVERTER,
             TypeConverters::toEntity
         )
         .bindOptionalParameter(
             "safetyCheck.checkInTime",
             { property -> Optional.ofNullable(property.checkInTime) },
             StartSafetyCheck.Argument.Builder::setCheckInTime,
-            TypeConverters::toZonedDateTime,
+            TypeConverters.ZONED_DATETIME_PARAM_VALUE_CONVERTER,
             TypeConverters::toEntity
         )
         .bindOptionalOutput(
             "safetyCheck",
             { output -> Optional.ofNullable(output.safetyCheck) },
-            TypeConverters::toParamValue
+            ParamValueConverter.of(SAFETY_CHECK_TYPE_SPEC)::toParamValue
         )
         .bindOptionalOutput(
             "executionStatus",