Add V4L2 Control Factory

Also moves all control factory methods into a separate file.

BUG: 30900438
TEST: unit tests pass
Change-Id: I885903d8e23a548b63fd20006568145a233c0316
diff --git a/modules/camera/3_4/Android.mk b/modules/camera/3_4/Android.mk
index 1e7f4d6..ae28598 100644
--- a/modules/camera/3_4/Android.mk
+++ b/modules/camera/3_4/Android.mk
@@ -50,6 +50,7 @@
   v4l2_wrapper.cpp \
 
 v4l2_test_files := \
+  metadata/control_factory_test.cpp \
   metadata/control_test.cpp \
   metadata/enum_converter_test.cpp \
   metadata/ignored_control_delegate_test.cpp \
diff --git a/modules/camera/3_4/metadata/control.h b/modules/camera/3_4/metadata/control.h
index 199ecd0..7c8c6bf 100644
--- a/modules/camera/3_4/metadata/control.h
+++ b/modules/camera/3_4/metadata/control.h
@@ -22,9 +22,7 @@
 #include <system/camera_metadata.h>
 
 #include "../common.h"
-#include "menu_control_options.h"
 #include "metadata_common.h"
-#include "no_effect_control_delegate.h"
 #include "partial_metadata_interface.h"
 #include "tagged_control_delegate.h"
 #include "tagged_control_options.h"
@@ -52,13 +50,7 @@
   virtual int SetRequestValues(
       const android::CameraMetadata& metadata) override;
 
-  // Factory methods for some common combinations of delegates & options.
-  // NoEffectMenuControl: Some menu options, but they have no effect.
-  // The default value will be the first element of |options|.
-  static std::unique_ptr<Control<T>> NoEffectMenuControl(
-      int32_t delegate_tag, int32_t options_tag, const std::vector<T>& options);
-
- protected:
+ private:
   std::unique_ptr<TaggedControlDelegate<T>> delegate_;
   std::unique_ptr<TaggedControlOptions<T>> options_;
 
@@ -178,24 +170,6 @@
   return delegate_->SetValue(requested);
 }
 
-template <typename T>
-std::unique_ptr<Control<T>> Control<T>::NoEffectMenuControl(
-    int32_t delegate_tag, int32_t options_tag, const std::vector<T>& options) {
-  HAL_LOG_ENTER();
-
-  if (options.empty()) {
-    HAL_LOGE("At least one option must be provided.");
-    return nullptr;
-  }
-
-  return std::make_unique<Control<T>>(
-      std::make_unique<TaggedControlDelegate<T>>(
-          delegate_tag,
-          std::make_unique<NoEffectControlDelegate<T>>(options[0])),
-      std::make_unique<TaggedControlOptions<T>>(
-          options_tag, std::make_unique<MenuControlOptions<T>>(options)));
-}
-
 }  // namespace v4l2_camera_hal
 
 #endif  // V4L2_CAMERA_HAL_METADATA_CONTROL_H_
diff --git a/modules/camera/3_4/metadata/control_factory.h b/modules/camera/3_4/metadata/control_factory.h
new file mode 100644
index 0000000..911b67f
--- /dev/null
+++ b/modules/camera/3_4/metadata/control_factory.h
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef V4L2_CAMERA_HAL_METADATA_CONTROL_FACTORY_H_
+#define V4L2_CAMERA_HAL_METADATA_CONTROL_FACTORY_H_
+
+#include "../common.h"
+#include "control.h"
+#include "menu_control_options.h"
+#include "no_effect_control_delegate.h"
+#include "ranged_converter.h"
+#include "slider_control_options.h"
+#include "tagged_control_delegate.h"
+#include "tagged_control_options.h"
+#include "v4l2_control_delegate.h"
+
+namespace v4l2_camera_hal {
+
+enum class ControlType { kMenu, kSlider };
+
+// Static functions to create controls. Nullptr is returned on failures.
+
+// NoEffectMenuControl: Some menu options, but they have no effect.
+// The default value will be the first element of |options|.
+template <typename T>
+static std::unique_ptr<Control<T>> NoEffectMenuControl(
+    int32_t delegate_tag, int32_t options_tag, const std::vector<T>& options);
+
+// NoEffectSliderControl: A slider of options, but they have no effect.
+// The default value will be |min|.
+template <typename T>
+static std::unique_ptr<Control<T>> NoEffectSliderControl(int32_t delegate_tag,
+                                                         int32_t options_tag,
+                                                         T min,
+                                                         T max);
+
+// NoEffectControl: A control with no effect and only a single allowable
+// value. Chooses an appropriate ControlOptionsInterface depending on type.
+template <typename T>
+static std::unique_ptr<Control<T>> NoEffectControl(ControlType type,
+                                                   int32_t delegate_tag,
+                                                   int32_t options_tag,
+                                                   T value);
+
+// V4L2Control: A control corresponding to a V4L2 control.
+template <typename T>
+static std::unique_ptr<Control<T>> V4L2Control(
+    ControlType type,
+    int32_t delegate_tag,
+    int32_t options_tag,
+    std::shared_ptr<V4L2Wrapper> device,
+    int control_id,
+    std::shared_ptr<ConverterInterface<T, int32_t>> converter);
+
+// V4L2ControlOrDefault: Like V4L2Control, but if the V4L2Control fails to
+// initialize for some reason, this method will fall back to NoEffectControl.
+template <typename T>
+static std::unique_ptr<Control<T>> V4L2ControlOrDefault(
+    ControlType type,
+    int32_t delegate_tag,
+    int32_t options_tag,
+    std::shared_ptr<V4L2Wrapper> device,
+    int control_id,
+    std::shared_ptr<ConverterInterface<T, int32_t>> converter,
+    const T& default_value);
+
+// -----------------------------------------------------------------------------
+
+template <typename T>
+std::unique_ptr<Control<T>> NoEffectMenuControl(int32_t delegate_tag,
+                                                int32_t options_tag,
+                                                const std::vector<T>& options) {
+  HAL_LOG_ENTER();
+
+  if (options.empty()) {
+    HAL_LOGE("At least one option must be provided.");
+    return nullptr;
+  }
+
+  return std::make_unique<Control<T>>(
+      std::make_unique<TaggedControlDelegate<T>>(
+          delegate_tag,
+          std::make_unique<NoEffectControlDelegate<T>>(options[0])),
+      std::make_unique<TaggedControlOptions<T>>(
+          options_tag, std::make_unique<MenuControlOptions<T>>(options)));
+}
+
+template <typename T>
+std::unique_ptr<Control<T>> NoEffectSliderControl(int32_t delegate_tag,
+                                                  int32_t options_tag,
+                                                  T min,
+                                                  T max) {
+  HAL_LOG_ENTER();
+
+  return std::make_unique<Control<T>>(
+      std::make_unique<TaggedControlDelegate<T>>(
+          delegate_tag, std::make_unique<NoEffectControlDelegate<T>>(min)),
+      std::make_unique<TaggedControlOptions<T>>(
+          options_tag, std::make_unique<SliderControlOptions<T>>(min, max)));
+}
+
+template <typename T>
+std::unique_ptr<Control<T>> NoEffectControl(ControlType type,
+                                            int32_t delegate_tag,
+                                            int32_t options_tag,
+                                            T value) {
+  HAL_LOG_ENTER();
+
+  switch (type) {
+    case ControlType::kMenu:
+      return NoEffectMenuControl<T>(delegate_tag, options_tag, {value});
+    case ControlType::kSlider:
+      return NoEffectSliderControl(delegate_tag, options_tag, value, value);
+  }
+}
+
+template <typename T>
+std::unique_ptr<Control<T>> V4L2Control(
+    ControlType type,
+    int32_t delegate_tag,
+    int32_t options_tag,
+    std::shared_ptr<V4L2Wrapper> device,
+    int control_id,
+    std::shared_ptr<ConverterInterface<T, int32_t>> converter) {
+  HAL_LOG_ENTER();
+
+  // Query the device.
+  v4l2_query_ext_ctrl control_query;
+  int res = device->QueryControl(control_id, &control_query);
+  if (res) {
+    HAL_LOGE("Failed to query control %d.", control_id);
+    return nullptr;
+  }
+
+  int32_t control_min = static_cast<int32_t>(control_query.minimum);
+  int32_t control_max = static_cast<int32_t>(control_query.maximum);
+  int32_t control_step = static_cast<int32_t>(control_query.step);
+  if (control_min > control_max) {
+    HAL_LOGE("No acceptable values (min %d is greater than max %d).",
+             control_min,
+             control_max);
+    return nullptr;
+  }
+
+  // Variables needed by the various switch statements.
+  std::vector<T> options;
+  T metadata_val;
+  T metadata_min;
+  T metadata_max;
+  // Set up the result converter and result options based on type.
+  std::shared_ptr<ConverterInterface<T, int32_t>> result_converter(converter);
+  std::unique_ptr<ControlOptionsInterface<T>> result_options;
+  switch (control_query.type) {
+    case V4L2_CTRL_TYPE_BOOLEAN:  // Fall-through.
+    case V4L2_CTRL_TYPE_MENU:
+      if (type != ControlType::kMenu) {
+        HAL_LOGE(
+            "V4L2 control %d is of type %d, which isn't compatible with "
+            "desired metadata control type %d",
+            control_id,
+            control_query.type,
+            type);
+        return nullptr;
+      }
+
+      // Convert each available option,
+      // ignoring ones without a known conversion.
+      for (int32_t i = control_min; i <= control_max; i += control_step) {
+        res = converter->V4L2ToMetadata(i, &metadata_val);
+        if (res == -EINVAL) {
+          HAL_LOGV("V4L2 value %d for control %d has no metadata equivalent.",
+                   i,
+                   control_id);
+          continue;
+        } else if (res) {
+          HAL_LOGE("Error converting value %d for control %d.", i, control_id);
+          return nullptr;
+        }
+        options.push_back(metadata_val);
+      }
+      // Check to make sure there's at least one option.
+      if (options.empty()) {
+        HAL_LOGE("No valid options for control %d.", control_id);
+        return nullptr;
+      }
+      result_options.reset(new MenuControlOptions<T>(options));
+      // No converter changes necessary.
+      break;
+    case V4L2_CTRL_TYPE_INTEGER:
+      if (type != ControlType::kSlider) {
+        HAL_LOGE(
+            "V4L2 control %d is of type %d, which isn't compatible with "
+            "desired metadata control type %d",
+            control_id,
+            control_query.type,
+            type);
+        return nullptr;
+      }
+
+      // Upgrade to a range/step-clamping converter.
+      result_converter.reset(new RangedConverter<T, int32_t>(
+          converter, control_min, control_max, control_step));
+
+      // Convert the min and max.
+      res = result_converter->V4L2ToMetadata(control_min, &metadata_min);
+      if (res) {
+        HAL_LOGE(
+            "Failed to convert V4L2 min value %d for control %d to metadata.",
+            control_min,
+            control_id);
+        return nullptr;
+      }
+      res = result_converter->V4L2ToMetadata(control_max, &metadata_max);
+      if (res) {
+        HAL_LOGE(
+            "Failed to convert V4L2 max value %d for control %d to metadata.",
+            control_max,
+            control_id);
+        return nullptr;
+      }
+      result_options.reset(
+          new SliderControlOptions<T>(metadata_min, metadata_max));
+      break;
+    default:
+      HAL_LOGE("Control %d is of unsupported type %d",
+               control_id,
+               control_query.type);
+      return nullptr;
+  }
+
+  // Construct the control.
+  return std::make_unique<Control<T>>(
+      std::make_unique<TaggedControlDelegate<T>>(
+          delegate_tag,
+          std::make_unique<V4L2ControlDelegate<T>>(
+              device, control_id, result_converter)),
+      std::make_unique<TaggedControlOptions<T>>(options_tag,
+                                                std::move(result_options)));
+}
+
+template <typename T>
+std::unique_ptr<Control<T>> V4L2ControlOrDefault(
+    ControlType type,
+    int32_t delegate_tag,
+    int32_t options_tag,
+    std::shared_ptr<V4L2Wrapper> device,
+    int control_id,
+    std::shared_ptr<ConverterInterface<T, int32_t>> converter,
+    const T& default_value) {
+  HAL_LOG_ENTER();
+
+  std::unique_ptr<Control<T>> result = V4L2Control(
+      type, delegate_tag, options_tag, device, control_id, converter);
+  if (!result) {
+    result = NoEffectControl(type, delegate_tag, options_tag, default_value);
+  }
+  return result;
+}
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_CONTROL_FACTORY_H_
diff --git a/modules/camera/3_4/metadata/control_factory_test.cpp b/modules/camera/3_4/metadata/control_factory_test.cpp
new file mode 100644
index 0000000..9010879
--- /dev/null
+++ b/modules/camera/3_4/metadata/control_factory_test.cpp
@@ -0,0 +1,433 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include <camera/CameraMetadata.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "../v4l2_wrapper_mock.h"
+#include "control_factory.h"
+#include "converter_interface_mock.h"
+#include "metadata_common.h"
+#include "test_common.h"
+
+using testing::AtMost;
+using testing::Expectation;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::Test;
+using testing::_;
+
+namespace v4l2_camera_hal {
+
+class ControlFactoryTest : public Test {
+ protected:
+  virtual void SetUp() {
+    mock_device_.reset(new V4L2WrapperMock());
+    mock_converter_.reset(new ConverterInterfaceMock<uint8_t, int32_t>());
+    // Nullify control so an error will be thrown
+    // if a test doesn't construct it.
+    control_.reset();
+  }
+
+  virtual void ExpectTags() {
+    ASSERT_EQ(control_->StaticTags().size(), 1);
+    EXPECT_EQ(control_->StaticTags()[0], options_tag_);
+    ASSERT_EQ(control_->ControlTags().size(), 1);
+    EXPECT_EQ(control_->ControlTags()[0], delegate_tag_);
+    ASSERT_EQ(control_->DynamicTags().size(), 1);
+    EXPECT_EQ(control_->DynamicTags()[0], delegate_tag_);
+  }
+
+  virtual void ExpectOptions(const std::vector<uint8_t>& options) {
+    // Options should be available.
+    android::CameraMetadata metadata;
+    ASSERT_EQ(control_->PopulateStaticFields(&metadata), 0);
+    EXPECT_EQ(metadata.entryCount(), 1);
+    ExpectMetadataEq(metadata, options_tag_, options);
+  }
+
+  virtual void ExpectValue(uint8_t value) {
+    android::CameraMetadata metadata;
+    ASSERT_EQ(control_->PopulateDynamicFields(&metadata), 0);
+    EXPECT_EQ(metadata.entryCount(), 1);
+    ExpectMetadataEq(metadata, delegate_tag_, value);
+  }
+
+  std::unique_ptr<Control<uint8_t>> control_;
+  std::shared_ptr<ConverterInterfaceMock<uint8_t, int32_t>> mock_converter_;
+  std::shared_ptr<V4L2WrapperMock> mock_device_;
+
+  // Need tags that match the data type (uint8_t) being passed.
+  const int32_t delegate_tag_ = ANDROID_COLOR_CORRECTION_ABERRATION_MODE;
+  const int32_t options_tag_ =
+      ANDROID_COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES;
+};
+
+TEST_F(ControlFactoryTest, NoEffectMenu) {
+  std::vector<uint8_t> test_options = {9, 8, 12};
+  control_ =
+      NoEffectMenuControl<uint8_t>(delegate_tag_, options_tag_, test_options);
+  ASSERT_NE(control_, nullptr);
+
+  ExpectTags();
+
+  // Options should be available.
+  ExpectOptions(test_options);
+  // Default value should be test_options[0].
+  ExpectValue(test_options[0]);
+}
+
+TEST_F(ControlFactoryTest, NoEffectGenericMenu) {
+  uint8_t default_val = 9;
+  control_ = NoEffectControl<uint8_t>(
+      ControlType::kMenu, delegate_tag_, options_tag_, default_val);
+  ASSERT_NE(control_, nullptr);
+
+  ExpectTags();
+
+  // Options should be available.
+  ExpectOptions({default_val});
+  // |default_val| should be default option.
+  ExpectValue(default_val);
+}
+
+TEST_F(ControlFactoryTest, NoEffectSlider) {
+  std::vector<uint8_t> test_range = {9, 12};
+  control_ = NoEffectSliderControl<uint8_t>(
+      delegate_tag_, options_tag_, test_range[0], test_range[1]);
+  ASSERT_NE(control_, nullptr);
+
+  ExpectTags();
+
+  // Single option should be available.
+  ExpectOptions(test_range);
+  // Default value should be the minimum (test_range[0]).
+  ExpectValue(test_range[0]);
+}
+
+TEST_F(ControlFactoryTest, NoEffectGenericSlider) {
+  uint8_t default_val = 9;
+  control_ = NoEffectControl<uint8_t>(
+      ControlType::kSlider, delegate_tag_, options_tag_, default_val);
+  ASSERT_NE(control_, nullptr);
+
+  ExpectTags();
+
+  // Range containing only |default_val| should be available.
+  ExpectOptions({default_val, default_val});
+  // |default_val| should be default option.
+  ExpectValue(default_val);
+}
+
+TEST_F(ControlFactoryTest, V4L2FactoryQueryFail) {
+  int control_id = 55;
+  // Should query the device.
+  EXPECT_CALL(*mock_device_, QueryControl(control_id, _)).WillOnce(Return(-1));
+  control_ = V4L2Control<uint8_t>(ControlType::kMenu,
+                                  delegate_tag_,
+                                  options_tag_,
+                                  mock_device_,
+                                  control_id,
+                                  mock_converter_);
+  // Failure, should return null.
+  ASSERT_EQ(control_, nullptr);
+}
+
+TEST_F(ControlFactoryTest, V4L2FactoryQueryBadType) {
+  int control_id = 55;
+  v4l2_query_ext_ctrl query_result;
+  query_result.type = V4L2_CTRL_TYPE_CTRL_CLASS;
+  // Should query the device.
+  EXPECT_CALL(*mock_device_, QueryControl(control_id, _))
+      .WillOnce(DoAll(SetArgPointee<1>(query_result), Return(0)));
+  control_ = V4L2Control<uint8_t>(ControlType::kMenu,
+                                  delegate_tag_,
+                                  options_tag_,
+                                  mock_device_,
+                                  control_id,
+                                  mock_converter_);
+  // Failure, should return null.
+  ASSERT_EQ(control_, nullptr);
+}
+
+TEST_F(ControlFactoryTest, V4L2FactoryQueryBadRange) {
+  int control_id = 55;
+  v4l2_query_ext_ctrl query_result;
+  query_result.type = V4L2_CTRL_TYPE_MENU;
+  query_result.minimum = 10;
+  query_result.maximum = 1;  // Less than minimum.
+  // Should query the device.
+  EXPECT_CALL(*mock_device_, QueryControl(control_id, _))
+      .WillOnce(DoAll(SetArgPointee<1>(query_result), Return(0)));
+  control_ = V4L2Control<uint8_t>(ControlType::kMenu,
+                                  delegate_tag_,
+                                  options_tag_,
+                                  mock_device_,
+                                  control_id,
+                                  mock_converter_);
+  // Failure, should return null.
+  ASSERT_EQ(control_, nullptr);
+}
+
+TEST_F(ControlFactoryTest, V4L2FactoryTypeRequestMenuMismatch) {
+  int control_id = 55;
+  v4l2_query_ext_ctrl query_result;
+  query_result.type = V4L2_CTRL_TYPE_INTEGER;
+  query_result.minimum = 1;
+  query_result.maximum = 7;
+  query_result.step = 2;
+  // Have conversions for values 1-5, by step size 2.
+  std::map<int32_t, uint8_t> conversion_map = {{1, 10}, {3, 30}, {5, 50}};
+
+  // Should query the device.
+  EXPECT_CALL(*mock_device_, QueryControl(control_id, _))
+      .WillOnce(DoAll(SetArgPointee<1>(query_result), Return(0)));
+
+  // If you ask for a Menu, but the V4L2 control is a slider type, that's bad.
+  control_ = V4L2Control<uint8_t>(ControlType::kMenu,
+                                  delegate_tag_,
+                                  options_tag_,
+                                  mock_device_,
+                                  control_id,
+                                  mock_converter_);
+  ASSERT_EQ(control_, nullptr);
+}
+
+TEST_F(ControlFactoryTest, V4L2FactoryTypeRequestSliderMismatch) {
+  int control_id = 55;
+  v4l2_query_ext_ctrl query_result;
+  query_result.type = V4L2_CTRL_TYPE_MENU;
+  query_result.minimum = 1;
+  query_result.maximum = 7;
+  query_result.step = 2;
+  // Have conversions for values 1-5, by step size 2.
+  std::map<int32_t, uint8_t> conversion_map = {{1, 10}, {3, 30}, {5, 50}};
+
+  // Should query the device.
+  EXPECT_CALL(*mock_device_, QueryControl(control_id, _))
+      .WillOnce(DoAll(SetArgPointee<1>(query_result), Return(0)));
+
+  // If you ask for a Slider and get a Menu, that's bad.
+  control_ = V4L2Control<uint8_t>(ControlType::kSlider,
+                                  delegate_tag_,
+                                  options_tag_,
+                                  mock_device_,
+                                  control_id,
+                                  mock_converter_);
+  ASSERT_EQ(control_, nullptr);
+}
+
+TEST_F(ControlFactoryTest, V4L2FactoryMenu) {
+  int control_id = 55;
+  v4l2_query_ext_ctrl query_result;
+  query_result.type = V4L2_CTRL_TYPE_MENU;
+  query_result.minimum = 1;
+  query_result.maximum = 7;
+  query_result.step = 2;
+  // Have conversions for values 1-5, by step size 2.
+  std::map<int32_t, uint8_t> conversion_map = {{1, 10}, {3, 30}, {5, 50}};
+
+  // Should query the device.
+  EXPECT_CALL(*mock_device_, QueryControl(control_id, _))
+      .WillOnce(DoAll(SetArgPointee<1>(query_result), Return(0)));
+  // Should convert values.
+  std::vector<uint8_t> expected_options;
+  for (auto kv : conversion_map) {
+    EXPECT_CALL(*mock_converter_, V4L2ToMetadata(kv.first, _))
+        .WillOnce(DoAll(SetArgPointee<1>(kv.second), Return(0)));
+    expected_options.push_back(kv.second);
+  }
+  // Will fail to convert 7 with -EINVAL, shouldn't matter.
+  EXPECT_CALL(*mock_converter_, V4L2ToMetadata(7, _)).WillOnce(Return(-EINVAL));
+
+  control_ = V4L2Control<uint8_t>(ControlType::kMenu,
+                                  delegate_tag_,
+                                  options_tag_,
+                                  mock_device_,
+                                  control_id,
+                                  mock_converter_);
+  ASSERT_NE(control_, nullptr);
+
+  ExpectTags();
+  ExpectOptions(expected_options);
+}
+
+TEST_F(ControlFactoryTest, V4L2FactoryMenuConversionFail) {
+  int control_id = 55;
+  v4l2_query_ext_ctrl query_result;
+  query_result.type = V4L2_CTRL_TYPE_MENU;
+  query_result.minimum = 1;
+  query_result.maximum = 7;
+  query_result.step = 2;
+
+  // Should query the device.
+  EXPECT_CALL(*mock_device_, QueryControl(control_id, _))
+      .WillOnce(DoAll(SetArgPointee<1>(query_result), Return(0)));
+  // Conversion fails with non-EINVAL error.
+  EXPECT_CALL(*mock_converter_, V4L2ToMetadata(_, _)).WillOnce(Return(-1));
+
+  control_ = V4L2Control<uint8_t>(ControlType::kMenu,
+                                  delegate_tag_,
+                                  options_tag_,
+                                  mock_device_,
+                                  control_id,
+                                  mock_converter_);
+  ASSERT_EQ(control_, nullptr);
+}
+
+TEST_F(ControlFactoryTest, V4L2FactoryMenuNoConversions) {
+  int control_id = 55;
+  v4l2_query_ext_ctrl query_result;
+  query_result.type = V4L2_CTRL_TYPE_MENU;
+  query_result.minimum = 1;
+  query_result.maximum = 1;
+  query_result.step = 1;
+
+  // Should query the device.
+  EXPECT_CALL(*mock_device_, QueryControl(control_id, _))
+      .WillOnce(DoAll(SetArgPointee<1>(query_result), Return(0)));
+  // Conversion fails with -EINVAL error.
+  EXPECT_CALL(*mock_converter_, V4L2ToMetadata(1, _)).WillOnce(Return(-EINVAL));
+
+  control_ = V4L2Control<uint8_t>(ControlType::kMenu,
+                                  delegate_tag_,
+                                  options_tag_,
+                                  mock_device_,
+                                  control_id,
+                                  mock_converter_);
+  // Since there were no convertable options, should fail.
+  ASSERT_EQ(control_, nullptr);
+}
+
+TEST_F(ControlFactoryTest, V4L2FactoryInteger) {
+  int control_id = 55;
+  v4l2_query_ext_ctrl query_result;
+  query_result.type = V4L2_CTRL_TYPE_INTEGER;
+  query_result.minimum = 1;
+  query_result.maximum = 7;
+  query_result.step = 2;
+  // Have conversions for values 1 & 7.
+  std::map<int32_t, uint8_t> conversion_map = {{1, 10}, {7, 70}};
+
+  // Should query the device.
+  EXPECT_CALL(*mock_device_, QueryControl(control_id, _))
+      .WillOnce(DoAll(SetArgPointee<1>(query_result), Return(0)));
+  // Should convert values.
+  std::vector<uint8_t> expected_options;
+  for (auto kv : conversion_map) {
+    EXPECT_CALL(*mock_converter_, V4L2ToMetadata(kv.first, _))
+        .WillOnce(DoAll(SetArgPointee<1>(kv.second), Return(0)));
+    expected_options.push_back(kv.second);
+  }
+
+  control_ = V4L2Control<uint8_t>(ControlType::kSlider,
+                                  delegate_tag_,
+                                  options_tag_,
+                                  mock_device_,
+                                  control_id,
+                                  mock_converter_);
+  ASSERT_NE(control_, nullptr);
+
+  ExpectTags();
+  ExpectOptions(expected_options);
+
+  // Should be fitting converted values to steps.
+  uint8_t set_val = 10;
+  android::CameraMetadata metadata;
+  EXPECT_EQ(UpdateMetadata(&metadata, delegate_tag_, set_val), 0);
+  EXPECT_CALL(*mock_converter_, MetadataToV4L2(set_val, _))
+      .WillOnce(DoAll(SetArgPointee<1>(4), Return(0)));
+  // When it calls into the device, the 4 returned above should be
+  // rounded down to the step value of 3.
+  EXPECT_CALL(*mock_device_, SetControl(control_id, 3, _)).WillOnce(Return(0));
+  EXPECT_EQ(control_->SetRequestValues(metadata), 0);
+}
+
+TEST_F(ControlFactoryTest, V4L2FactoryIntegerFailedConversion) {
+  int control_id = 55;
+  v4l2_query_ext_ctrl query_result;
+  query_result.type = V4L2_CTRL_TYPE_INTEGER;
+  query_result.minimum = 1;
+  query_result.maximum = 7;
+  query_result.step = 2;
+
+  // Should query the device.
+  EXPECT_CALL(*mock_device_, QueryControl(control_id, _))
+      .WillOnce(DoAll(SetArgPointee<1>(query_result), Return(0)));
+  // Fail to convert a value. Even -EINVAL is bad in this case.
+  EXPECT_CALL(*mock_converter_, V4L2ToMetadata(1, _)).WillOnce(Return(-EINVAL));
+
+  control_ = V4L2Control<uint8_t>(ControlType::kSlider,
+                                  delegate_tag_,
+                                  options_tag_,
+                                  mock_device_,
+                                  control_id,
+                                  mock_converter_);
+  ASSERT_EQ(control_, nullptr);
+}
+
+TEST_F(ControlFactoryTest, V4L2FallbackMenu) {
+  uint8_t default_val = 9;
+  int control_id = 55;
+
+  // Should query the device.
+  EXPECT_CALL(*mock_device_, QueryControl(control_id, _)).WillOnce(Return(-1));
+
+  // Shouldn't fail, should fall back to menu control.
+  control_ = V4L2ControlOrDefault<uint8_t>(ControlType::kMenu,
+                                           delegate_tag_,
+                                           options_tag_,
+                                           mock_device_,
+                                           control_id,
+                                           mock_converter_,
+                                           default_val);
+  ASSERT_NE(control_, nullptr);
+
+  ExpectTags();
+
+  // Options should be available.
+  ExpectOptions({default_val});
+  // |default_val| should be default option.
+  ExpectValue(default_val);
+}
+
+TEST_F(ControlFactoryTest, V4L2FallbackSlider) {
+  uint8_t default_val = 9;
+  int control_id = 55;
+
+  // Should query the device.
+  EXPECT_CALL(*mock_device_, QueryControl(control_id, _)).WillOnce(Return(-1));
+
+  // Shouldn't fail, should fall back to slider control.
+  control_ = V4L2ControlOrDefault<uint8_t>(ControlType::kSlider,
+                                           delegate_tag_,
+                                           options_tag_,
+                                           mock_device_,
+                                           control_id,
+                                           mock_converter_,
+                                           default_val);
+  ASSERT_NE(control_, nullptr);
+
+  ExpectTags();
+
+  // Range containing only |default_val| should be available.
+  ExpectOptions({default_val, default_val});
+  // |default_val| should be default option.
+  ExpectValue(default_val);
+}
+
+}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/metadata/control_test.cpp b/modules/camera/3_4/metadata/control_test.cpp
index b414ad3..5297a01 100644
--- a/modules/camera/3_4/metadata/control_test.cpp
+++ b/modules/camera/3_4/metadata/control_test.cpp
@@ -28,7 +28,6 @@
 using testing::AtMost;
 using testing::Expectation;
 using testing::Return;
-using testing::ReturnRef;
 using testing::SetArgPointee;
 using testing::Test;
 using testing::_;
@@ -62,6 +61,35 @@
     }
   }
 
+  virtual void ExpectTags(bool with_options = true) {
+    if (with_options) {
+      ASSERT_EQ(control_->StaticTags().size(), 1);
+      EXPECT_EQ(control_->StaticTags()[0], options_tag_);
+    } else {
+      EXPECT_TRUE(control_->StaticTags().empty());
+    }
+    // Controls use the same delgate, and thus tag, for getting and setting.
+    ASSERT_EQ(control_->ControlTags().size(), 1);
+    EXPECT_EQ(control_->ControlTags()[0], delegate_tag_);
+    ASSERT_EQ(control_->DynamicTags().size(), 1);
+    EXPECT_EQ(control_->DynamicTags()[0], delegate_tag_);
+  }
+
+  virtual void ExpectOptions(const std::vector<uint8_t>& options) {
+    // Options should be available.
+    android::CameraMetadata metadata;
+    ASSERT_EQ(control_->PopulateStaticFields(&metadata), 0);
+    EXPECT_EQ(metadata.entryCount(), 1);
+    ExpectMetadataEq(metadata, options_tag_, options);
+  }
+
+  virtual void ExpectValue(uint8_t value) {
+    android::CameraMetadata metadata;
+    ASSERT_EQ(control_->PopulateDynamicFields(&metadata), 0);
+    EXPECT_EQ(metadata.entryCount(), 1);
+    ExpectMetadataEq(metadata, delegate_tag_, value);
+  }
+
   std::unique_ptr<Control<uint8_t>> control_;
   std::unique_ptr<ControlDelegateInterfaceMock<uint8_t>> mock_delegate_;
   std::unique_ptr<ControlOptionsInterfaceMock<uint8_t>> mock_options_;
@@ -74,24 +102,12 @@
 
 TEST_F(ControlTest, Tags) {
   PrepareControl();
-  ASSERT_EQ(control_->StaticTags().size(), 1);
-  EXPECT_EQ(control_->StaticTags()[0], options_tag_);
-  // Controls use the same delgate, and thus tag, for getting and setting.
-  ASSERT_EQ(control_->ControlTags().size(), 1);
-  EXPECT_EQ(control_->ControlTags()[0], delegate_tag_);
-  ASSERT_EQ(control_->DynamicTags().size(), 1);
-  EXPECT_EQ(control_->DynamicTags()[0], delegate_tag_);
+  ExpectTags();
 }
 
 TEST_F(ControlTest, TagsNoOptions) {
   PrepareControl(false);
-  // No options, so no options tag.
-  ASSERT_EQ(control_->StaticTags().size(), 0);
-  // Controls use the same delgate, and thus tag, for getting and setting.
-  ASSERT_EQ(control_->ControlTags().size(), 1);
-  EXPECT_EQ(control_->ControlTags()[0], delegate_tag_);
-  ASSERT_EQ(control_->DynamicTags().size(), 1);
-  EXPECT_EQ(control_->DynamicTags()[0], delegate_tag_);
+  ExpectTags(false);
 }
 
 TEST_F(ControlTest, PopulateStatic) {
@@ -99,13 +115,7 @@
   EXPECT_CALL(*mock_options_, MetadataRepresentation())
       .WillOnce(Return(expected));
   PrepareControl();
-
-  android::CameraMetadata metadata;
-  ASSERT_EQ(control_->PopulateStaticFields(&metadata), 0);
-  // Should only have added 1 entry.
-  EXPECT_EQ(metadata.entryCount(), 1);
-  // Should have added the right entry.
-  ExpectMetadataEq(metadata, options_tag_, expected);
+  ExpectOptions(expected);
 }
 
 TEST_F(ControlTest, PopulateStaticNoOptions) {
@@ -121,14 +131,7 @@
   EXPECT_CALL(*mock_delegate_, GetValue(_))
       .WillOnce(DoAll(SetArgPointee<0>(test_option), Return(0)));
   PrepareControl();
-
-  android::CameraMetadata metadata;
-  ASSERT_EQ(control_->PopulateDynamicFields(&metadata), 0);
-
-  // Should only have added 1 entry.
-  EXPECT_EQ(metadata.entryCount(), 1);
-  // Should have added the right entry.
-  ExpectMetadataEq(metadata, delegate_tag_, test_option);
+  ExpectValue(test_option);
 }
 
 TEST_F(ControlTest, PopulateDynamicNoOptions) {
@@ -137,14 +140,7 @@
   EXPECT_CALL(*mock_delegate_, GetValue(_))
       .WillOnce(DoAll(SetArgPointee<0>(test_option), Return(0)));
   PrepareControl(false);
-
-  android::CameraMetadata metadata;
-  EXPECT_EQ(control_->PopulateDynamicFields(&metadata), 0);
-
-  // Should only have added 1 entry.
-  EXPECT_EQ(metadata.entryCount(), 1);
-  // Should have added the right entry.
-  ExpectMetadataEq(metadata, delegate_tag_, test_option);
+  ExpectValue(test_option);
 }
 
 TEST_F(ControlTest, PopulateDynamicFail) {
@@ -303,31 +299,4 @@
   EXPECT_EQ(control_->SetRequestValues(metadata), 0);
 }
 
-TEST_F(ControlTest, NoEffectMenuFactory) {
-  std::vector<uint8_t> test_options = {9, 8, 12};
-  std::unique_ptr<Control<uint8_t>> dut = Control<uint8_t>::NoEffectMenuControl(
-      delegate_tag_, options_tag_, test_options);
-  ASSERT_NE(dut, nullptr);
-
-  ASSERT_EQ(dut->StaticTags().size(), 1);
-  EXPECT_EQ(dut->StaticTags()[0], options_tag_);
-  // Controls use the same delgate, and thus tag, for getting and setting.
-  ASSERT_EQ(dut->ControlTags().size(), 1);
-  EXPECT_EQ(dut->ControlTags()[0], delegate_tag_);
-  ASSERT_EQ(dut->DynamicTags().size(), 1);
-  EXPECT_EQ(dut->DynamicTags()[0], delegate_tag_);
-
-  // Options should be available.
-  android::CameraMetadata metadata;
-  ASSERT_EQ(dut->PopulateStaticFields(&metadata), 0);
-  EXPECT_EQ(metadata.entryCount(), 1);
-  ExpectMetadataEq(metadata, options_tag_, test_options);
-
-  // Default value should be test_options[0].
-  metadata.clear();
-  ASSERT_EQ(dut->PopulateDynamicFields(&metadata), 0);
-  EXPECT_EQ(metadata.entryCount(), 1);
-  ExpectMetadataEq(metadata, delegate_tag_, test_options[0]);
-}
-
 }  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/v4l2_metadata.cpp b/modules/camera/3_4/v4l2_metadata.cpp
index c289a40..06bd8d9 100644
--- a/modules/camera/3_4/v4l2_metadata.cpp
+++ b/modules/camera/3_4/v4l2_metadata.cpp
@@ -20,6 +20,8 @@
 
 #include "common.h"
 #include "metadata/control.h"
+#include "metadata/control_factory.h"
+#include "metadata/enum_converter.h"
 #include "metadata/property.h"
 
 namespace v4l2_camera_hal {
@@ -36,7 +38,7 @@
   // V4L2 enum controls. Will add the other properties as more PartialMetadata
   // subclasses get implemented.
 
-  AddComponent(Control<uint8_t>::NoEffectMenuControl(
+  AddComponent(NoEffectMenuControl<uint8_t>(
       ANDROID_COLOR_CORRECTION_ABERRATION_MODE,
       ANDROID_COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES,
       {ANDROID_COLOR_CORRECTION_ABERRATION_MODE_FAST,
@@ -48,37 +50,46 @@
   AddComponent(std::unique_ptr<PartialMetadataInterface>(
       new Property<std::array<int32_t, 3>>(ANDROID_CONTROL_MAX_REGIONS,
                                            {{/*AE*/ 0, /*AWB*/ 0, /*AF*/ 0}})));
-  AddEnumControlOrDefault(V4L2_CID_EXPOSURE_AUTO,
-                          ANDROID_CONTROL_AE_MODE,
-                          ANDROID_CONTROL_AE_AVAILABLE_MODES,
-                          {{V4L2_EXPOSURE_AUTO, ANDROID_CONTROL_AE_MODE_ON},
-                           {V4L2_EXPOSURE_MANUAL, ANDROID_CONTROL_AE_MODE_OFF}},
-                          ANDROID_CONTROL_AE_MODE_ON);
-  AddEnumControlOrDefault(V4L2_CID_POWER_LINE_FREQUENCY,
-                          ANDROID_CONTROL_AE_ANTIBANDING_MODE,
-                          ANDROID_CONTROL_AE_AVAILABLE_ANTIBANDING_MODES,
-                          {{V4L2_CID_POWER_LINE_FREQUENCY_DISABLED,
-                            ANDROID_CONTROL_AE_ANTIBANDING_MODE_OFF},
-                           {V4L2_CID_POWER_LINE_FREQUENCY_50HZ,
-                            ANDROID_CONTROL_AE_ANTIBANDING_MODE_50HZ},
-                           {V4L2_CID_POWER_LINE_FREQUENCY_60HZ,
-                            ANDROID_CONTROL_AE_ANTIBANDING_MODE_60HZ},
-                           {V4L2_CID_POWER_LINE_FREQUENCY_AUTO,
-                            ANDROID_CONTROL_AE_ANTIBANDING_MODE_AUTO}},
-                          ANDROID_CONTROL_AE_ANTIBANDING_MODE_AUTO);
+  AddComponent(V4L2ControlOrDefault<uint8_t>(
+      ControlType::kMenu,
+      ANDROID_CONTROL_AE_MODE,
+      ANDROID_CONTROL_AE_AVAILABLE_MODES,
+      device_,
+      V4L2_CID_EXPOSURE_AUTO,
+      std::shared_ptr<ConverterInterface<uint8_t, int32_t>>(new EnumConverter(
+          {{V4L2_EXPOSURE_AUTO, ANDROID_CONTROL_AE_MODE_ON},
+           {V4L2_EXPOSURE_MANUAL, ANDROID_CONTROL_AE_MODE_OFF}})),
+      ANDROID_CONTROL_AE_MODE_ON));
+  AddComponent(V4L2ControlOrDefault<uint8_t>(
+      ControlType::kMenu,
+      ANDROID_CONTROL_AE_ANTIBANDING_MODE,
+      ANDROID_CONTROL_AE_AVAILABLE_ANTIBANDING_MODES,
+      device_,
+      V4L2_CID_POWER_LINE_FREQUENCY,
+      std::shared_ptr<ConverterInterface<uint8_t, int32_t>>(
+          new EnumConverter({{V4L2_CID_POWER_LINE_FREQUENCY_DISABLED,
+                              ANDROID_CONTROL_AE_ANTIBANDING_MODE_OFF},
+                             {V4L2_CID_POWER_LINE_FREQUENCY_50HZ,
+                              ANDROID_CONTROL_AE_ANTIBANDING_MODE_50HZ},
+                             {V4L2_CID_POWER_LINE_FREQUENCY_60HZ,
+                              ANDROID_CONTROL_AE_ANTIBANDING_MODE_60HZ},
+                             {V4L2_CID_POWER_LINE_FREQUENCY_AUTO,
+                              ANDROID_CONTROL_AE_ANTIBANDING_MODE_AUTO}})),
+      ANDROID_CONTROL_AE_ANTIBANDING_MODE_AUTO));
   // V4L2 offers multiple white balance interfaces. Try the advanced one before
   // falling
   // back to the simpler version.
   // Modes from each API that don't match up:
   // Android: WARM_FLUORESCENT, TWILIGHT.
   // V4L2: FLUORESCENT_H, HORIZON, FLASH.
-  /* TODO(b/30900438): Use v4l2 control factory.
-  std::unique_ptr<PartialMetadataInterface> awb(
-      V4L2EnumControl::NewV4L2EnumControl(
-          device_,
-          V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE,
-          ANDROID_CONTROL_AWB_MODE,
-          ANDROID_CONTROL_AWB_AVAILABLE_MODES,
+  std::unique_ptr<PartialMetadataInterface> awb(V4L2Control<
+                                                uint8_t>(
+      ControlType::kMenu,
+      ANDROID_CONTROL_AWB_MODE,
+      ANDROID_CONTROL_AWB_AVAILABLE_MODES,
+      device_,
+      V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE,
+      std::shared_ptr<ConverterInterface<uint8_t, int32_t>>(new EnumConverter(
           {{V4L2_WHITE_BALANCE_MANUAL, ANDROID_CONTROL_AWB_MODE_OFF},
            {V4L2_WHITE_BALANCE_AUTO, ANDROID_CONTROL_AWB_MODE_AUTO},
            {V4L2_WHITE_BALANCE_INCANDESCENT,
@@ -88,73 +99,83 @@
            {V4L2_WHITE_BALANCE_DAYLIGHT, ANDROID_CONTROL_AWB_MODE_DAYLIGHT},
            {V4L2_WHITE_BALANCE_CLOUDY,
             ANDROID_CONTROL_AWB_MODE_CLOUDY_DAYLIGHT},
-           {V4L2_WHITE_BALANCE_SHADE, ANDROID_CONTROL_AWB_MODE_SHADE}}));
+           {V4L2_WHITE_BALANCE_SHADE, ANDROID_CONTROL_AWB_MODE_SHADE}}))));
   if (awb) {
     AddComponent(std::move(awb));
   } else {
     // Fall back to simpler AWB or even just an ignored control.
-    AddEnumControlOrDefault(
-        V4L2_CID_AUTO_WHITE_BALANCE,
+    AddComponent(V4L2ControlOrDefault<uint8_t>(
+        ControlType::kMenu,
         ANDROID_CONTROL_AWB_MODE,
         ANDROID_CONTROL_AWB_AVAILABLE_MODES,
-        {{0, ANDROID_CONTROL_AWB_MODE_OFF}, {1, ANDROID_CONTROL_AWB_MODE_AUTO}},
-        ANDROID_CONTROL_AWB_MODE_AUTO);
+        device_,
+        V4L2_CID_AUTO_WHITE_BALANCE,
+        std::shared_ptr<ConverterInterface<uint8_t, int32_t>>(
+            new EnumConverter({{0, ANDROID_CONTROL_AWB_MODE_OFF},
+                               {1, ANDROID_CONTROL_AWB_MODE_AUTO}})),
+        ANDROID_CONTROL_AWB_MODE_AUTO));
   }
-  */
   // TODO(b/30510395): subcomponents of scene modes
   // (may itself be a subcomponent of 3A).
   // Modes from each API that don't match up:
   // Android: FACE_PRIORITY, ACTION, NIGHT_PORTRAIT, THEATRE, STEADYPHOTO,
-  // BARCODE, HIGH_SPEED_VIDEO, SNOW (combined with BEACH in V4L2. Only BEACH
-  // is reported to avoid ambiguity).
+  // BARCODE, HIGH_SPEED_VIDEO.
   // V4L2: BACKLIGHT, DAWN_DUSK, FALL_COLORS, TEXT.
-  AddEnumControlOrDefault(
-      V4L2_CID_SCENE_MODE,
+  AddComponent(V4L2ControlOrDefault<uint8_t>(
+      ControlType::kMenu,
       ANDROID_CONTROL_SCENE_MODE,
       ANDROID_CONTROL_AVAILABLE_SCENE_MODES,
-      {{V4L2_SCENE_MODE_NONE, ANDROID_CONTROL_SCENE_MODE_DISABLED},
-       {V4L2_SCENE_MODE_BEACH_SNOW, ANDROID_CONTROL_SCENE_MODE_BEACH},
-       {V4L2_SCENE_MODE_CANDLE_LIGHT, ANDROID_CONTROL_SCENE_MODE_CANDLELIGHT},
-       {V4L2_SCENE_MODE_FIREWORKS, ANDROID_CONTROL_SCENE_MODE_FIREWORKS},
-       {V4L2_SCENE_MODE_LANDSCAPE, ANDROID_CONTROL_SCENE_MODE_LANDSCAPE},
-       {V4L2_SCENE_MODE_NIGHT, ANDROID_CONTROL_SCENE_MODE_NIGHT},
-       {V4L2_SCENE_MODE_PARTY_INDOOR, ANDROID_CONTROL_SCENE_MODE_PARTY},
-       {V4L2_SCENE_MODE_SPORTS, ANDROID_CONTROL_SCENE_MODE_SPORTS},
-       {V4L2_SCENE_MODE_SUNSET, ANDROID_CONTROL_SCENE_MODE_SUNSET}},
-      ANDROID_CONTROL_SCENE_MODE_DISABLED);
+      device_,
+      V4L2_CID_SCENE_MODE,
+      std::shared_ptr<ConverterInterface<uint8_t, int32_t>>(new EnumConverter(
+          {{V4L2_SCENE_MODE_NONE, ANDROID_CONTROL_SCENE_MODE_DISABLED},
+           {V4L2_SCENE_MODE_BEACH_SNOW, ANDROID_CONTROL_SCENE_MODE_BEACH},
+           {V4L2_SCENE_MODE_BEACH_SNOW, ANDROID_CONTROL_SCENE_MODE_SNOW},
+           {V4L2_SCENE_MODE_CANDLE_LIGHT,
+            ANDROID_CONTROL_SCENE_MODE_CANDLELIGHT},
+           {V4L2_SCENE_MODE_FIREWORKS, ANDROID_CONTROL_SCENE_MODE_FIREWORKS},
+           {V4L2_SCENE_MODE_LANDSCAPE, ANDROID_CONTROL_SCENE_MODE_LANDSCAPE},
+           {V4L2_SCENE_MODE_NIGHT, ANDROID_CONTROL_SCENE_MODE_NIGHT},
+           {V4L2_SCENE_MODE_PARTY_INDOOR, ANDROID_CONTROL_SCENE_MODE_PARTY},
+           {V4L2_SCENE_MODE_SPORTS, ANDROID_CONTROL_SCENE_MODE_SPORTS},
+           {V4L2_SCENE_MODE_SUNSET, ANDROID_CONTROL_SCENE_MODE_SUNSET}})),
+      ANDROID_CONTROL_SCENE_MODE_DISABLED));
   // Modes from each API that don't match up:
   // Android: POSTERIZE, WHITEBOARD, BLACKBOARD.
   // V4L2: ANTIQUE, ART_FREEZE, EMBOSS, GRASS_GREEN, SKETCH, SKIN_WHITEN,
   // SKY_BLUE, SILHOUETTE, VIVID, SET_CBCR.
-  AddEnumControlOrDefault(
-      V4L2_CID_COLORFX,
+  AddComponent(V4L2ControlOrDefault<uint8_t>(
+      ControlType::kMenu,
       ANDROID_CONTROL_EFFECT_MODE,
       ANDROID_CONTROL_AVAILABLE_EFFECTS,
-      {{V4L2_COLORFX_NONE, ANDROID_CONTROL_EFFECT_MODE_OFF},
-       {V4L2_COLORFX_BW, ANDROID_CONTROL_EFFECT_MODE_MONO},
-       {V4L2_COLORFX_NEGATIVE, ANDROID_CONTROL_EFFECT_MODE_NEGATIVE},
-       {V4L2_COLORFX_SOLARIZATION, ANDROID_CONTROL_EFFECT_MODE_SOLARIZE},
-       {V4L2_COLORFX_SEPIA, ANDROID_CONTROL_EFFECT_MODE_SEPIA},
-       {V4L2_COLORFX_AQUA, ANDROID_CONTROL_EFFECT_MODE_AQUA}},
-      ANDROID_CONTROL_EFFECT_MODE_OFF);
+      device_,
+      V4L2_CID_COLORFX,
+      std::shared_ptr<ConverterInterface<uint8_t, int32_t>>(new EnumConverter(
+          {{V4L2_COLORFX_NONE, ANDROID_CONTROL_EFFECT_MODE_OFF},
+           {V4L2_COLORFX_BW, ANDROID_CONTROL_EFFECT_MODE_MONO},
+           {V4L2_COLORFX_NEGATIVE, ANDROID_CONTROL_EFFECT_MODE_NEGATIVE},
+           {V4L2_COLORFX_SOLARIZATION, ANDROID_CONTROL_EFFECT_MODE_SOLARIZE},
+           {V4L2_COLORFX_SEPIA, ANDROID_CONTROL_EFFECT_MODE_SEPIA},
+           {V4L2_COLORFX_AQUA, ANDROID_CONTROL_EFFECT_MODE_AQUA}})),
+      ANDROID_CONTROL_EFFECT_MODE_OFF));
 
   // Not sure if V4L2 does or doesn't do this, but HAL documentation says
   // all devices must support FAST, and FAST can be equivalent to OFF, so
   // either way it's fine to list.
-  AddComponent(
-      Control<uint8_t>::NoEffectMenuControl(ANDROID_EDGE_MODE,
-                                            ANDROID_EDGE_AVAILABLE_EDGE_MODES,
-                                            {ANDROID_EDGE_MODE_FAST}));
+  AddComponent(NoEffectMenuControl<uint8_t>(
+      ANDROID_EDGE_MODE,
+      ANDROID_EDGE_AVAILABLE_EDGE_MODES,
+      {ANDROID_EDGE_MODE_FAST}));
 
   // TODO(30510395): subcomponents of hotpixel.
   // No known V4L2 hot pixel correction. But it might be happening,
   // so we report FAST/HIGH_QUALITY.
-  AddComponent(Control<uint8_t>::NoEffectMenuControl(
+  AddComponent(NoEffectMenuControl<uint8_t>(
       ANDROID_HOT_PIXEL_MODE,
       ANDROID_HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES,
       {ANDROID_HOT_PIXEL_MODE_FAST, ANDROID_HOT_PIXEL_MODE_HIGH_QUALITY}));
   // ON only needs to be supported for RAW capable devices.
-  AddComponent(Control<uint8_t>::NoEffectMenuControl(
+  AddComponent(NoEffectMenuControl<uint8_t>(
       ANDROID_STATISTICS_HOT_PIXEL_MAP_MODE,
       ANDROID_STATISTICS_INFO_AVAILABLE_HOT_PIXEL_MAP_MODES,
       {ANDROID_STATISTICS_HOT_PIXEL_MAP_MODE_OFF}));
@@ -162,17 +183,17 @@
   // TODO(30510395): subcomponents focus/lens.
   // No way to actually get the aperture and focal length
   // in V4L2, but they're required keys, so fake them.
-  AddComponent(
-      Control<float>::NoEffectMenuControl(ANDROID_LENS_APERTURE,
-                                          ANDROID_LENS_INFO_AVAILABLE_APERTURES,
-                                          {2.0}));  // RPi camera v2 is f/2.0.
-  AddComponent(Control<float>::NoEffectMenuControl(
+  AddComponent(NoEffectMenuControl<float>(
+      ANDROID_LENS_APERTURE,
+      ANDROID_LENS_INFO_AVAILABLE_APERTURES,
+      {2.0}));  // RPi camera v2 is f/2.0.
+  AddComponent(NoEffectMenuControl<float>(
       ANDROID_LENS_FOCAL_LENGTH,
       ANDROID_LENS_INFO_AVAILABLE_FOCAL_LENGTHS,
       {3.04}));  // RPi camera v2 is 3.04mm.
   // No known way to get filter densities from V4L2,
   // report 0 to indicate this control is not supported.
-  AddComponent(Control<float>::NoEffectMenuControl(
+  AddComponent(NoEffectMenuControl<float>(
       ANDROID_LENS_FILTER_DENSITY,
       ANDROID_LENS_INFO_AVAILABLE_FILTER_DENSITIES,
       {0.0}));
@@ -183,31 +204,35 @@
   // info.hyperfocalDistance not required for UNCALIBRATED.
   // No known V4L2 lens shading. But it might be happening,
   // so report FAST/HIGH_QUALITY.
-  AddComponent(Control<uint8_t>::NoEffectMenuControl(
+  AddComponent(NoEffectMenuControl<uint8_t>(
       ANDROID_SHADING_MODE,
       ANDROID_SHADING_AVAILABLE_MODES,
       {ANDROID_SHADING_MODE_FAST, ANDROID_SHADING_MODE_HIGH_QUALITY}));
   // ON only needs to be supported for RAW capable devices.
-  AddComponent(Control<uint8_t>::NoEffectMenuControl(
+  AddComponent(NoEffectMenuControl<uint8_t>(
       ANDROID_STATISTICS_LENS_SHADING_MAP_MODE,
       ANDROID_STATISTICS_INFO_AVAILABLE_LENS_SHADING_MAP_MODES,
       {ANDROID_STATISTICS_LENS_SHADING_MAP_MODE_OFF}));
   // V4L2 doesn't differentiate between OPTICAL and VIDEO stabilization,
   // so only report one (and report the other as OFF).
-  AddEnumControlOrDefault(V4L2_CID_IMAGE_STABILIZATION,
-                          ANDROID_CONTROL_VIDEO_STABILIZATION_MODE,
-                          ANDROID_CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES,
-                          {{0, ANDROID_CONTROL_VIDEO_STABILIZATION_MODE_OFF},
-                           {1, ANDROID_CONTROL_VIDEO_STABILIZATION_MODE_ON}},
-                          ANDROID_CONTROL_VIDEO_STABILIZATION_MODE_OFF);
-  AddComponent(Control<uint8_t>::NoEffectMenuControl(
+  AddComponent(V4L2ControlOrDefault<uint8_t>(
+      ControlType::kMenu,
+      ANDROID_CONTROL_VIDEO_STABILIZATION_MODE,
+      ANDROID_CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES,
+      device_,
+      V4L2_CID_IMAGE_STABILIZATION,
+      std::shared_ptr<ConverterInterface<uint8_t, int32_t>>(new EnumConverter(
+          {{0, ANDROID_CONTROL_VIDEO_STABILIZATION_MODE_OFF},
+           {1, ANDROID_CONTROL_VIDEO_STABILIZATION_MODE_ON}})),
+      ANDROID_CONTROL_VIDEO_STABILIZATION_MODE_OFF));
+  AddComponent(NoEffectMenuControl<uint8_t>(
       ANDROID_LENS_OPTICAL_STABILIZATION_MODE,
       ANDROID_LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION,
       {ANDROID_LENS_OPTICAL_STABILIZATION_MODE_OFF}));
 
   // Unable to control noise reduction in V4L2 devices,
   // but FAST is allowed to be the same as OFF.
-  AddComponent(Control<uint8_t>::NoEffectMenuControl(
+  AddComponent(NoEffectMenuControl<uint8_t>(
       ANDROID_NOISE_REDUCTION_MODE,
       ANDROID_NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES,
       {ANDROID_NOISE_REDUCTION_MODE_FAST}));
@@ -216,7 +241,7 @@
   // For now, no thumbnails available (only [0,0], the "no thumbnail" size).
   // TODO(b/29580107): Could end up with a mismatch between request & result,
   // since V4L2 doesn't actually allow for thumbnail size control.
-  AddComponent(Control<std::array<int32_t, 2>>::NoEffectMenuControl(
+  AddComponent(NoEffectMenuControl<std::array<int32_t, 2>>(
       ANDROID_JPEG_THUMBNAIL_SIZE,
       ANDROID_JPEG_AVAILABLE_THUMBNAIL_SIZES,
       {{{0, 0}}}));
@@ -265,7 +290,7 @@
 
   // TODO(30510395): subcomponents of face detection.
   // Face detection not supported.
-  AddComponent(Control<uint8_t>::NoEffectMenuControl(
+  AddComponent(NoEffectMenuControl<uint8_t>(
       ANDROID_STATISTICS_FACE_DETECT_MODE,
       ANDROID_STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES,
       {ANDROID_STATISTICS_FACE_DETECT_MODE_OFF}));
@@ -288,20 +313,4 @@
   HAL_LOG_ENTER();
 }
 
-void V4L2Metadata::AddEnumControlOrDefault(
-    int v4l2_control,
-    int32_t control_tag,
-    int32_t options_tag,
-    const std::map<int32_t, uint8_t>& v4l2_to_metadata,
-    uint8_t default_value) {
-  HAL_LOG_ENTER();
-  // TODO(b/30900438): Replace this function with a V4L2 control factory.
-
-  std::unique_ptr<PartialMetadataInterface> control(
-      Control<uint8_t>::NoEffectMenuControl(
-          control_tag, options_tag, {default_value}));
-
-  AddComponent(std::move(control));
-}
-
 }  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/v4l2_metadata.h b/modules/camera/3_4/v4l2_metadata.h
index a046d3f..29e69b9 100644
--- a/modules/camera/3_4/v4l2_metadata.h
+++ b/modules/camera/3_4/v4l2_metadata.h
@@ -34,15 +34,6 @@
   virtual ~V4L2Metadata();
 
  private:
-  // Attempt to construct and add an enum control. If construction fails,
-  // use an IgnoredControl with only the default value instead.
-  void AddEnumControlOrDefault(
-      int v4l2_control,
-      int32_t control_tag,
-      int32_t options_tag,
-      const std::map<int32_t, uint8_t>& v4l2_to_metadata,
-      uint8_t default_value);
-
   // Access to the device.
   std::shared_ptr<V4L2Wrapper> device_;