Switch to composition over inheritance.

Switches v4l2_metadata over to the updated controls/properties
using inheritance over composition. Slight regression
in that the V4L2-based control factory method was removed,
will be replaced (and improved) in a CL soon.

BUG: 30140438
TEST: unit tests pass
Change-Id: I636fef67cd3ceeb3ebf9853d04b068988c4c4944
diff --git a/modules/camera/3_4/Android.mk b/modules/camera/3_4/Android.mk
index 7c8f430..1e7f4d6 100644
--- a/modules/camera/3_4/Android.mk
+++ b/modules/camera/3_4/Android.mk
@@ -41,7 +41,6 @@
   camera.cpp \
   metadata/enum_converter.cpp \
   metadata/metadata.cpp \
-  metadata/v4l2_enum_control.cpp \
   stream.cpp \
   stream_format.cpp \
   v4l2_camera.cpp \
@@ -53,20 +52,17 @@
 v4l2_test_files := \
   metadata/control_test.cpp \
   metadata/enum_converter_test.cpp \
-  metadata/fixed_property_test.cpp \
   metadata/ignored_control_delegate_test.cpp \
-  metadata/ignored_control_test.cpp \
   metadata/map_converter_test.cpp \
   metadata/menu_control_options_test.cpp \
   metadata/metadata_test.cpp \
   metadata/no_effect_control_delegate_test.cpp \
-  metadata/optioned_control_test.cpp \
+  metadata/property_test.cpp \
   metadata/ranged_converter_test.cpp \
   metadata/slider_control_options_test.cpp \
   metadata/tagged_control_delegate_test.cpp \
   metadata/tagged_control_options_test.cpp \
   metadata/v4l2_control_delegate_test.cpp \
-  metadata/v4l2_enum_control_test.cpp \
 
 # V4L2 Camera HAL.
 # ==============================================================================
diff --git a/modules/camera/3_4/metadata/control.h b/modules/camera/3_4/metadata/control.h
index eff8c2f..199ecd0 100644
--- a/modules/camera/3_4/metadata/control.h
+++ b/modules/camera/3_4/metadata/control.h
@@ -22,18 +22,29 @@
 #include <system/camera_metadata.h>
 
 #include "../common.h"
+#include "menu_control_options.h"
 #include "metadata_common.h"
-#include "tagged_partial_metadata.h"
+#include "no_effect_control_delegate.h"
+#include "partial_metadata_interface.h"
+#include "tagged_control_delegate.h"
+#include "tagged_control_options.h"
 
 namespace v4l2_camera_hal {
 
 // A Control is a PartialMetadata with values that can be gotten/set.
 template <typename T>
-class Control : public TaggedPartialMetadata {
+class Control : public PartialMetadataInterface {
  public:
-  Control(int32_t control_tag, std::vector<int32_t> static_tags = {});
+  // Options are optional (i.e. nullable), delegate is not.
+  Control(std::unique_ptr<TaggedControlDelegate<T>> delegate,
+          std::unique_ptr<TaggedControlOptions<T>> options = nullptr);
 
-  // Child classes still need to implement PopulateStaticFields.
+  virtual std::vector<int32_t> StaticTags() const override;
+  virtual std::vector<int32_t> ControlTags() const override;
+  virtual std::vector<int32_t> DynamicTags() const override;
+
+  virtual int PopulateStaticFields(
+      android::CameraMetadata* metadata) const override;
   virtual int PopulateDynamicFields(
       android::CameraMetadata* metadata) const override;
   virtual bool SupportsRequestValues(
@@ -41,20 +52,15 @@
   virtual int SetRequestValues(
       const android::CameraMetadata& metadata) override;
 
- protected:
-  // Simpler access to tag.
-  inline int32_t ControlTag() const { return ControlTags()[0]; }
+  // 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);
 
-  // Get/Set the control value. Return non-0 on failure.
-  // Controls are allowed to be unreliable, so SetValue is best-effort;
-  // GetValue immediately after may not match (SetValue may, for example,
-  // automatically replace invalid values with valid ones,
-  // or have a delay before setting the requested value).
-  // GetValue should always indicate the actual current value of the control.
-  virtual int GetValue(T* value) const = 0;
-  virtual int SetValue(const T& value) = 0;
-  // Helper to check if val is supported by this control.
-  virtual bool IsSupported(const T& val) const = 0;
+ protected:
+  std::unique_ptr<TaggedControlDelegate<T>> delegate_;
+  std::unique_ptr<TaggedControlOptions<T>> options_;
 
   DISALLOW_COPY_AND_ASSIGN(Control);
 };
@@ -62,24 +68,55 @@
 // -----------------------------------------------------------------------------
 
 template <typename T>
-Control<T>::Control(int32_t control_tag, std::vector<int32_t> static_tags)
-    // Controls use the same tag for setting the control
-    // and getting the dynamic value.
-    : TaggedPartialMetadata(static_tags, {control_tag}, {control_tag}) {
+Control<T>::Control(std::unique_ptr<TaggedControlDelegate<T>> delegate,
+                    std::unique_ptr<TaggedControlOptions<T>> options)
+    : delegate_(std::move(delegate)), options_(std::move(options)) {
   HAL_LOG_ENTER();
 }
 
 template <typename T>
+std::vector<int32_t> Control<T>::StaticTags() const {
+  std::vector<int32_t> result;
+  if (options_) {
+    result.push_back(options_->tag());
+  }
+  return result;
+}
+
+template <typename T>
+std::vector<int32_t> Control<T>::ControlTags() const {
+  return {delegate_->tag()};
+}
+
+template <typename T>
+std::vector<int32_t> Control<T>::DynamicTags() const {
+  return {delegate_->tag()};
+}
+
+template <typename T>
+int Control<T>::PopulateStaticFields(android::CameraMetadata* metadata) const {
+  HAL_LOG_ENTER();
+
+  if (!options_) {
+    HAL_LOGV("No options for control, nothing to populate.");
+    return 0;
+  }
+
+  return UpdateMetadata(
+      metadata, options_->tag(), options_->MetadataRepresentation());
+}
+
+template <typename T>
 int Control<T>::PopulateDynamicFields(android::CameraMetadata* metadata) const {
   HAL_LOG_ENTER();
 
   // Populate the current setting.
   T value;
-  int res = GetValue(&value);
+  int res = delegate_->GetValue(&value);
   if (res) {
     return res;
   }
-  return UpdateMetadata(metadata, ControlTag(), value);
+  return UpdateMetadata(metadata, delegate_->tag(), value);
 }
 
 template <typename T>
@@ -91,18 +128,25 @@
     return true;
   }
 
-  // Check that the requested setting is in the supported options.
+  // Get the requested setting for this control.
   T requested;
-  int res = SingleTagValue(metadata, ControlTag(), &requested);
+  int res = SingleTagValue(metadata, delegate_->tag(), &requested);
   if (res == -ENOENT) {
     // Nothing requested of this control, that's fine.
     return true;
   } else if (res) {
     HAL_LOGE("Failure while searching for request value for tag %d",
-             ControlTag());
+             delegate_->tag());
     return false;
   }
-  return IsSupported(requested);
+
+  // Check that the requested setting is in the supported options.
+  if (!options_) {
+    HAL_LOGV("No options for control %d; request implicitly supported.",
+             delegate_->tag());
+    return true;
+  }
+  return options_->IsSupported(requested);
 }
 
 template <typename T>
@@ -115,17 +159,41 @@
 
   // Get the requested value.
   T requested;
-  int res = SingleTagValue(metadata, ControlTag(), &requested);
+  int res = SingleTagValue(metadata, delegate_->tag(), &requested);
   if (res == -ENOENT) {
     // Nothing requested of this control, nothing to do.
     return 0;
   } else if (res) {
     HAL_LOGE("Failure while searching for request value for tag %d",
-             ControlTag());
+             delegate_->tag());
     return res;
   }
 
-  return SetValue(requested);
+  // Check that the value is supported.
+  if (options_ && !options_->IsSupported(requested)) {
+    HAL_LOGE("Unsupported value requested for control %d.", delegate_->tag());
+    return -EINVAL;
+  }
+
+  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
diff --git a/modules/camera/3_4/metadata/control_test.cpp b/modules/camera/3_4/metadata/control_test.cpp
index ef4b6af..b414ad3 100644
--- a/modules/camera/3_4/metadata/control_test.cpp
+++ b/modules/camera/3_4/metadata/control_test.cpp
@@ -16,16 +16,17 @@
 
 #include "control.h"
 
-#include <array>
-
 #include <camera/CameraMetadata.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include "control_delegate_interface_mock.h"
+#include "control_options_interface_mock.h"
 #include "metadata_common.h"
 #include "test_common.h"
 
 using testing::AtMost;
+using testing::Expectation;
 using testing::Return;
 using testing::ReturnRef;
 using testing::SetArgPointee;
@@ -36,46 +37,106 @@
 
 class ControlTest : public Test {
  protected:
-  // A subclass of Control with the pure virtual methods mocked out.
-  template <typename T>
-  class MockControl : public Control<T> {
-   public:
-    MockControl(int32_t control_tag) : Control<T>(control_tag){};
-    MOCK_CONST_METHOD1_T(PopulateStaticFields,
-                         int(android::CameraMetadata* metadata));
-    MOCK_CONST_METHOD1_T(GetValue, int(T* value));
-    MOCK_METHOD1_T(SetValue, int(const T& value));
-    MOCK_CONST_METHOD1_T(IsSupported, bool(const T& value));
-  };
-
   virtual void SetUp() {
-    control_.reset(new MockControl<uint8_t>(control_tag_));
+    mock_delegate_.reset(new ControlDelegateInterfaceMock<uint8_t>());
+    mock_options_.reset(new ControlOptionsInterfaceMock<uint8_t>());
+    // Nullify control so an error will be thrown if a test doesn't call
+    // PrepareControl.
+    control_.reset();
   }
 
-  std::unique_ptr<MockControl<uint8_t>> control_;
+  virtual void PrepareControl(bool with_options = true) {
+    // Use this method after all the EXPECT_CALLs to pass ownership of the mocks
+    // to the device.
+    std::unique_ptr<TaggedControlDelegate<uint8_t>> delegate =
+        std::make_unique<TaggedControlDelegate<uint8_t>>(
+            delegate_tag_, std::move(mock_delegate_));
+    std::unique_ptr<TaggedControlOptions<uint8_t>> options =
+        std::make_unique<TaggedControlOptions<uint8_t>>(
+            options_tag_, std::move(mock_options_));
+    if (with_options) {
+      control_.reset(
+          new Control<uint8_t>(std::move(delegate), std::move(options)));
+    } else {
+      control_.reset(new Control<uint8_t>(std::move(delegate)));
+    }
+  }
+
+  std::unique_ptr<Control<uint8_t>> control_;
+  std::unique_ptr<ControlDelegateInterfaceMock<uint8_t>> mock_delegate_;
+  std::unique_ptr<ControlOptionsInterfaceMock<uint8_t>> mock_options_;
 
   // Need tags that match the data type (uint8_t) being passed.
-  static constexpr int32_t control_tag_ =
-      ANDROID_COLOR_CORRECTION_ABERRATION_MODE;
+  const int32_t delegate_tag_ = ANDROID_COLOR_CORRECTION_ABERRATION_MODE;
+  const int32_t options_tag_ =
+      ANDROID_COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES;
 };
 
 TEST_F(ControlTest, Tags) {
-  // Should have no static tags by default.
-  EXPECT_EQ(control_->StaticTags().size(), 0);
-  // Controls use the same tag for getting and setting.
-  // The macro doesn't like the static variables
-  // being passed directly to assertions.
-  int32_t expected_tag = control_tag_;
+  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], expected_tag);
+  EXPECT_EQ(control_->ControlTags()[0], delegate_tag_);
   ASSERT_EQ(control_->DynamicTags().size(), 1);
-  EXPECT_EQ(control_->DynamicTags()[0], expected_tag);
+  EXPECT_EQ(control_->DynamicTags()[0], delegate_tag_);
+}
+
+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_);
+}
+
+TEST_F(ControlTest, PopulateStatic) {
+  std::vector<uint8_t> expected{1, 10, 20};
+  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);
+}
+
+TEST_F(ControlTest, PopulateStaticNoOptions) {
+  PrepareControl(false);
+  android::CameraMetadata metadata;
+  ASSERT_EQ(control_->PopulateStaticFields(&metadata), 0);
+  // Should not have added any entry.
+  EXPECT_TRUE(metadata.isEmpty());
 }
 
 TEST_F(ControlTest, PopulateDynamic) {
   uint8_t test_option = 99;
-  EXPECT_CALL(*control_, GetValue(_))
+  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);
+}
+
+TEST_F(ControlTest, PopulateDynamicNoOptions) {
+  // Lack of options shouldn't change anything for PopulateDynamic.
+  uint8_t test_option = 99;
+  EXPECT_CALL(*mock_delegate_, GetValue(_))
+      .WillOnce(DoAll(SetArgPointee<0>(test_option), Return(0)));
+  PrepareControl(false);
 
   android::CameraMetadata metadata;
   EXPECT_EQ(control_->PopulateDynamicFields(&metadata), 0);
@@ -83,12 +144,13 @@
   // Should only have added 1 entry.
   EXPECT_EQ(metadata.entryCount(), 1);
   // Should have added the right entry.
-  ExpectMetadataEq(metadata, control_tag_, test_option);
+  ExpectMetadataEq(metadata, delegate_tag_, test_option);
 }
 
 TEST_F(ControlTest, PopulateDynamicFail) {
   int err = -99;
-  EXPECT_CALL(*control_, GetValue(_)).WillOnce(Return(err));
+  EXPECT_CALL(*mock_delegate_, GetValue(_)).WillOnce(Return(err));
+  PrepareControl();
 
   android::CameraMetadata metadata;
   EXPECT_EQ(control_->PopulateDynamicFields(&metadata), err);
@@ -100,28 +162,31 @@
 TEST_F(ControlTest, SupportsRequest) {
   android::CameraMetadata metadata;
   uint8_t test_option = 123;
-  ASSERT_EQ(UpdateMetadata(&metadata, control_tag_, test_option), 0);
+  ASSERT_EQ(UpdateMetadata(&metadata, delegate_tag_, test_option), 0);
 
-  EXPECT_CALL(*control_, IsSupported(test_option)).WillOnce(Return(true));
+  EXPECT_CALL(*mock_options_, IsSupported(test_option)).WillOnce(Return(true));
+  PrepareControl();
+
   EXPECT_EQ(control_->SupportsRequestValues(metadata), true);
 }
 
-TEST_F(ControlTest, ArraySupportsRequest) {
+TEST_F(ControlTest, SupportsRequestNoOptions) {
   android::CameraMetadata metadata;
-  std::array<uint8_t, 2> test_option = {{12, 34}};
-  ASSERT_EQ(UpdateMetadata(&metadata, control_tag_, test_option), 0);
+  uint8_t test_option = 123;
+  ASSERT_EQ(UpdateMetadata(&metadata, delegate_tag_, test_option), 0);
+  PrepareControl(false);
 
-  MockControl<std::array<uint8_t, 2>> test_control(control_tag_);
-  EXPECT_CALL(test_control, IsSupported(test_option)).WillOnce(Return(true));
-  EXPECT_EQ(test_control.SupportsRequestValues(metadata), true);
+  EXPECT_EQ(control_->SupportsRequestValues(metadata), true);
 }
 
 TEST_F(ControlTest, SupportsRequestFail) {
   android::CameraMetadata metadata;
   uint8_t test_option = 123;
-  ASSERT_EQ(UpdateMetadata(&metadata, control_tag_, test_option), 0);
+  ASSERT_EQ(UpdateMetadata(&metadata, delegate_tag_, test_option), 0);
 
-  EXPECT_CALL(*control_, IsSupported(test_option)).WillOnce(Return(false));
+  EXPECT_CALL(*mock_options_, IsSupported(test_option)).WillOnce(Return(false));
+  PrepareControl();
+
   EXPECT_EQ(control_->SupportsRequestValues(metadata), false);
 }
 
@@ -129,77 +194,140 @@
   // Start with a request for multiple values.
   android::CameraMetadata metadata;
   std::vector<uint8_t> test_data = {1, 2, 3};
-  ASSERT_EQ(UpdateMetadata(&metadata, control_tag_, test_data), 0);
+  ASSERT_EQ(UpdateMetadata(&metadata, delegate_tag_, test_data), 0);
+  PrepareControl();
   EXPECT_EQ(control_->SupportsRequestValues(metadata), false);
 }
 
-TEST_F(ControlTest, ArraySupportsRequestInvalidNumber) {
-  // Start with a request for a single (non-array) value.
+TEST_F(ControlTest, SupportsRequestInvalidNumberNoOptions) {
+  // Start with a request for multiple values.
   android::CameraMetadata metadata;
-  uint8_t test_data = 1;
-  ASSERT_EQ(UpdateMetadata(&metadata, control_tag_, test_data), 0);
-
-  MockControl<std::array<uint8_t, 2>> test_control(control_tag_);
-  EXPECT_EQ(test_control.SupportsRequestValues(metadata), false);
+  std::vector<uint8_t> test_data = {1, 2, 3};
+  ASSERT_EQ(UpdateMetadata(&metadata, delegate_tag_, test_data), 0);
+  PrepareControl(false);
+  // Not having any explicit options does not exempt a control
+  // from requiring the right number of values.
+  EXPECT_EQ(control_->SupportsRequestValues(metadata), false);
 }
 
 TEST_F(ControlTest, SupportsRequestEmpty) {
   android::CameraMetadata metadata;
+  PrepareControl();
   EXPECT_EQ(control_->SupportsRequestValues(metadata), true);
 }
 
 TEST_F(ControlTest, SetRequest) {
-  android::CameraMetadata metadata(1);
+  android::CameraMetadata metadata;
   uint8_t test_option = 123;
-  ASSERT_EQ(UpdateMetadata(&metadata, control_tag_, test_option), 0);
+  ASSERT_EQ(UpdateMetadata(&metadata, delegate_tag_, test_option), 0);
 
-  EXPECT_CALL(*control_, SetValue(test_option)).WillOnce(Return(0));
+  Expectation validation_check =
+      EXPECT_CALL(*mock_options_, IsSupported(test_option))
+          .WillOnce(Return(true));
+  EXPECT_CALL(*mock_delegate_, SetValue(test_option))
+      .After(validation_check)
+      .WillOnce(Return(0));
+  PrepareControl();
+
   // Make the request.
   ASSERT_EQ(control_->SetRequestValues(metadata), 0);
 }
 
-TEST_F(ControlTest, ArraySetRequest) {
+TEST_F(ControlTest, SetRequestNoOptions) {
   android::CameraMetadata metadata;
-  std::array<uint8_t, 2> test_option = {{12, 34}};
-  ASSERT_EQ(UpdateMetadata(&metadata, control_tag_, test_option), 0);
+  uint8_t test_option = 123;
+  ASSERT_EQ(UpdateMetadata(&metadata, delegate_tag_, test_option), 0);
 
-  MockControl<std::array<uint8_t, 2>> test_control(control_tag_);
-  EXPECT_CALL(test_control, SetValue(test_option)).WillOnce(Return(0));
-  EXPECT_EQ(test_control.SetRequestValues(metadata), 0);
+  // No options, no validation check.
+  EXPECT_CALL(*mock_delegate_, SetValue(test_option)).WillOnce(Return(0));
+  PrepareControl(false);
+
+  // Make the request.
+  ASSERT_EQ(control_->SetRequestValues(metadata), 0);
 }
 
-TEST_F(ControlTest, SetRequestFail) {
-  android::CameraMetadata metadata(1);
+TEST_F(ControlTest, SetRequestSettingFail) {
+  android::CameraMetadata metadata;
   uint8_t test_option = 123;
-  ASSERT_EQ(UpdateMetadata(&metadata, control_tag_, test_option), 0);
+  ASSERT_EQ(UpdateMetadata(&metadata, delegate_tag_, test_option), 0);
 
-  int err = -99;
-  EXPECT_CALL(*control_, SetValue(test_option)).WillOnce(Return(err));
+  int err = 99;
+  Expectation validation_check =
+      EXPECT_CALL(*mock_options_, IsSupported(test_option))
+          .WillOnce(Return(true));
+  EXPECT_CALL(*mock_delegate_, SetValue(test_option))
+      .After(validation_check)
+      .WillOnce(Return(err));
+  PrepareControl();
+
   EXPECT_EQ(control_->SetRequestValues(metadata), err);
 }
 
+TEST_F(ControlTest, SetRequestValidationFail) {
+  android::CameraMetadata metadata;
+  uint8_t test_option = 123;
+  ASSERT_EQ(UpdateMetadata(&metadata, delegate_tag_, test_option), 0);
+
+  EXPECT_CALL(*mock_options_, IsSupported(test_option)).WillOnce(Return(false));
+  PrepareControl();
+
+  EXPECT_EQ(control_->SetRequestValues(metadata), -EINVAL);
+}
+
 TEST_F(ControlTest, SetRequestInvalidNumber) {
   // Start with a request for multiple values.
   android::CameraMetadata metadata;
   std::vector<uint8_t> test_data = {1, 2, 3};
-  ASSERT_EQ(UpdateMetadata(&metadata, control_tag_, test_data), 0);
+  ASSERT_EQ(UpdateMetadata(&metadata, delegate_tag_, test_data), 0);
+
+  PrepareControl();
   EXPECT_EQ(control_->SetRequestValues(metadata), -EINVAL);
 }
 
-TEST_F(ControlTest, ArraySetRequestInvalidNumber) {
-  // Start with a request for a single (non-array) value.
+TEST_F(ControlTest, SetRequestInvalidNumberNoOptions) {
+  // Start with a request for multiple values.
   android::CameraMetadata metadata;
-  uint8_t test_data = 1;
-  ASSERT_EQ(UpdateMetadata(&metadata, control_tag_, test_data), 0);
+  std::vector<uint8_t> test_data = {1, 2, 3};
+  ASSERT_EQ(UpdateMetadata(&metadata, delegate_tag_, test_data), 0);
 
-  MockControl<std::array<uint8_t, 2>> test_control(control_tag_);
-  EXPECT_EQ(test_control.SetRequestValues(metadata), -EINVAL);
+  PrepareControl(false);
+  // Not having explicit options does not change that an incorrect
+  // number of values is invalid.
+  EXPECT_EQ(control_->SetRequestValues(metadata), -EINVAL);
 }
 
 TEST_F(ControlTest, SetRequestEmpty) {
   // Should do nothing.
   android::CameraMetadata metadata;
+  PrepareControl();
   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/metadata/fixed_property.h b/modules/camera/3_4/metadata/fixed_property.h
deleted file mode 100644
index 31f7a82..0000000
--- a/modules/camera/3_4/metadata/fixed_property.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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_FIXED_PROPERTY_H_
-#define V4L2_CAMERA_HAL_METADATA_FIXED_PROPERTY_H_
-
-#include "property.h"
-
-namespace v4l2_camera_hal {
-
-// A property with a fixed value set at creation.
-template <typename T>
-class FixedProperty : public Property<T> {
- public:
-  FixedProperty(int32_t tag, T value)
-      : Property<T>(tag), value_(std::move(value)){};
-  virtual ~FixedProperty(){};
-
- private:
-  virtual const T& Value() const override { return value_; };
-
-  const T value_;
-};
-
-}  // namespace v4l2_camera_hal
-
-#endif  // V4L2_CAMERA_HAL_METADATA_FIXED_PROPERTY_H_
diff --git a/modules/camera/3_4/metadata/ignored_control.h b/modules/camera/3_4/metadata/ignored_control.h
deleted file mode 100644
index aea8988..0000000
--- a/modules/camera/3_4/metadata/ignored_control.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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_IGNORED_CONTROL_H_
-#define V4L2_CAMERA_HAL_METADATA_IGNORED_CONTROL_H_
-
-#include <vector>
-
-#include <gtest/gtest_prod.h>
-
-#include "../common.h"
-#include "optioned_control.h"
-
-namespace v4l2_camera_hal {
-
-// A IgnoredControl is a PartialMetadata with a few static options that can
-// be chosen from, but they do nothing.
-template <typename T>
-class IgnoredControl : public OptionedControl<T> {
- public:
-  IgnoredControl(int32_t control_tag,
-                 int32_t options_tag,
-                 std::vector<T> options,
-                 T default_option)
-      : OptionedControl<T>(control_tag, options_tag, options),
-        // Note: default option is not enforced as being in |options|,
-        // but it may be confusing if it isn't.
-        current_setting_(default_option) {
-    HAL_LOG_ENTER();
-  };
-
- protected:
-  virtual int GetValue(T* value) const override {
-    HAL_LOG_ENTER();
-    *value = current_setting_;
-    return 0;
-  };
-  virtual int SetValue(const T& value) override {
-    HAL_LOG_ENTER();
-    if (!OptionedControl<T>::IsSupported(value)) {
-      return -EINVAL;
-    }
-    current_setting_ = value;
-    return 0;
-  };
-
- private:
-  T current_setting_;
-
-  FRIEND_TEST(IgnoredControlTest, GetDefaultValue);
-  FRIEND_TEST(IgnoredControlTest, SetAndGetValue);
-  FRIEND_TEST(IgnoredControlTest, SetAndGetBadValue);
-  DISALLOW_COPY_AND_ASSIGN(IgnoredControl);
-};
-
-}  // namespace v4l2_camera_hal
-
-#endif  // V4L2_CAMERA_HAL_METADATA_IGNORED_CONTROL_H_
diff --git a/modules/camera/3_4/metadata/ignored_control_test.cpp b/modules/camera/3_4/metadata/ignored_control_test.cpp
deleted file mode 100644
index 04408d2..0000000
--- a/modules/camera/3_4/metadata/ignored_control_test.cpp
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * 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 "ignored_control.h"
-
-#include <array>
-
-#include <camera/CameraMetadata.h>
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-
-#include "array_vector.h"
-
-using testing::AtMost;
-using testing::Return;
-using testing::ReturnRef;
-using testing::Test;
-using testing::_;
-
-namespace v4l2_camera_hal {
-
-class IgnoredControlTest : public Test {
- protected:
-  IgnoredControlTest()
-      : options_({ANDROID_COLOR_CORRECTION_ABERRATION_MODE_OFF,
-                  ANDROID_COLOR_CORRECTION_ABERRATION_MODE_FAST,
-                  ANDROID_COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY}),
-        default_option_(options_[2]) {}
-
-  virtual void SetUp() {
-    control_.reset(new IgnoredControl<uint8_t>(
-        control_tag_, options_tag_, options_, default_option_));
-  }
-
-  std::unique_ptr<IgnoredControl<uint8_t>> control_;
-
-  // Need tags that match the data type (uint8_t) being passed.
-  static constexpr int32_t options_tag_ =
-      ANDROID_COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES;
-  static constexpr int32_t control_tag_ =
-      ANDROID_COLOR_CORRECTION_ABERRATION_MODE;
-
-  const std::vector<uint8_t> options_;
-  const uint8_t default_option_;
-};
-
-TEST_F(IgnoredControlTest, Tags) {
-  // The macro doesn't like the static variables
-  // being passed directly to assertions.
-  int32_t expected_tag = options_tag_;
-  ASSERT_EQ(control_->StaticTags().size(), 1);
-  EXPECT_EQ(control_->StaticTags()[0], expected_tag);
-
-  // Controls use the same tag for getting and setting.
-  expected_tag = control_tag_;
-  ASSERT_EQ(control_->ControlTags().size(), 1);
-  EXPECT_EQ(control_->ControlTags()[0], expected_tag);
-  ASSERT_EQ(control_->DynamicTags().size(), 1);
-  EXPECT_EQ(control_->DynamicTags()[0], expected_tag);
-}
-
-TEST_F(IgnoredControlTest, GetDefaultValue) {
-  uint8_t value;
-  EXPECT_EQ(control_->GetValue(&value), 0);
-  EXPECT_EQ(value, default_option_);
-}
-
-TEST_F(IgnoredControlTest, SetAndGetValue) {
-  uint8_t new_value = options_[0];
-  // Make sure this isn't just testing overwriting the default with itself.
-  ASSERT_NE(new_value, default_option_);
-
-  EXPECT_EQ(control_->SetValue(new_value), 0);
-  uint8_t result = new_value + 1;  // Initialize to something incorrect.
-  EXPECT_EQ(control_->GetValue(&result), 0);
-  EXPECT_EQ(result, new_value);
-}
-
-TEST_F(IgnoredControlTest, SetAndGetBadValue) {
-  uint8_t unsupported_value = 99;
-  // Make sure this is actually unsupported
-  ASSERT_FALSE(control_->IsSupported(unsupported_value));
-
-  EXPECT_EQ(control_->SetValue(unsupported_value), -EINVAL);
-  // Value shouldn't have changed.
-  uint8_t result = default_option_ + 1;  // Initialize to something incorrect.
-  EXPECT_EQ(control_->GetValue(&result), 0);
-  EXPECT_EQ(result, default_option_);
-}
-
-}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/metadata/metadata.cpp b/modules/camera/3_4/metadata/metadata.cpp
index 61336fe..e18a454 100644
--- a/modules/camera/3_4/metadata/metadata.cpp
+++ b/modules/camera/3_4/metadata/metadata.cpp
@@ -19,6 +19,7 @@
 #include <camera/CameraMetadata.h>
 
 #include "../common.h"
+#include "metadata_common.h"
 
 namespace v4l2_camera_hal {
 
@@ -64,35 +65,38 @@
     }
 
     // Note what tags the component adds.
-    const std::vector<int32_t>* tags = &component->StaticTags();
-    static_tags.insert(static_tags.end(), tags->begin(), tags->end());
-    tags = &component->ControlTags();
-    control_tags.insert(control_tags.end(), tags->begin(), tags->end());
-    tags = &component->DynamicTags();
-    dynamic_tags.insert(dynamic_tags.end(), tags->begin(), tags->end());
+    std::vector<int32_t> tags = component->StaticTags();
+    std::move(tags.begin(),
+              tags.end(),
+              std::inserter(static_tags, static_tags.end()));
+    tags = component->ControlTags();
+    std::move(tags.begin(),
+              tags.end(),
+              std::inserter(control_tags, control_tags.end()));
+    tags = component->DynamicTags();
+    std::move(tags.begin(),
+              tags.end(),
+              std::inserter(dynamic_tags, dynamic_tags.end()));
   }
 
   // Populate the meta fields.
   static_tags.push_back(ANDROID_REQUEST_AVAILABLE_REQUEST_KEYS);
-  res = metadata->update(ANDROID_REQUEST_AVAILABLE_REQUEST_KEYS,
-                         control_tags.data(),
-                         control_tags.size());
+  res = UpdateMetadata(
+      metadata, ANDROID_REQUEST_AVAILABLE_REQUEST_KEYS, control_tags);
   if (res != android::OK) {
     HAL_LOGE("Failed to add request keys meta key.");
     return -ENODEV;
   }
   static_tags.push_back(ANDROID_REQUEST_AVAILABLE_RESULT_KEYS);
-  res = metadata->update(ANDROID_REQUEST_AVAILABLE_RESULT_KEYS,
-                         dynamic_tags.data(),
-                         dynamic_tags.size());
+  res = UpdateMetadata(
+      metadata, ANDROID_REQUEST_AVAILABLE_RESULT_KEYS, dynamic_tags);
   if (res != android::OK) {
     HAL_LOGE("Failed to add result keys meta key.");
     return -ENODEV;
   }
   static_tags.push_back(ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS);
-  res = metadata->update(ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS,
-                         static_tags.data(),
-                         static_tags.size());
+  res = UpdateMetadata(
+      metadata, ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS, static_tags);
   if (res != android::OK) {
     HAL_LOGE("Failed to add characteristics keys meta key.");
     return -ENODEV;
diff --git a/modules/camera/3_4/metadata/metadata_test.cpp b/modules/camera/3_4/metadata/metadata_test.cpp
index 62bb565..3ea94ba 100644
--- a/modules/camera/3_4/metadata/metadata_test.cpp
+++ b/modules/camera/3_4/metadata/metadata_test.cpp
@@ -28,7 +28,6 @@
 
 using testing::AtMost;
 using testing::Return;
-using testing::ReturnRef;
 using testing::Test;
 using testing::_;
 
@@ -88,12 +87,12 @@
   std::vector<int32_t> control_tags_2({7, 8});
   std::vector<int32_t> dynamic_tags_1({9, 10});
   std::vector<int32_t> dynamic_tags_2({11, 12});
-  EXPECT_CALL(*component1_, StaticTags()).WillOnce(ReturnRef(static_tags_1));
-  EXPECT_CALL(*component1_, ControlTags()).WillOnce(ReturnRef(control_tags_1));
-  EXPECT_CALL(*component1_, DynamicTags()).WillOnce(ReturnRef(dynamic_tags_1));
-  EXPECT_CALL(*component2_, StaticTags()).WillOnce(ReturnRef(static_tags_2));
-  EXPECT_CALL(*component2_, ControlTags()).WillOnce(ReturnRef(control_tags_2));
-  EXPECT_CALL(*component2_, DynamicTags()).WillOnce(ReturnRef(dynamic_tags_2));
+  EXPECT_CALL(*component1_, StaticTags()).WillOnce(Return(static_tags_1));
+  EXPECT_CALL(*component1_, ControlTags()).WillOnce(Return(control_tags_1));
+  EXPECT_CALL(*component1_, DynamicTags()).WillOnce(Return(dynamic_tags_1));
+  EXPECT_CALL(*component2_, StaticTags()).WillOnce(Return(static_tags_2));
+  EXPECT_CALL(*component2_, ControlTags()).WillOnce(Return(control_tags_2));
+  EXPECT_CALL(*component2_, DynamicTags()).WillOnce(Return(dynamic_tags_2));
 
   AddComponents();
   // Should succeed. If it didn't, no reason to continue checking output.
@@ -137,22 +136,22 @@
   // May or may not exit early, may still try to populate meta tags.
   EXPECT_CALL(*component1_, StaticTags())
       .Times(AtMost(1))
-      .WillOnce(ReturnRef(empty_tags_));
+      .WillOnce(Return(empty_tags_));
   EXPECT_CALL(*component1_, ControlTags())
       .Times(AtMost(1))
-      .WillOnce(ReturnRef(empty_tags_));
+      .WillOnce(Return(empty_tags_));
   EXPECT_CALL(*component1_, DynamicTags())
       .Times(AtMost(1))
-      .WillOnce(ReturnRef(empty_tags_));
+      .WillOnce(Return(empty_tags_));
   EXPECT_CALL(*component2_, StaticTags())
       .Times(AtMost(1))
-      .WillOnce(ReturnRef(empty_tags_));
+      .WillOnce(Return(empty_tags_));
   EXPECT_CALL(*component2_, ControlTags())
       .Times(AtMost(1))
-      .WillOnce(ReturnRef(empty_tags_));
+      .WillOnce(Return(empty_tags_));
   EXPECT_CALL(*component2_, DynamicTags())
       .Times(AtMost(1))
-      .WillOnce(ReturnRef(empty_tags_));
+      .WillOnce(Return(empty_tags_));
 
   AddComponents();
   // If any component errors, error should be returned
diff --git a/modules/camera/3_4/metadata/optioned_control.h b/modules/camera/3_4/metadata/optioned_control.h
deleted file mode 100644
index e205a59..0000000
--- a/modules/camera/3_4/metadata/optioned_control.h
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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_OPTIONED_CONTROL_H_
-#define V4L2_CAMERA_HAL_METADATA_OPTIONED_CONTROL_H_
-
-#include <algorithm>
-#include <vector>
-
-#include <gtest/gtest_prod.h>
-
-#include "../common.h"
-#include "control.h"
-#include "metadata_common.h"
-
-namespace v4l2_camera_hal {
-
-// An OptionedControl is a Control with a fixed list of options that can be
-// selected from.
-template <typename T>
-class OptionedControl : public Control<T> {
- public:
-  OptionedControl(int32_t control_tag,
-                  int32_t options_tag,
-                  std::vector<T> options);
-
-  virtual int PopulateStaticFields(
-      android::CameraMetadata* metadata) const override;
-
- protected:
-  // Simpler access to tag.
-  inline int32_t OptionsTag() const { return Control<T>::StaticTags()[0]; }
-
-  // Child classes still need to implement Get/Set from Control.
-  virtual bool IsSupported(const T& val) const override;
-
- private:
-  const std::vector<T> options_;
-
-  FRIEND_TEST(OptionedControlTest, IsSupported);
-
-  DISALLOW_COPY_AND_ASSIGN(OptionedControl);
-};
-
-// -----------------------------------------------------------------------------
-
-template <typename T>
-OptionedControl<T>::OptionedControl(int32_t control_tag,
-                                    int32_t options_tag,
-                                    std::vector<T> options)
-    : Control<T>(control_tag, {options_tag}), options_(std::move(options)) {
-  HAL_LOG_ENTER();
-}
-
-template <typename T>
-int OptionedControl<T>::PopulateStaticFields(
-    android::CameraMetadata* metadata) const {
-  HAL_LOG_ENTER();
-
-  // Populate the available options.
-  return UpdateMetadata(metadata, OptionsTag(), options_);
-}
-
-template <typename T>
-bool OptionedControl<T>::IsSupported(const T& value) const {
-  return (std::find(options_.begin(), options_.end(), value) != options_.end());
-}
-
-}  // namespace v4l2_camera_hal
-
-#endif  // V4L2_CAMERA_HAL_METADATA_OPTIONED_CONTROL_H_
diff --git a/modules/camera/3_4/metadata/optioned_control_test.cpp b/modules/camera/3_4/metadata/optioned_control_test.cpp
deleted file mode 100644
index bd9b705..0000000
--- a/modules/camera/3_4/metadata/optioned_control_test.cpp
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * 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 "optioned_control.h"
-
-#include <camera/CameraMetadata.h>
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-
-#include "test_common.h"
-
-using testing::AtMost;
-using testing::Return;
-using testing::ReturnRef;
-using testing::Test;
-using testing::_;
-
-namespace v4l2_camera_hal {
-
-class OptionedControlTest : public Test {
- protected:
-  // A subclass of OptionedControl with the pure virtual methods mocked out.
-  template <typename T>
-  class MockOptionedControl : public OptionedControl<T> {
-   public:
-    MockOptionedControl(int32_t control_tag,
-                        int32_t options_tag,
-                        std::vector<T> options)
-        : OptionedControl<T>(control_tag, options_tag, options){};
-    MOCK_CONST_METHOD1_T(GetValue, int(T* value));
-    MOCK_METHOD1_T(SetValue, int(const T& value));
-  };
-
-  OptionedControlTest()
-      : options_({ANDROID_COLOR_CORRECTION_ABERRATION_MODE_OFF,
-                  ANDROID_COLOR_CORRECTION_ABERRATION_MODE_FAST,
-                  ANDROID_COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY}) {}
-
-  virtual void SetUp() {
-    control_.reset(
-        new MockOptionedControl<uint8_t>(control_tag_, options_tag_, options_));
-  }
-
-  std::unique_ptr<OptionedControl<uint8_t>> control_;
-
-  // Need tags that match the data type (uint8_t) being passed.
-  static constexpr int32_t options_tag_ =
-      ANDROID_COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES;
-  static constexpr int32_t control_tag_ =
-      ANDROID_COLOR_CORRECTION_ABERRATION_MODE;
-
-  const std::vector<uint8_t> options_;
-};
-
-TEST_F(OptionedControlTest, Tags) {
-  // The macro doesn't like the static variables
-  // being passed directly to assertions.
-  int32_t expected_tag = options_tag_;
-  ASSERT_EQ(control_->StaticTags().size(), 1);
-  EXPECT_EQ(control_->StaticTags()[0], expected_tag);
-
-  // Controls use the same tag for getting and setting.
-  expected_tag = control_tag_;
-  ASSERT_EQ(control_->ControlTags().size(), 1);
-  EXPECT_EQ(control_->ControlTags()[0], expected_tag);
-  ASSERT_EQ(control_->DynamicTags().size(), 1);
-  EXPECT_EQ(control_->DynamicTags()[0], expected_tag);
-}
-
-TEST_F(OptionedControlTest, PopulateStatic) {
-  // Populate static fields.
-  android::CameraMetadata metadata;
-  ASSERT_EQ(control_->PopulateStaticFields(&metadata), 0);
-
-  // Check the results.
-  // Should only have added 1 entry.
-  EXPECT_EQ(metadata.entryCount(), 1);
-  // Should have added the right entry.
-  ExpectMetadataEq(metadata, options_tag_, options_);
-}
-
-TEST_F(OptionedControlTest, IsSupported) {
-  for (auto option : options_) {
-    EXPECT_TRUE(control_->IsSupported(option));
-  }
-  // And at least one unsupported.
-  EXPECT_FALSE(control_->IsSupported(99));
-}
-
-}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/metadata/partial_metadata_interface.h b/modules/camera/3_4/metadata/partial_metadata_interface.h
index d1a434f..cc1f9db 100644
--- a/modules/camera/3_4/metadata/partial_metadata_interface.h
+++ b/modules/camera/3_4/metadata/partial_metadata_interface.h
@@ -34,9 +34,9 @@
 
   // The metadata tags this partial metadata is responsible for.
   // See system/media/camera/docs/docs.html for descriptions of each tag.
-  virtual const std::vector<int32_t>& StaticTags() const = 0;
-  virtual const std::vector<int32_t>& ControlTags() const = 0;
-  virtual const std::vector<int32_t>& DynamicTags() const = 0;
+  virtual std::vector<int32_t> StaticTags() const = 0;
+  virtual std::vector<int32_t> ControlTags() const = 0;
+  virtual std::vector<int32_t> DynamicTags() const = 0;
 
   // Add all the static properties this partial metadata
   // is responsible for to |metadata|.
diff --git a/modules/camera/3_4/metadata/partial_metadata_interface_mock.h b/modules/camera/3_4/metadata/partial_metadata_interface_mock.h
index 63b24db..e380dee 100644
--- a/modules/camera/3_4/metadata/partial_metadata_interface_mock.h
+++ b/modules/camera/3_4/metadata/partial_metadata_interface_mock.h
@@ -28,9 +28,9 @@
 class PartialMetadataInterfaceMock : public PartialMetadataInterface {
  public:
   PartialMetadataInterfaceMock() : PartialMetadataInterface(){};
-  MOCK_CONST_METHOD0(StaticTags, const std::vector<int32_t>&());
-  MOCK_CONST_METHOD0(ControlTags, const std::vector<int32_t>&());
-  MOCK_CONST_METHOD0(DynamicTags, const std::vector<int32_t>&());
+  MOCK_CONST_METHOD0(StaticTags, std::vector<int32_t>());
+  MOCK_CONST_METHOD0(ControlTags, std::vector<int32_t>());
+  MOCK_CONST_METHOD0(DynamicTags, std::vector<int32_t>());
   MOCK_CONST_METHOD1(PopulateStaticFields,
                      int(android::CameraMetadata* metadata));
   MOCK_CONST_METHOD1(PopulateDynamicFields,
diff --git a/modules/camera/3_4/metadata/property.h b/modules/camera/3_4/metadata/property.h
index a3ed6bf..1ddd20f 100644
--- a/modules/camera/3_4/metadata/property.h
+++ b/modules/camera/3_4/metadata/property.h
@@ -19,20 +19,26 @@
 
 #include "../common.h"
 #include "metadata_common.h"
-#include "tagged_partial_metadata.h"
+#include "partial_metadata_interface.h"
 
 namespace v4l2_camera_hal {
 
 // A Property is a PartialMetadata that only has a single static tag.
 template <typename T>
-class Property : public TaggedPartialMetadata {
+class Property : public PartialMetadataInterface {
  public:
-  Property(int32_t tag) : TaggedPartialMetadata({tag}, {}, {}){};
+  Property(int32_t tag, T value) : tag_(tag), value_(value){};
+
+  virtual std::vector<int32_t> StaticTags() const override { return {tag_}; };
+
+  virtual std::vector<int32_t> ControlTags() const override { return {}; };
+
+  virtual std::vector<int32_t> DynamicTags() const override { return {}; };
 
   virtual int PopulateStaticFields(
       android::CameraMetadata* metadata) const override {
     HAL_LOG_ENTER();
-    return UpdateMetadata(metadata, Tag(), Value());
+    return UpdateMetadata(metadata, tag_, value_);
   };
 
   virtual int PopulateDynamicFields(
@@ -53,11 +59,9 @@
     return 0;
   };
 
- protected:
-  // Simpler access to tag.
-  inline int32_t Tag() const { return StaticTags()[0]; }
-  // Get the value associated with this property.
-  virtual const T& Value() const = 0;
+ private:
+  int32_t tag_;
+  T value_;
 };
 
 }  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/metadata/fixed_property_test.cpp b/modules/camera/3_4/metadata/property_test.cpp
similarity index 80%
rename from modules/camera/3_4/metadata/fixed_property_test.cpp
rename to modules/camera/3_4/metadata/property_test.cpp
index 07a1bce..50b9d4c 100644
--- a/modules/camera/3_4/metadata/fixed_property_test.cpp
+++ b/modules/camera/3_4/metadata/property_test.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "fixed_property.h"
+#include "property.h"
 
 #include <array>
 #include <vector>
@@ -35,7 +35,7 @@
 
 namespace v4l2_camera_hal {
 
-class FixedPropertyTest : public Test {
+class PropertyTest : public Test {
  protected:
   // Need tags that match the data types being passed.
   static constexpr int32_t byte_tag_ = ANDROID_CONTROL_SCENE_MODE_OVERRIDES;
@@ -44,8 +44,8 @@
   static constexpr int32_t int_tag2_ = ANDROID_JPEG_ORIENTATION;
 };
 
-TEST_F(FixedPropertyTest, Tags) {
-  FixedProperty<int32_t> property(int_tag_, 1);
+TEST_F(PropertyTest, Tags) {
+  Property<int32_t> property(int_tag_, 1);
 
   // Should have only the single tag it was constructed with.
   EXPECT_EQ(property.ControlTags().size(), 0);
@@ -56,10 +56,10 @@
   EXPECT_EQ(property.StaticTags()[0], expected_tag);
 }
 
-TEST_F(FixedPropertyTest, PopulateStaticSingleNumber) {
+TEST_F(PropertyTest, PopulateStaticSingleNumber) {
   // Set up a fixed property.
   int32_t data = 1234;
-  FixedProperty<int32_t> property(int_tag_, data);
+  Property<int32_t> property(int_tag_, data);
 
   // Populate static fields.
   android::CameraMetadata metadata;
@@ -72,10 +72,12 @@
   ExpectMetadataEq(metadata, int_tag_, data);
 }
 
-TEST_F(FixedPropertyTest, PopulateStaticVector) {
+// TODO(b/30839858): These tests are really testing the metadata_common.h
+// UpdateMetadata methods, and shouldn't be conducted here.
+TEST_F(PropertyTest, PopulateStaticVector) {
   // Set up a fixed property.
   std::vector<float> data({0.1, 2.3, 4.5, 6.7});
-  FixedProperty<std::vector<float>> property(float_tag_, data);
+  Property<std::vector<float>> property(float_tag_, data);
 
   // Populate static fields.
   android::CameraMetadata metadata;
@@ -88,10 +90,10 @@
   ExpectMetadataEq(metadata, float_tag_, data);
 }
 
-TEST_F(FixedPropertyTest, PopulateStaticArray) {
+TEST_F(PropertyTest, PopulateStaticArray) {
   // Set up a fixed property.
   std::array<float, 4> data({{0.1, 2.3, 4.5, 6.7}});
-  FixedProperty<std::array<float, 4>> property(float_tag_, data);
+  Property<std::array<float, 4>> property(float_tag_, data);
 
   // Populate static fields.
   android::CameraMetadata metadata;
@@ -104,12 +106,12 @@
   ExpectMetadataEq(metadata, float_tag_, data);
 }
 
-TEST_F(FixedPropertyTest, PopulateStaticArrayVector) {
+TEST_F(PropertyTest, PopulateStaticArrayVector) {
   // Set up a fixed property.
   ArrayVector<uint8_t, 3> data;
   data.push_back({{1, 2, 3}});
   data.push_back({{4, 5, 6}});
-  FixedProperty<ArrayVector<uint8_t, 3>> property(byte_tag_, data);
+  Property<ArrayVector<uint8_t, 3>> property(byte_tag_, data);
 
   // Populate static fields.
   android::CameraMetadata metadata;
@@ -122,8 +124,8 @@
   ExpectMetadataEq(metadata, byte_tag_, data);
 }
 
-TEST_F(FixedPropertyTest, PopulateDynamic) {
-  FixedProperty<int32_t> property(int_tag_, 1);
+TEST_F(PropertyTest, PopulateDynamic) {
+  Property<int32_t> property(int_tag_, 1);
 
   android::CameraMetadata metadata;
   EXPECT_EQ(property.PopulateDynamicFields(&metadata), 0);
@@ -132,14 +134,14 @@
   EXPECT_TRUE(metadata.isEmpty());
 }
 
-TEST_F(FixedPropertyTest, SupportsRequest) {
-  FixedProperty<int32_t> property(int_tag_, 1);
+TEST_F(PropertyTest, SupportsRequest) {
+  Property<int32_t> property(int_tag_, 1);
   android::CameraMetadata metadata;
   EXPECT_EQ(property.SupportsRequestValues(metadata), true);
 }
 
-TEST_F(FixedPropertyTest, SetRequest) {
-  FixedProperty<int32_t> property(int_tag_, 1);
+TEST_F(PropertyTest, SetRequest) {
+  Property<int32_t> property(int_tag_, 1);
   android::CameraMetadata metadata;
   EXPECT_EQ(property.SetRequestValues(metadata), 0);
 }
diff --git a/modules/camera/3_4/metadata/tagged_partial_metadata.h b/modules/camera/3_4/metadata/tagged_partial_metadata.h
deleted file mode 100644
index 0e02c06..0000000
--- a/modules/camera/3_4/metadata/tagged_partial_metadata.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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_TAGGED_PARTIAL_METADATA_H_
-#define V4L2_CAMERA_HAL_METADATA_TAGGED_PARTIAL_METADATA_H_
-
-#include "partial_metadata_interface.h"
-
-namespace v4l2_camera_hal {
-
-// A simple base class for PartialMetadataInterfaces that tracks tags.
-class TaggedPartialMetadata : public PartialMetadataInterface {
- public:
-  TaggedPartialMetadata(std::vector<int32_t> static_tags,
-                        std::vector<int32_t> control_tags,
-                        std::vector<int32_t> dynamic_tags)
-      : static_tags_(std::move(static_tags)),
-        control_tags_(std::move(control_tags)),
-        dynamic_tags_(std::move(dynamic_tags)){};
-  virtual ~TaggedPartialMetadata(){};
-
-  virtual const std::vector<int32_t>& StaticTags() const override {
-    return static_tags_;
-  };
-
-  virtual const std::vector<int32_t>& ControlTags() const override {
-    return control_tags_;
-  };
-
-  virtual const std::vector<int32_t>& DynamicTags() const override {
-    return dynamic_tags_;
-  };
-
-  // Child classes still need to override the Populate/Supported/Set methods.
-
- private:
-  const std::vector<int32_t> static_tags_;
-  const std::vector<int32_t> control_tags_;
-  const std::vector<int32_t> dynamic_tags_;
-};
-
-}  // namespace v4l2_camera_hal
-
-#endif  // V4L2_CAMERA_HAL_METADATA_TAGGED_PARTIAL_METADATA_H_
diff --git a/modules/camera/3_4/metadata/v4l2_enum_control.cpp b/modules/camera/3_4/metadata/v4l2_enum_control.cpp
deleted file mode 100644
index 9968b0b..0000000
--- a/modules/camera/3_4/metadata/v4l2_enum_control.cpp
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * 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 "v4l2_enum_control.h"
-
-#include "../common.h"
-
-namespace v4l2_camera_hal {
-
-V4L2EnumControl* V4L2EnumControl::NewV4L2EnumControl(
-    std::shared_ptr<V4L2Wrapper> device,
-    int v4l2_control,
-    int32_t control_tag,
-    int32_t options_tag,
-    const std::map<int32_t, uint8_t>& v4l2_to_metadata) {
-  HAL_LOG_ENTER();
-
-  // Query the device.
-  v4l2_query_ext_ctrl control_query;
-  int res = device->QueryControl(v4l2_control, &control_query);
-  if (res) {
-    HAL_LOGE("Failed to query control %d.", v4l2_control);
-    return nullptr;
-  }
-  if (control_query.type != V4L2_CTRL_TYPE_MENU &&
-      control_query.type != V4L2_CTRL_TYPE_BOOLEAN) {
-    HAL_LOGE(
-        "Enum controls can only be constructed from V4L2 menu and boolean "
-        "controls (%d is of type %d)",
-        v4l2_control,
-        control_query.type);
-    return nullptr;
-  }
-
-  // Convert device options to metadata options.
-  std::vector<uint8_t> options;
-  int32_t control_max = static_cast<int32_t>(control_query.maximum);
-  // Query maximum is inclusive.
-  for (int32_t i = static_cast<int32_t>(control_query.minimum);
-       i <= control_max;
-       i += control_query.step) {
-    auto map_entry = v4l2_to_metadata.find(i);
-    if (map_entry == v4l2_to_metadata.end()) {
-      HAL_LOGW("Control %d has unknown option %d.", v4l2_control, i);
-    } else {
-      options.push_back(map_entry->second);
-    }
-  }
-  if (options.empty()) {
-    HAL_LOGE("No supported options for control %d.", v4l2_control);
-    return nullptr;
-  }
-
-  // Construct the device.
-  return new V4L2EnumControl(device,
-                             v4l2_control,
-                             control_tag,
-                             options_tag,
-                             std::move(v4l2_to_metadata),
-                             std::move(options));
-}
-
-V4L2EnumControl::V4L2EnumControl(
-    std::shared_ptr<V4L2Wrapper> device,
-    int v4l2_control,
-    int32_t control_tag,
-    int32_t options_tag,
-    const std::map<int32_t, uint8_t> v4l2_to_metadata,
-    std::vector<uint8_t> options)
-    : OptionedControl<uint8_t>(control_tag, options_tag, std::move(options)),
-      device_(device),
-      v4l2_control_(v4l2_control),
-      v4l2_to_metadata_(std::move(v4l2_to_metadata)) {
-  HAL_LOG_ENTER();
-}
-
-int V4L2EnumControl::GetValue(uint8_t* value) const {
-  HAL_LOG_ENTER();
-
-  // Query the device for V4L2 value.
-  int32_t v4l2_value = 0;
-  int res = device_->GetControl(v4l2_control_, &v4l2_value);
-  if (res) {
-    HAL_LOGE("Failed to get value for control %d from device.", v4l2_control_);
-    return res;
-  }
-
-  // Convert to metadata value.
-  auto metadata_element = v4l2_to_metadata_.find(v4l2_value);
-  if (metadata_element == v4l2_to_metadata_.end()) {
-    HAL_LOGE("Unknown value %d for control %d.", v4l2_value, v4l2_control_);
-    return -ENODEV;
-  }
-  *value = metadata_element->second;
-  return 0;
-}
-
-int V4L2EnumControl::SetValue(const uint8_t& value) {
-  HAL_LOG_ENTER();
-
-  if (!IsSupported(value)) {
-    HAL_LOGE("Invalid control value %d.", value);
-    return -EINVAL;
-  }
-
-  // Convert to V4L2 value by doing an inverse lookup in the map.
-  bool found = false;
-  int32_t v4l2_value = -1;
-  for (auto kv : v4l2_to_metadata_) {
-    if (kv.second == value) {
-      v4l2_value = kv.first;
-      found = true;
-      break;
-    }
-  }
-  if (!found) {
-    HAL_LOGE("Couldn't find V4L2 conversion of valid control value %d.", value);
-    return -ENODEV;
-  }
-
-  return device_->SetControl(v4l2_control_, v4l2_value);
-}
-
-}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/metadata/v4l2_enum_control.h b/modules/camera/3_4/metadata/v4l2_enum_control.h
deleted file mode 100644
index f4027b8..0000000
--- a/modules/camera/3_4/metadata/v4l2_enum_control.h
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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_V4L2_ENUM_CONTROL_H_
-#define V4L2_CAMERA_HAL_METADATA_V4L2_ENUM_CONTROL_H_
-
-#include <map>
-#include <memory>
-#include <vector>
-
-#include <gtest/gtest_prod.h>
-
-#include "../v4l2_wrapper.h"
-#include "optioned_control.h"
-
-namespace v4l2_camera_hal {
-
-// A V4L2EnumControl is a direct mapping between a V4L2 control
-// and an Android metadata control.
-class V4L2EnumControl : public OptionedControl<uint8_t> {
- public:
-  // Use this method to create V4L2EnumControl objects.
-  // Functionally equivalent to "new V4L2EnumControl"
-  // except that it may return nullptr in case of failure.
-  static V4L2EnumControl* NewV4L2EnumControl(
-      std::shared_ptr<V4L2Wrapper> device,
-      int v4l2_control,
-      int32_t control_tag,
-      int32_t options_tag,
-      const std::map<int32_t, uint8_t>& v4l2_to_metadata);
-
- private:
-  // Constructor private to allow failing on bad input.
-  // Use NewV4L2EnumControl instead.
-  // The values in |v4l2_to_metadata| must be a superset of |options|.
-  V4L2EnumControl(std::shared_ptr<V4L2Wrapper> device,
-                  int v4l2_control,
-                  int32_t control_tag,
-                  int32_t options_tag,
-                  const std::map<int32_t, uint8_t> v4l2_to_metadata,
-                  std::vector<uint8_t> options);
-
-  virtual int GetValue(uint8_t* value) const override;
-  virtual int SetValue(const uint8_t& value) override;
-
-  std::shared_ptr<V4L2Wrapper> device_;
-  const int v4l2_control_;
-  const std::map<int32_t, uint8_t> v4l2_to_metadata_;
-
-  FRIEND_TEST(V4L2EnumControlTest, SetValue);
-  FRIEND_TEST(V4L2EnumControlTest, SetValueFail);
-  FRIEND_TEST(V4L2EnumControlTest, SetInvalidValue);
-  FRIEND_TEST(V4L2EnumControlTest, SetUnmapped);
-  FRIEND_TEST(V4L2EnumControlTest, GetValue);
-  FRIEND_TEST(V4L2EnumControlTest, GetValueFail);
-  FRIEND_TEST(V4L2EnumControlTest, GetUnmapped);
-  friend class V4L2EnumControlTest;  // Access to private constructor.
-
-  DISALLOW_COPY_AND_ASSIGN(V4L2EnumControl);
-};
-
-}  // namespace v4l2_camera_hal
-
-#endif  // V4L2_CAMERA_HAL_METADATA_V4L2_ENUM_CONTROL_H_
diff --git a/modules/camera/3_4/metadata/v4l2_enum_control_test.cpp b/modules/camera/3_4/metadata/v4l2_enum_control_test.cpp
deleted file mode 100644
index 05257c8..0000000
--- a/modules/camera/3_4/metadata/v4l2_enum_control_test.cpp
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * 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 "v4l2_enum_control.h"
-
-#include <array>
-
-#include <camera/CameraMetadata.h>
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-
-#include "../v4l2_wrapper_mock.h"
-#include "array_vector.h"
-#include "test_common.h"
-
-using testing::AtMost;
-using testing::Return;
-using testing::SetArgPointee;
-using testing::Test;
-using testing::_;
-
-namespace v4l2_camera_hal {
-
-class V4L2EnumControlTest : public Test {
- protected:
-  V4L2EnumControlTest()
-      : options_({10, 20, 30, 50}),  // Subset of the full map.
-        options_map_(
-            {{0, 0}, {1, 10}, {2, 20}, {3, 30}, {4, 40}, {5, 50}, {6, 60}}) {}
-
-  virtual void SetUp() {
-    device_.reset(new V4L2WrapperMock());
-    control_.reset(new V4L2EnumControl(device_,
-                                       v4l2_control_,
-                                       control_tag_,
-                                       options_tag_,
-                                       options_map_,
-                                       options_));
-  }
-
-  virtual uint8_t V4L2ToMetadata(int32_t value) {
-    return options_map_.at(value);
-  }
-
-  virtual int32_t MetadataToV4L2(uint8_t value) {
-    for (auto kv : options_map_) {
-      if (kv.second == value) {
-        return kv.first;
-      }
-    }
-    return -1;
-  }
-
-  std::unique_ptr<V4L2EnumControl> control_;
-  std::shared_ptr<V4L2WrapperMock> device_;
-
-  static constexpr int v4l2_control_ = 123;
-  // Need tags that match the data type (uint8_t) being passed.
-  static constexpr int32_t options_tag_ = ANDROID_CONTROL_AVAILABLE_SCENE_MODES;
-  static constexpr int32_t control_tag_ = ANDROID_CONTROL_SCENE_MODE;
-
-  const std::vector<uint8_t> options_;
-  const std::map<int32_t, uint8_t> options_map_;
-};
-
-TEST_F(V4L2EnumControlTest, NewV4L2EnumSuccess) {
-  // Should query the device.
-  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;
-  EXPECT_CALL(*device_, QueryControl(v4l2_control_, _))
-      .WillOnce(DoAll(SetArgPointee<1>(query_result), Return(0)));
-
-  std::unique_ptr<V4L2EnumControl> test_control(
-      V4L2EnumControl::NewV4L2EnumControl(
-          device_, v4l2_control_, control_tag_, options_tag_, options_map_));
-  // Shouldn't be null.
-  ASSERT_NE(test_control.get(), nullptr);
-
-  // Should pass through tags.
-  // The macro doesn't like the static variables
-  // being passed directly to assertions.
-  int32_t expected_tag = options_tag_;
-  ASSERT_EQ(test_control->StaticTags().size(), 1);
-  EXPECT_EQ(test_control->StaticTags()[0], expected_tag);
-  // Controls use the same tag for getting and setting.
-  expected_tag = control_tag_;
-  ASSERT_EQ(test_control->ControlTags().size(), 1);
-  EXPECT_EQ(test_control->ControlTags()[0], expected_tag);
-  ASSERT_EQ(test_control->DynamicTags().size(), 1);
-  EXPECT_EQ(test_control->DynamicTags()[0], expected_tag);
-
-  // Should populate the options according to capabilities returned.
-  android::CameraMetadata metadata;
-  ASSERT_EQ(test_control->PopulateStaticFields(&metadata), 0);
-  // Min 1, max 7, step 2 means {1,3,5} converted to metadata values.
-  // There is no 7 in the map, so it shouldn't be present.
-  std::vector<uint8_t> expected_options;
-  expected_options.push_back(V4L2ToMetadata(1));
-  expected_options.push_back(V4L2ToMetadata(3));
-  expected_options.push_back(V4L2ToMetadata(5));
-  ExpectMetadataEq(metadata, options_tag_, expected_options);
-}
-
-TEST_F(V4L2EnumControlTest, NewV4L2EnumFailed) {
-  // Querying the device fails.
-  int err = -99;
-  EXPECT_CALL(*device_, QueryControl(v4l2_control_, _)).WillOnce(Return(err));
-
-  std::unique_ptr<V4L2EnumControl> test_control(
-      V4L2EnumControl::NewV4L2EnumControl(
-          device_, v4l2_control_, control_tag_, options_tag_, options_map_));
-  // Should return null to indicate error.
-  ASSERT_EQ(test_control.get(), nullptr);
-}
-
-TEST_F(V4L2EnumControlTest, NewV4L2EnumInvalid) {
-  // Control type is not supported.
-  v4l2_query_ext_ctrl query_result;
-  query_result.type = V4L2_CTRL_TYPE_INTEGER;
-  query_result.minimum = 1;
-  query_result.maximum = 5;
-  query_result.step = 2;
-  EXPECT_CALL(*device_, QueryControl(v4l2_control_, _))
-      .WillOnce(DoAll(SetArgPointee<1>(query_result), Return(0)));
-
-  std::unique_ptr<V4L2EnumControl> test_control(
-      V4L2EnumControl::NewV4L2EnumControl(
-          device_, v4l2_control_, control_tag_, options_tag_, options_map_));
-  // Should return null to indicate error.
-  ASSERT_FALSE(test_control);
-}
-
-TEST_F(V4L2EnumControlTest, SetValue) {
-  // Should go through the device.
-  uint8_t val = options_[1];
-  EXPECT_CALL(*device_, SetControl(v4l2_control_, MetadataToV4L2(val), _))
-      .WillOnce(Return(0));
-  EXPECT_EQ(control_->SetValue(val), 0);
-}
-
-TEST_F(V4L2EnumControlTest, SetValueFail) {
-  // Should go through the device but fail.
-  uint8_t val = options_[1];
-  int err = -99;
-  EXPECT_CALL(*device_, SetControl(v4l2_control_, MetadataToV4L2(val), _))
-      .WillOnce(Return(err));
-  EXPECT_EQ(control_->SetValue(val), err);
-}
-
-TEST_F(V4L2EnumControlTest, SetInvalidValue) {
-  uint8_t val = 99;  // Not one of the supported values.
-  EXPECT_EQ(control_->SetValue(val), -EINVAL);
-}
-
-TEST_F(V4L2EnumControlTest, SetUnmapped) {
-  // If the enum control is validly constructed, this should never happen.
-  // Purposefully misconstruct a device for this test (empty map).
-  V4L2EnumControl test_control(
-      device_, v4l2_control_, control_tag_, options_tag_, {}, options_);
-  EXPECT_EQ(test_control.SetValue(options_[0]), -ENODEV);
-}
-
-TEST_F(V4L2EnumControlTest, GetValue) {
-  // Should go through the device.
-  uint8_t expected = options_[0];
-  EXPECT_CALL(*device_, GetControl(v4l2_control_, _))
-      .WillOnce(DoAll(SetArgPointee<1>(MetadataToV4L2(expected)), Return(0)));
-  uint8_t actual =
-      expected + 1;  // Initialize to something other than expected.
-  EXPECT_EQ(control_->GetValue(&actual), 0);
-  EXPECT_EQ(actual, expected);
-}
-
-TEST_F(V4L2EnumControlTest, GetValueFail) {
-  // Should go through the device but fail.
-  int err = -99;
-  EXPECT_CALL(*device_, GetControl(v4l2_control_, _)).WillOnce(Return(err));
-  uint8_t unused;
-  EXPECT_EQ(control_->GetValue(&unused), err);
-}
-
-TEST_F(V4L2EnumControlTest, GetUnmapped) {
-  int32_t invalid = -99;  // Not in our map.
-  EXPECT_CALL(*device_, GetControl(v4l2_control_, _))
-      .WillOnce(DoAll(SetArgPointee<1>(invalid), Return(0)));
-  uint8_t unused;
-  EXPECT_EQ(control_->GetValue(&unused), -ENODEV);
-}
-
-}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/v4l2_metadata.cpp b/modules/camera/3_4/v4l2_metadata.cpp
index 0ad667b..c289a40 100644
--- a/modules/camera/3_4/v4l2_metadata.cpp
+++ b/modules/camera/3_4/v4l2_metadata.cpp
@@ -19,9 +19,8 @@
 #include <camera/CameraMetadata.h>
 
 #include "common.h"
-#include "metadata/fixed_property.h"
-#include "metadata/ignored_control.h"
-#include "metadata/v4l2_enum_control.h"
+#include "metadata/control.h"
+#include "metadata/property.h"
 
 namespace v4l2_camera_hal {
 
@@ -37,20 +36,18 @@
   // V4L2 enum controls. Will add the other properties as more PartialMetadata
   // subclasses get implemented.
 
-  AddComponent(
-      std::unique_ptr<PartialMetadataInterface>(new IgnoredControl<uint8_t>(
-          ANDROID_COLOR_CORRECTION_ABERRATION_MODE,
-          ANDROID_COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES,
-          {ANDROID_COLOR_CORRECTION_ABERRATION_MODE_FAST,
-           ANDROID_COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY},
-          ANDROID_COLOR_CORRECTION_ABERRATION_MODE_FAST)));
+  AddComponent(Control<uint8_t>::NoEffectMenuControl(
+      ANDROID_COLOR_CORRECTION_ABERRATION_MODE,
+      ANDROID_COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES,
+      {ANDROID_COLOR_CORRECTION_ABERRATION_MODE_FAST,
+       ANDROID_COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY}));
 
   // TODO(b/30510395): subcomponents of 3A.
   // In general, default to ON/AUTO since they imply pretty much nothing,
   // while OFF implies guarantees about not hindering performance.
   AddComponent(std::unique_ptr<PartialMetadataInterface>(
-      new FixedProperty<std::array<int32_t, 3>>(
-          ANDROID_CONTROL_MAX_REGIONS, {{/*AE*/ 0, /*AWB*/ 0, /*AF*/ 0}})));
+      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,
@@ -75,6 +72,7 @@
   // 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_,
@@ -102,6 +100,7 @@
         {{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:
@@ -142,70 +141,57 @@
   // 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(std::unique_ptr<PartialMetadataInterface>(
-      new IgnoredControl<uint8_t>(ANDROID_EDGE_MODE,
-                                  ANDROID_EDGE_AVAILABLE_EDGE_MODES,
-                                  {ANDROID_EDGE_MODE_FAST},
-                                  ANDROID_EDGE_MODE_FAST)));
+  AddComponent(
+      Control<uint8_t>::NoEffectMenuControl(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(
-      std::unique_ptr<PartialMetadataInterface>(new IgnoredControl<uint8_t>(
-          ANDROID_HOT_PIXEL_MODE,
-          ANDROID_HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES,
-          {ANDROID_HOT_PIXEL_MODE_FAST, ANDROID_HOT_PIXEL_MODE_HIGH_QUALITY},
-          ANDROID_HOT_PIXEL_MODE_FAST)));
+  AddComponent(Control<uint8_t>::NoEffectMenuControl(
+      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(
-      std::unique_ptr<PartialMetadataInterface>(new IgnoredControl<uint8_t>(
-          ANDROID_STATISTICS_HOT_PIXEL_MAP_MODE,
-          ANDROID_STATISTICS_INFO_AVAILABLE_HOT_PIXEL_MAP_MODES,
-          {ANDROID_STATISTICS_HOT_PIXEL_MAP_MODE_OFF},
-          ANDROID_STATISTICS_HOT_PIXEL_MAP_MODE_OFF)));
+  AddComponent(Control<uint8_t>::NoEffectMenuControl(
+      ANDROID_STATISTICS_HOT_PIXEL_MAP_MODE,
+      ANDROID_STATISTICS_INFO_AVAILABLE_HOT_PIXEL_MAP_MODES,
+      {ANDROID_STATISTICS_HOT_PIXEL_MAP_MODE_OFF}));
 
   // 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(std::unique_ptr<PartialMetadataInterface>(
-      new IgnoredControl<float>(ANDROID_LENS_APERTURE,
-                                ANDROID_LENS_INFO_AVAILABLE_APERTURES,
-                                {2.0},
-                                2.0)));  // RPi camera v2 is f/2.0.
-  AddComponent(std::unique_ptr<PartialMetadataInterface>(
-      new IgnoredControl<float>(ANDROID_LENS_FOCAL_LENGTH,
-                                ANDROID_LENS_INFO_AVAILABLE_FOCAL_LENGTHS,
-                                {3.04},
-                                3.04)));  // RPi camera v2 is 3.04mm.
+  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(
+      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(std::unique_ptr<PartialMetadataInterface>(
-      new IgnoredControl<float>(ANDROID_LENS_FILTER_DENSITY,
-                                ANDROID_LENS_INFO_AVAILABLE_FILTER_DENSITIES,
-                                {0.0},
-                                0.0)));
+  AddComponent(Control<float>::NoEffectMenuControl(
+      ANDROID_LENS_FILTER_DENSITY,
+      ANDROID_LENS_INFO_AVAILABLE_FILTER_DENSITIES,
+      {0.0}));
   // V4L2 focal units do not correspond to a particular physical unit.
-  AddComponent(
-      std::unique_ptr<PartialMetadataInterface>(new FixedProperty<uint8_t>(
-          ANDROID_LENS_INFO_FOCUS_DISTANCE_CALIBRATION,
-          ANDROID_LENS_INFO_FOCUS_DISTANCE_CALIBRATION_UNCALIBRATED)));
+  AddComponent(std::unique_ptr<PartialMetadataInterface>(new Property<uint8_t>(
+      ANDROID_LENS_INFO_FOCUS_DISTANCE_CALIBRATION,
+      ANDROID_LENS_INFO_FOCUS_DISTANCE_CALIBRATION_UNCALIBRATED)));
   // info.hyperfocalDistance not required for UNCALIBRATED.
   // No known V4L2 lens shading. But it might be happening,
   // so report FAST/HIGH_QUALITY.
-  AddComponent(
-      std::unique_ptr<PartialMetadataInterface>(new IgnoredControl<uint8_t>(
-          ANDROID_SHADING_MODE,
-          ANDROID_SHADING_AVAILABLE_MODES,
-          {ANDROID_SHADING_MODE_FAST, ANDROID_SHADING_MODE_HIGH_QUALITY},
-          ANDROID_SHADING_MODE_FAST)));
+  AddComponent(Control<uint8_t>::NoEffectMenuControl(
+      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(
-      std::unique_ptr<PartialMetadataInterface>(new IgnoredControl<uint8_t>(
-          ANDROID_STATISTICS_LENS_SHADING_MAP_MODE,
-          ANDROID_STATISTICS_INFO_AVAILABLE_LENS_SHADING_MAP_MODES,
-          {ANDROID_STATISTICS_LENS_SHADING_MAP_MODE_OFF},
-          ANDROID_STATISTICS_LENS_SHADING_MAP_MODE_OFF)));
+  AddComponent(Control<uint8_t>::NoEffectMenuControl(
+      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,
@@ -214,98 +200,86 @@
                           {{0, ANDROID_CONTROL_VIDEO_STABILIZATION_MODE_OFF},
                            {1, ANDROID_CONTROL_VIDEO_STABILIZATION_MODE_ON}},
                           ANDROID_CONTROL_VIDEO_STABILIZATION_MODE_OFF);
-  AddComponent(
-      std::unique_ptr<PartialMetadataInterface>(new IgnoredControl<uint8_t>(
-          ANDROID_LENS_OPTICAL_STABILIZATION_MODE,
-          ANDROID_LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION,
-          {ANDROID_LENS_OPTICAL_STABILIZATION_MODE_OFF},
-          ANDROID_LENS_OPTICAL_STABILIZATION_MODE_OFF)));
+  AddComponent(Control<uint8_t>::NoEffectMenuControl(
+      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(
-      std::unique_ptr<PartialMetadataInterface>(new IgnoredControl<uint8_t>(
-          ANDROID_NOISE_REDUCTION_MODE,
-          ANDROID_NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES,
-          {ANDROID_NOISE_REDUCTION_MODE_FAST},
-          ANDROID_NOISE_REDUCTION_MODE_FAST)));
+  AddComponent(Control<uint8_t>::NoEffectMenuControl(
+      ANDROID_NOISE_REDUCTION_MODE,
+      ANDROID_NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES,
+      {ANDROID_NOISE_REDUCTION_MODE_FAST}));
 
   // TODO(30510395): subcomponents of formats/streams.
   // 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(std::unique_ptr<PartialMetadataInterface>(
-      new IgnoredControl<std::array<int32_t, 2>>(
-          ANDROID_JPEG_THUMBNAIL_SIZE,
-          ANDROID_JPEG_AVAILABLE_THUMBNAIL_SIZES,
-          {{{0, 0}}},
-          {{0, 0}})));
+  AddComponent(Control<std::array<int32_t, 2>>::NoEffectMenuControl(
+      ANDROID_JPEG_THUMBNAIL_SIZE,
+      ANDROID_JPEG_AVAILABLE_THUMBNAIL_SIZES,
+      {{{0, 0}}}));
   // TODO(b/29939583): V4L2 can only support 1 stream at a time.
   // For now, just reporting minimum allowable for LIMITED devices.
   AddComponent(std::unique_ptr<PartialMetadataInterface>(
-      new FixedProperty<std::array<int32_t, 3>>(
+      new Property<std::array<int32_t, 3>>(
           ANDROID_REQUEST_MAX_NUM_OUTPUT_STREAMS,
           {{/* Raw */ 0, /* Non-stalling */ 2, /* Stalling */ 1}})));
   // Reprocessing not supported.
   AddComponent(std::unique_ptr<PartialMetadataInterface>(
-      new FixedProperty<int32_t>(ANDROID_REQUEST_MAX_NUM_INPUT_STREAMS, 0)));
+      new Property<int32_t>(ANDROID_REQUEST_MAX_NUM_INPUT_STREAMS, 0)));
   // No way to know pipeline depth for V4L2, so fake with max allowable latency.
   // Doesn't mean much without per-frame controls anyways.
   AddComponent(std::unique_ptr<PartialMetadataInterface>(
-      new FixedProperty<uint8_t>(ANDROID_REQUEST_PIPELINE_MAX_DEPTH, 4)));
+      new Property<uint8_t>(ANDROID_REQUEST_PIPELINE_MAX_DEPTH, 4)));
   // "LIMITED devices are strongly encouraged to use a non-negative value.
   // If UNKNOWN is used here then app developers do not have a way to know
   // when sensor settings have been applied." - Unfortunately, V4L2 doesn't
   // really help here either. Could even be that adjusting settings mid-stream
   // blocks in V4L2, and should be avoided.
-  AddComponent(
-      std::unique_ptr<PartialMetadataInterface>(new FixedProperty<int32_t>(
-          ANDROID_SYNC_MAX_LATENCY, ANDROID_SYNC_MAX_LATENCY_UNKNOWN)));
+  AddComponent(std::unique_ptr<PartialMetadataInterface>(new Property<int32_t>(
+      ANDROID_SYNC_MAX_LATENCY, ANDROID_SYNC_MAX_LATENCY_UNKNOWN)));
 
   // TODO(30510395): subcomponents of cropping/sensors.
   // V4L2 VIDIOC_CROPCAP doesn't give a way to query this;
   // it's driver dependent. For now, assume freeform, and
   // some cameras may just behave badly.
   // TODO(b/29579652): Figure out a way to determine this.
-  AddComponent(std::unique_ptr<PartialMetadataInterface>(
-      new FixedProperty<uint8_t>(ANDROID_SCALER_CROPPING_TYPE,
-                                 ANDROID_SCALER_CROPPING_TYPE_FREEFORM)));
+  AddComponent(std::unique_ptr<PartialMetadataInterface>(new Property<uint8_t>(
+      ANDROID_SCALER_CROPPING_TYPE, ANDROID_SCALER_CROPPING_TYPE_FREEFORM)));
   // No way to get in V4L2, so faked. RPi camera v2 is 3.674 x 2.760 mm.
   // Physical size is used in framework calculations (field of view,
   // pixel pitch, etc.), so faking it may have unexpected results.
   AddComponent(std::unique_ptr<PartialMetadataInterface>(
-      new FixedProperty<std::array<float, 2>>(ANDROID_SENSOR_INFO_PHYSICAL_SIZE,
-                                              {{3.674, 2.760}})));
+      new Property<std::array<float, 2>>(ANDROID_SENSOR_INFO_PHYSICAL_SIZE,
+                                         {{3.674, 2.760}})));
   // HAL uses BOOTTIME timestamps.
   // TODO(b/29457051): make sure timestamps are consistent throughout the HAL.
-  AddComponent(
-      std::unique_ptr<PartialMetadataInterface>(new FixedProperty<uint8_t>(
-          ANDROID_SENSOR_INFO_TIMESTAMP_SOURCE,
-          ANDROID_SENSOR_INFO_TIMESTAMP_SOURCE_UNKNOWN)));
+  AddComponent(std::unique_ptr<PartialMetadataInterface>(
+      new Property<uint8_t>(ANDROID_SENSOR_INFO_TIMESTAMP_SOURCE,
+                            ANDROID_SENSOR_INFO_TIMESTAMP_SOURCE_UNKNOWN)));
   // Noo way to actually get orientation from V4L2.
   AddComponent(std::unique_ptr<PartialMetadataInterface>(
-      new FixedProperty<int32_t>(ANDROID_SENSOR_ORIENTATION, 0)));
+      new Property<int32_t>(ANDROID_SENSOR_ORIENTATION, 0)));
 
   // TODO(30510395): subcomponents of face detection.
   // Face detection not supported.
-  AddComponent(
-      std::unique_ptr<PartialMetadataInterface>(new IgnoredControl<uint8_t>(
-          ANDROID_STATISTICS_FACE_DETECT_MODE,
-          ANDROID_STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES,
-          {ANDROID_STATISTICS_FACE_DETECT_MODE_OFF},
-          ANDROID_STATISTICS_FACE_DETECT_MODE_OFF)));
+  AddComponent(Control<uint8_t>::NoEffectMenuControl(
+      ANDROID_STATISTICS_FACE_DETECT_MODE,
+      ANDROID_STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES,
+      {ANDROID_STATISTICS_FACE_DETECT_MODE_OFF}));
   AddComponent(std::unique_ptr<PartialMetadataInterface>(
-      new FixedProperty<int32_t>(ANDROID_STATISTICS_INFO_MAX_FACE_COUNT, 0)));
+      new Property<int32_t>(ANDROID_STATISTICS_INFO_MAX_FACE_COUNT, 0)));
 
   /* Capabilities. */
   // The V4L2Metadata pretends to at least meet the
   // "LIMITED" and "BACKWARD_COMPATIBLE" functionality requirements.
-  AddComponent(
-      std::unique_ptr<PartialMetadataInterface>(new FixedProperty<uint8_t>(
-          ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL,
-          ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED)));
   AddComponent(std::unique_ptr<PartialMetadataInterface>(
-      new FixedProperty<std::vector<uint8_t>>(
+      new Property<uint8_t>(ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL,
+                            ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED)));
+  AddComponent(std::unique_ptr<PartialMetadataInterface>(
+      new Property<std::vector<uint8_t>>(
           ANDROID_REQUEST_AVAILABLE_CAPABILITIES,
           {ANDROID_REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE})));
 }
@@ -321,15 +295,11 @@
     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(
-      V4L2EnumControl::NewV4L2EnumControl(
-          device_, v4l2_control, control_tag, options_tag, v4l2_to_metadata));
-
-  if (!control) {
-    control.reset(new IgnoredControl<uint8_t>(
-        control_tag, options_tag, {default_value}, default_value));
-  }
+      Control<uint8_t>::NoEffectMenuControl(
+          control_tag, options_tag, {default_value}));
 
   AddComponent(std::move(control));
 }