Merge changes from topic "equalizer"
am: eba0da9019

Change-Id: I82025275ee812b1e06038f277bdb6a5fa28634ec
diff --git a/include/hardware/gralloc.h b/include/hardware/gralloc.h
index 1b06ebf..5dafea0 100644
--- a/include/hardware/gralloc.h
+++ b/include/hardware/gralloc.h
@@ -18,7 +18,6 @@
 #ifndef ANDROID_GRALLOC_INTERFACE_H
 #define ANDROID_GRALLOC_INTERFACE_H
 
-#include <system/window.h>
 #include <system/graphics.h>
 #include <hardware/hardware.h>
 
diff --git a/include/hardware/gralloc1.h b/include/hardware/gralloc1.h
index 4845010..0a6843f 100644
--- a/include/hardware/gralloc1.h
+++ b/include/hardware/gralloc1.h
@@ -18,7 +18,7 @@
 #define ANDROID_HARDWARE_GRALLOC1_H
 
 #include <hardware/hardware.h>
-#include <system/window.h>
+#include <cutils/native_handle.h>
 
 __BEGIN_DECLS
 
diff --git a/include/hardware/tv_input.h b/include/hardware/tv_input.h
index ed3fafb..b421d43 100644
--- a/include/hardware/tv_input.h
+++ b/include/hardware/tv_input.h
@@ -23,7 +23,7 @@
 
 #include <hardware/hardware.h>
 #include <system/audio.h>
-#include <system/window.h>
+#include <cutils/native_handle.h>
 
 __BEGIN_DECLS
 
diff --git a/modules/camera/3_4/Android.mk b/modules/camera/3_4/Android.mk
new file mode 100644
index 0000000..d4201ba
--- /dev/null
+++ b/modules/camera/3_4/Android.mk
@@ -0,0 +1,111 @@
+#
+# Copyright 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+# Prevent the HAL from building on devices not specifically
+# requesting to use it.
+ifeq ($(USE_CAMERA_V4L2_HAL), true)
+
+v4l2_shared_libs := \
+  libbase \
+  libcamera_client \
+  libcamera_metadata \
+  libcutils \
+  libhardware \
+  liblog \
+  libsync \
+  libutils \
+
+v4l2_static_libs :=
+
+v4l2_cflags := -fno-short-enums -Wall -Wextra -fvisibility=hidden
+
+v4l2_c_includes := $(call include-path-for, camera)
+
+v4l2_src_files := \
+  camera.cpp \
+  capture_request.cpp \
+  format_metadata_factory.cpp \
+  metadata/boottime_state_delegate.cpp \
+  metadata/enum_converter.cpp \
+  metadata/metadata.cpp \
+  metadata/metadata_reader.cpp \
+  request_tracker.cpp \
+  static_properties.cpp \
+  stream_format.cpp \
+  v4l2_camera.cpp \
+  v4l2_camera_hal.cpp \
+  v4l2_gralloc.cpp \
+  v4l2_metadata_factory.cpp \
+  v4l2_wrapper.cpp \
+
+v4l2_test_files := \
+  format_metadata_factory_test.cpp \
+  metadata/control_test.cpp \
+  metadata/default_option_delegate_test.cpp \
+  metadata/enum_converter_test.cpp \
+  metadata/ignored_control_delegate_test.cpp \
+  metadata/map_converter_test.cpp \
+  metadata/menu_control_options_test.cpp \
+  metadata/metadata_reader_test.cpp \
+  metadata/metadata_test.cpp \
+  metadata/no_effect_control_delegate_test.cpp \
+  metadata/partial_metadata_factory_test.cpp \
+  metadata/property_test.cpp \
+  metadata/ranged_converter_test.cpp \
+  metadata/slider_control_options_test.cpp \
+  metadata/state_test.cpp \
+  metadata/tagged_control_delegate_test.cpp \
+  metadata/tagged_control_options_test.cpp \
+  metadata/v4l2_control_delegate_test.cpp \
+  request_tracker_test.cpp \
+  static_properties_test.cpp \
+
+# V4L2 Camera HAL.
+# ==============================================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := camera.v4l2
+LOCAL_MODULE_RELATIVE_PATH := hw
+LOCAL_CFLAGS += $(v4l2_cflags)
+LOCAL_SHARED_LIBRARIES := $(v4l2_shared_libs)
+LOCAL_STATIC_LIBRARIES := \
+  libgtest_prod \
+  $(v4l2_static_libs) \
+
+LOCAL_C_INCLUDES += $(v4l2_c_includes)
+LOCAL_SRC_FILES := $(v4l2_src_files)
+include $(BUILD_SHARED_LIBRARY)
+
+# Unit tests for V4L2 Camera HAL.
+# ==============================================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := camera.v4l2_test
+LOCAL_CFLAGS += $(v4l2_cflags)
+LOCAL_SHARED_LIBRARIES := $(v4l2_shared_libs)
+LOCAL_STATIC_LIBRARIES := \
+  libBionicGtestMain \
+  libgmock \
+  $(v4l2_static_libs) \
+
+LOCAL_C_INCLUDES += $(v4l2_c_includes)
+LOCAL_SRC_FILES := \
+  $(v4l2_src_files) \
+  $(v4l2_test_files) \
+
+include $(BUILD_NATIVE_TEST)
+
+endif # USE_CAMERA_V4L2_HAL
diff --git a/modules/camera/3_4/README.md b/modules/camera/3_4/README.md
new file mode 100644
index 0000000..73d0c13
--- /dev/null
+++ b/modules/camera/3_4/README.md
@@ -0,0 +1,151 @@
+# V4L2 Camera HALv3
+
+The camera.v4l2 library implements a Camera HALv3 using the
+Video For Linux 2 (V4L2) interface. This allows it to theoretically
+work with a wide variety of devices, though the limitations of V4L2
+introduce some [caveats](#V4L2-Deficiencies), causing this HAL to
+not be fully spec-compliant.
+
+## Building a Device with the HAL
+
+To ensure the HAL is built for a device, include the following in your
+`<device>.mk`:
+
+```
+USE_CAMERA_V4L2_HAL := true
+PRODUCT_PACKAGES += camera.v4l2
+PRODUCT_PROPERTY_OVERRIDES += ro.hardware.camera=v4l2
+```
+
+The first line ensures the V4L2 HAL module is visible to the build system.
+This prevents checkbuilds on devices that don't have the necessary support
+from failing. The product packages tells the build system to include the V4L2
+HALv3 library in the system image. The final line tells the hardware manager
+to load the V4L2 HAL instead of a default Camera HAL.
+
+## Requirements for Using the HAL
+
+Devices and cameras wishing to use this HAL must meet
+the following requirements:
+
+* The camera must support BGR32, YUV420, and JPEG formats.
+* The gralloc and other graphics modules used by the device must use
+`HAL_PIXEL_FORMAT_RGBA_8888` as the `HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED`
+
+## Understanding the HAL Code
+
+There are three large pieces to the V4L2 Camera HAL: the general HALv3
+Camera & HAL code, the specific implementation using V4L2,
+and the Metadata system.
+
+For context, you may also wish to read some of the documentation in
+libhardware/include/camera3.h about how the framework interacts with the HAL.
+
+### Camera & HAL Interface
+
+The camera and HAL interfaces are implemented by the Camera and
+V4L2CameraHAL classes.
+
+The V4L2CameraHAL class deals primarily with initialization of the system.
+On creation, it searches /dev/video* nodes for ones with the necessary
+capabilities. These are then all presented to the framework as available
+for use. Further operations are passed to the individual Cameras as appropriate.
+
+The Camera class implements the general logic for handling the camera -
+opening and closing, configuring streams, preparing and tracking requests, etc.
+While it handles the logistics surrounding the camera, actual image
+capture and settings logic are implemented by calling down into the
+[V4L2 Camera](#V4L2-Camera). The Camera (using helper classes) enforces
+restrictions given in the [Metadata](#Metadata) initialized by the V4L2Camera,
+such as limits on the number of in-flight requests per stream.
+Notably, this means you should be able to replace the V4L2 implementation
+with something else, and as long as you fill in the metadata correctly the
+Camera class should "just work".
+
+### V4L2 Specific Implementation
+
+The V4L2Camera class is the implementation of all the capture functionality.
+It includes some methods for the Camera class to verify the setup, but the
+bulk of the class is the request queue. The Camera class submits CaptureRequests
+as they come in and are verified. The V4L2Camera runs these through a three
+stage asynchronous pipeline:
+
+* Acceptance: the V4L2Camera accepts the request, and puts it into waiting to be
+picked up by the enqueuer.
+* Enqueuing: the V4L2Camera reads the request settings, applies them to the
+device, takes a snapshot of the settings, and hands the buffer over to the
+V4L2 driver.
+* Dequeueing: A completed frame is reclaimed from the driver, and sent
+back to the Camera class for final processing (validation, filling in the
+result object, and sending the data back to the framework).
+
+Much of this work is aided by the V4L2Wrapper helper class,
+which provides simpler inputs and outputs around the V4L2 ioctls
+based on their known use by the HAL; filling in common values automatically
+and extracting the information useful to the HAL from the results.
+This wrapper is also used to expose V4L2 controls to their corresponding
+Metadata components.
+
+### Metadata
+
+The Metadata subsystem attempts to organize and simplify handling of
+camera metadata (system/media/camera/docs/docs.html). At the top level
+is the Metadata class and the PartialMetadataInterface. The Metadata
+class provides high level interaction with the individual components -
+filling the static metadata, validating, getting, and setting settings,
+etc. The Metadata class passes all of these things on to the component
+PartialMetadataInterfaces, each of which filter for their specific
+metadata components and perform the requested task.
+
+Some generalized metadata classes are provided to simplify common logic
+for this filtering and application. At a high level, there are three
+types:
+
+* Properties: a static value.
+* Controls: dynamically adjustable values, and optionally an
+associated static property indicating what allowable values are.
+* States: a dynamic read-only value.
+
+The Metadata system uses further interfaces and subclasses to distinguish
+the variety of different functionalities necessary for different metadata
+tags.
+
+#### Metadata Factory
+
+This V4L2 Camera HAL implementation utilizes a metadata factory method.
+This method initializes all the 100+ required metadata components for
+basic HAL spec compliance. Most do nothing/report fixed values,
+but a few are hooked up to the V4L2 driver.
+
+This HAL was initially designed for use with the Raspberry Pi camera module
+v2.1, so the fixed defaults are usually assigned based on that camera.
+
+## V4L2 Deficiencies
+
+* One stream at a time is supported. Notably, this means you must re-configure
+the stream between preview and capture if they're not the same format.
+This makes this HAL not backwards compatible with the Android Camera (v1) API
+as many of its methods attempt to do just that; Camera2 must be used instead.
+* A variety of metadata properties can't be filled in from V4L2,
+such as physical properties of the camera. Thus this HAL will never be capable
+of providing perfectly accurate information for all cameras it can theoretically
+support.
+* Android requires HALs support YUV420, JPEG, and a format of the graphics
+stack's choice ("implementation defined"). Very few cameras actually support
+all of these formats (so far the Raspberry Pi cameras are the only known ones),
+so some form of format conversion built in to the HAL would be a useful feature
+to expand the reach/usefulness of this HAL.
+* V4L2 doesn't make promises about how fast settings will apply, and there's no
+good way to determine what settings were in effect for a given frame. Thus,
+the settings passed into requests and out with results are applied/read as
+a best effort and may be incorrect.
+* Many features V4L2 is capable of are not hooked up to the HAL, so the HAL
+is underfeatured compared to the ideal/what is possible.
+
+## Other Known Issues
+
+* A variety of features are unimplemented: High speed capture,
+flash torch mode, hotplugging/unplugging.
+* The HAL uses BGR for RGBA. Again, the HAL was designed for the Raspberry Pi
+camera, which doesn't support RGB, but RGB is a common default format for
+graphics stacks.
diff --git a/modules/camera/3_4/camera.cpp b/modules/camera/3_4/camera.cpp
new file mode 100644
index 0000000..7f42eef
--- /dev/null
+++ b/modules/camera/3_4/camera.cpp
@@ -0,0 +1,596 @@
+/*
+ * 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.
+ */
+
+// Modified from hardware/libhardware/modules/camera/Camera.cpp
+
+#include <cstdlib>
+#include <memory>
+#include <vector>
+#include <stdio.h>
+#include <hardware/camera3.h>
+#include <sync/sync.h>
+#include <system/camera_metadata.h>
+#include <system/graphics.h>
+#include <utils/Mutex.h>
+
+#include "metadata/metadata_common.h"
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "Camera"
+#include <cutils/log.h>
+
+#define ATRACE_TAG (ATRACE_TAG_CAMERA | ATRACE_TAG_HAL)
+#include <utils/Trace.h>
+
+#include "camera.h"
+
+#define CAMERA_SYNC_TIMEOUT 5000 // in msecs
+
+namespace default_camera_hal {
+
+extern "C" {
+// Shim passed to the framework to close an opened device.
+static int close_device(hw_device_t* dev)
+{
+    camera3_device_t* cam_dev = reinterpret_cast<camera3_device_t*>(dev);
+    Camera* cam = static_cast<Camera*>(cam_dev->priv);
+    return cam->close();
+}
+} // extern "C"
+
+Camera::Camera(int id)
+  : mId(id),
+    mSettingsSet(false),
+    mBusy(false),
+    mCallbackOps(NULL),
+    mInFlightTracker(new RequestTracker)
+{
+    memset(&mTemplates, 0, sizeof(mTemplates));
+    memset(&mDevice, 0, sizeof(mDevice));
+    mDevice.common.tag    = HARDWARE_DEVICE_TAG;
+    mDevice.common.version = CAMERA_DEVICE_API_VERSION_3_4;
+    mDevice.common.close  = close_device;
+    mDevice.ops           = const_cast<camera3_device_ops_t*>(&sOps);
+    mDevice.priv          = this;
+}
+
+Camera::~Camera()
+{
+}
+
+int Camera::openDevice(const hw_module_t *module, hw_device_t **device)
+{
+    ALOGI("%s:%d: Opening camera device", __func__, mId);
+    ATRACE_CALL();
+    android::Mutex::Autolock al(mDeviceLock);
+
+    if (mBusy) {
+        ALOGE("%s:%d: Error! Camera device already opened", __func__, mId);
+        return -EBUSY;
+    }
+
+    int connectResult = connect();
+    if (connectResult != 0) {
+      return connectResult;
+    }
+    mBusy = true;
+    mDevice.common.module = const_cast<hw_module_t*>(module);
+    *device = &mDevice.common;
+    return 0;
+}
+
+int Camera::getInfo(struct camera_info *info)
+{
+    info->device_version = mDevice.common.version;
+    initDeviceInfo(info);
+    if (!mStaticInfo) {
+        int res = loadStaticInfo();
+        if (res) {
+            return res;
+        }
+    }
+    info->static_camera_characteristics = mStaticInfo->raw_metadata();
+    info->facing = mStaticInfo->facing();
+    info->orientation = mStaticInfo->orientation();
+
+    return 0;
+}
+
+int Camera::loadStaticInfo() {
+  // Using a lock here ensures |mStaticInfo| will only ever be set once,
+  // even in concurrent situations.
+  android::Mutex::Autolock al(mStaticInfoLock);
+
+  if (mStaticInfo) {
+    return 0;
+  }
+
+  std::unique_ptr<android::CameraMetadata> static_metadata =
+      std::make_unique<android::CameraMetadata>();
+  int res = initStaticInfo(static_metadata.get());
+  if (res) {
+    ALOGE("%s:%d: Failed to get static info from device.",
+          __func__, mId);
+    return res;
+  }
+
+  mStaticInfo.reset(StaticProperties::NewStaticProperties(
+      std::move(static_metadata)));
+  if (!mStaticInfo) {
+    ALOGE("%s:%d: Failed to initialize static properties from device metadata.",
+          __func__, mId);
+    return -ENODEV;
+  }
+
+  return 0;
+}
+
+int Camera::close()
+{
+    ALOGI("%s:%d: Closing camera device", __func__, mId);
+    ATRACE_CALL();
+    android::Mutex::Autolock al(mDeviceLock);
+
+    if (!mBusy) {
+        ALOGE("%s:%d: Error! Camera device not open", __func__, mId);
+        return -EINVAL;
+    }
+
+    flush();
+    disconnect();
+    mBusy = false;
+    return 0;
+}
+
+int Camera::initialize(const camera3_callback_ops_t *callback_ops)
+{
+    int res;
+
+    ALOGV("%s:%d: callback_ops=%p", __func__, mId, callback_ops);
+    mCallbackOps = callback_ops;
+    // per-device specific initialization
+    res = initDevice();
+    if (res != 0) {
+        ALOGE("%s:%d: Failed to initialize device!", __func__, mId);
+        return res;
+    }
+    return 0;
+}
+
+int Camera::configureStreams(camera3_stream_configuration_t *stream_config)
+{
+    android::Mutex::Autolock al(mDeviceLock);
+
+    ALOGV("%s:%d: stream_config=%p", __func__, mId, stream_config);
+    ATRACE_CALL();
+
+    // Check that there are no in-flight requests.
+    if (!mInFlightTracker->Empty()) {
+        ALOGE("%s:%d: Can't configure streams while frames are in flight.",
+              __func__, mId);
+        return -EINVAL;
+    }
+
+    // Verify the set of streams in aggregate, and perform configuration if valid.
+    int res = validateStreamConfiguration(stream_config);
+    if (res) {
+        ALOGE("%s:%d: Failed to validate stream set", __func__, mId);
+    } else {
+        // Set up all streams. Since they've been validated,
+        // this should only result in fatal (-ENODEV) errors.
+        // This occurs after validation to ensure that if there
+        // is a non-fatal error, the stream configuration doesn't change states.
+        res = setupStreams(stream_config);
+        if (res) {
+            ALOGE("%s:%d: Failed to setup stream set", __func__, mId);
+        }
+    }
+
+    // Set trackers based on result.
+    if (!res) {
+        // Success, set up the in-flight trackers for the new streams.
+        mInFlightTracker->SetStreamConfiguration(*stream_config);
+        // Must provide new settings for the new configuration.
+        mSettingsSet = false;
+    } else if (res != -EINVAL) {
+        // Fatal error, the old configuration is invalid.
+        mInFlightTracker->ClearStreamConfiguration();
+    }
+    // On a non-fatal error the old configuration, if any, remains valid.
+    return res;
+}
+
+int Camera::validateStreamConfiguration(
+    const camera3_stream_configuration_t* stream_config)
+{
+    // Check that the configuration is well-formed.
+    if (stream_config == nullptr) {
+        ALOGE("%s:%d: NULL stream configuration array", __func__, mId);
+        return -EINVAL;
+    } else if (stream_config->num_streams == 0) {
+        ALOGE("%s:%d: Empty stream configuration array", __func__, mId);
+        return -EINVAL;
+    } else if (stream_config->streams == nullptr) {
+        ALOGE("%s:%d: NULL stream configuration streams", __func__, mId);
+        return -EINVAL;
+    }
+
+    // Check that the configuration is supported.
+    // Make sure static info has been initialized before trying to use it.
+    if (!mStaticInfo) {
+        int res = loadStaticInfo();
+        if (res) {
+            return res;
+        }
+    }
+    if (!mStaticInfo->StreamConfigurationSupported(stream_config)) {
+        ALOGE("%s:%d: Stream configuration does not match static "
+              "metadata restrictions.", __func__, mId);
+        return -EINVAL;
+    }
+
+    // Dataspace support is poorly documented - unclear if the expectation
+    // is that a device supports ALL dataspaces that could match a given
+    // format. For now, defer to child class implementation.
+    // Rotation support isn't described by metadata, so must defer to device.
+    if (!validateDataspacesAndRotations(stream_config)) {
+        ALOGE("%s:%d: Device can not handle configuration "
+              "dataspaces or rotations.", __func__, mId);
+        return -EINVAL;
+    }
+
+    return 0;
+}
+
+bool Camera::isValidTemplateType(int type)
+{
+    return type > 0 && type < CAMERA3_TEMPLATE_COUNT;
+}
+
+const camera_metadata_t* Camera::constructDefaultRequestSettings(int type)
+{
+    ALOGV("%s:%d: type=%d", __func__, mId, type);
+
+    if (!isValidTemplateType(type)) {
+        ALOGE("%s:%d: Invalid template request type: %d", __func__, mId, type);
+        return NULL;
+    }
+
+    if (!mTemplates[type]) {
+        // Check if the device has the necessary features
+        // for the requested template. If not, don't bother.
+        if (!mStaticInfo->TemplateSupported(type)) {
+            ALOGW("%s:%d: Camera does not support template type %d",
+                  __func__, mId, type);
+            return NULL;
+        }
+
+        // Initialize this template if it hasn't been initialized yet.
+        std::unique_ptr<android::CameraMetadata> new_template =
+            std::make_unique<android::CameraMetadata>();
+        int res = initTemplate(type, new_template.get());
+        if (res || !new_template) {
+            ALOGE("%s:%d: Failed to generate template of type: %d",
+                  __func__, mId, type);
+            return NULL;
+        }
+        mTemplates[type] = std::move(new_template);
+    }
+
+    // The "locking" here only causes non-const methods to fail,
+    // which is not a problem since the CameraMetadata being locked
+    // is already const. Destructing automatically "unlocks".
+    return mTemplates[type]->getAndLock();
+}
+
+int Camera::processCaptureRequest(camera3_capture_request_t *temp_request)
+{
+    int res;
+    // TODO(b/32917568): A capture request submitted or ongoing during a flush
+    // should be returned with an error; for now they are mutually exclusive.
+    android::Mutex::Autolock al(mFlushLock);
+
+    ATRACE_CALL();
+
+    if (temp_request == NULL) {
+        ALOGE("%s:%d: NULL request recieved", __func__, mId);
+        return -EINVAL;
+    }
+
+    // Make a persistent copy of request, since otherwise it won't live
+    // past the end of this method.
+    std::shared_ptr<CaptureRequest> request = std::make_shared<CaptureRequest>(temp_request);
+
+    ALOGV("%s:%d: frame: %d", __func__, mId, request->frame_number);
+
+    if (!mInFlightTracker->CanAddRequest(*request)) {
+        // Streams are full or frame number is not unique.
+        ALOGE("%s:%d: Can not add request.", __func__, mId);
+        return -EINVAL;
+    }
+
+    // Null/Empty indicates use last settings
+    if (request->settings.isEmpty() && !mSettingsSet) {
+        ALOGE("%s:%d: NULL settings without previous set Frame:%d",
+              __func__, mId, request->frame_number);
+        return -EINVAL;
+    }
+
+    if (request->input_buffer != NULL) {
+        ALOGV("%s:%d: Reprocessing input buffer %p", __func__, mId,
+              request->input_buffer.get());
+    } else {
+        ALOGV("%s:%d: Capturing new frame.", __func__, mId);
+    }
+
+    if (!isValidRequestSettings(request->settings)) {
+        ALOGE("%s:%d: Invalid request settings.", __func__, mId);
+        return -EINVAL;
+    }
+
+    // Pre-process output buffers.
+    if (request->output_buffers.size() <= 0) {
+        ALOGE("%s:%d: Invalid number of output buffers: %d", __func__, mId,
+              request->output_buffers.size());
+        return -EINVAL;
+    }
+    for (auto& output_buffer : request->output_buffers) {
+        res = preprocessCaptureBuffer(&output_buffer);
+        if (res)
+            return -ENODEV;
+    }
+
+    // Add the request to tracking.
+    if (!mInFlightTracker->Add(request)) {
+        ALOGE("%s:%d: Failed to track request for frame %d.",
+              __func__, mId, request->frame_number);
+        return -ENODEV;
+    }
+
+    // Valid settings have been provided (mSettingsSet is a misnomer;
+    // all that matters is that a previous request with valid settings
+    // has been passed to the device, not that they've been set).
+    mSettingsSet = true;
+
+    // Send the request off to the device for completion.
+    enqueueRequest(request);
+
+    // Request is now in flight. The device will call completeRequest
+    // asynchronously when it is done filling buffers and metadata.
+    return 0;
+}
+
+void Camera::completeRequest(std::shared_ptr<CaptureRequest> request, int err)
+{
+    if (!mInFlightTracker->Remove(request)) {
+        ALOGE("%s:%d: Completed request %p is not being tracked. "
+              "It may have been cleared out during a flush.",
+              __func__, mId, request.get());
+        return;
+    }
+
+    // Since |request| has been removed from the tracking, this method
+    // MUST call sendResult (can still return a result in an error state, e.g.
+    // through completeRequestWithError) so the frame doesn't get lost.
+
+    if (err) {
+      ALOGE("%s:%d: Error completing request for frame %d.",
+            __func__, mId, request->frame_number);
+      completeRequestWithError(request);
+      return;
+    }
+
+    // Notify the framework with the shutter time (extracted from the result).
+    int64_t timestamp = 0;
+    // TODO(b/31360070): The general metadata methods should be part of the
+    // default_camera_hal namespace, not the v4l2_camera_hal namespace.
+    int res = v4l2_camera_hal::SingleTagValue(
+        request->settings, ANDROID_SENSOR_TIMESTAMP, &timestamp);
+    if (res) {
+        ALOGE("%s:%d: Request for frame %d is missing required metadata.",
+              __func__, mId, request->frame_number);
+        // TODO(b/31653322): Send RESULT error.
+        // For now sending REQUEST error instead.
+        completeRequestWithError(request);
+        return;
+    }
+    notifyShutter(request->frame_number, timestamp);
+
+    // TODO(b/31653322): Check all returned buffers for errors
+    // (if any, send BUFFER error).
+
+    sendResult(request);
+}
+
+int Camera::flush()
+{
+    ALOGV("%s:%d: Flushing.", __func__, mId);
+    // TODO(b/32917568): Synchronization. Behave "appropriately"
+    // (i.e. according to camera3.h) if process_capture_request()
+    // is called concurrently with this (in either order).
+    // Since the callback to completeRequest also may happen on a separate
+    // thread, this function should behave nicely concurrently with that too.
+    android::Mutex::Autolock al(mFlushLock);
+
+    std::set<std::shared_ptr<CaptureRequest>> requests;
+    mInFlightTracker->Clear(&requests);
+    for (auto& request : requests) {
+        // TODO(b/31653322): See camera3.h. Should return different error
+        // depending on status of the request.
+        completeRequestWithError(request);
+    }
+
+    ALOGV("%s:%d: Flushed %u requests.", __func__, mId, requests.size());
+
+    // Call down into the device flushing.
+    return flushBuffers();
+}
+
+int Camera::preprocessCaptureBuffer(camera3_stream_buffer_t *buffer)
+{
+    int res;
+    // TODO(b/29334616): This probably should be non-blocking; part
+    // of the asynchronous request processing.
+    if (buffer->acquire_fence != -1) {
+        res = sync_wait(buffer->acquire_fence, CAMERA_SYNC_TIMEOUT);
+        if (res == -ETIME) {
+            ALOGE("%s:%d: Timeout waiting on buffer acquire fence",
+                    __func__, mId);
+            return res;
+        } else if (res) {
+            ALOGE("%s:%d: Error waiting on buffer acquire fence: %s(%d)",
+                    __func__, mId, strerror(-res), res);
+            return res;
+        }
+    }
+
+    // Acquire fence has been waited upon.
+    buffer->acquire_fence = -1;
+    // No release fence waiting unless the device sets it.
+    buffer->release_fence = -1;
+
+    buffer->status = CAMERA3_BUFFER_STATUS_OK;
+    return 0;
+}
+
+void Camera::notifyShutter(uint32_t frame_number, uint64_t timestamp)
+{
+    camera3_notify_msg_t message;
+    memset(&message, 0, sizeof(message));
+    message.type = CAMERA3_MSG_SHUTTER;
+    message.message.shutter.frame_number = frame_number;
+    message.message.shutter.timestamp = timestamp;
+    mCallbackOps->notify(mCallbackOps, &message);
+}
+
+void Camera::completeRequestWithError(std::shared_ptr<CaptureRequest> request)
+{
+    // Send an error notification.
+    camera3_notify_msg_t message;
+    memset(&message, 0, sizeof(message));
+    message.type = CAMERA3_MSG_ERROR;
+    message.message.error.frame_number = request->frame_number;
+    message.message.error.error_stream = nullptr;
+    message.message.error.error_code = CAMERA3_MSG_ERROR_REQUEST;
+    mCallbackOps->notify(mCallbackOps, &message);
+
+    // TODO(b/31856611): Ensure all the buffers indicate their error status.
+
+    // Send the errored out result.
+    sendResult(request);
+}
+
+void Camera::sendResult(std::shared_ptr<CaptureRequest> request) {
+    // Fill in the result struct
+    // (it only needs to live until the end of the framework callback).
+    camera3_capture_result_t result {
+        request->frame_number,
+        request->settings.getAndLock(),
+        request->output_buffers.size(),
+        request->output_buffers.data(),
+        request->input_buffer.get(),
+        1  // Total result; only 1 part.
+    };
+    // Make the framework callback.
+    mCallbackOps->process_capture_result(mCallbackOps, &result);
+}
+
+void Camera::dump(int fd)
+{
+    ALOGV("%s:%d: Dumping to fd %d", __func__, mId, fd);
+    ATRACE_CALL();
+    android::Mutex::Autolock al(mDeviceLock);
+
+    dprintf(fd, "Camera ID: %d (Busy: %d)\n", mId, mBusy);
+
+    // TODO: dump all settings
+}
+
+const char* Camera::templateToString(int type)
+{
+    switch (type) {
+    case CAMERA3_TEMPLATE_PREVIEW:
+        return "CAMERA3_TEMPLATE_PREVIEW";
+    case CAMERA3_TEMPLATE_STILL_CAPTURE:
+        return "CAMERA3_TEMPLATE_STILL_CAPTURE";
+    case CAMERA3_TEMPLATE_VIDEO_RECORD:
+        return "CAMERA3_TEMPLATE_VIDEO_RECORD";
+    case CAMERA3_TEMPLATE_VIDEO_SNAPSHOT:
+        return "CAMERA3_TEMPLATE_VIDEO_SNAPSHOT";
+    case CAMERA3_TEMPLATE_ZERO_SHUTTER_LAG:
+        return "CAMERA3_TEMPLATE_ZERO_SHUTTER_LAG";
+    }
+    // TODO: support vendor templates
+    return "Invalid template type!";
+}
+
+extern "C" {
+// Get handle to camera from device priv data
+static Camera *camdev_to_camera(const camera3_device_t *dev)
+{
+    return reinterpret_cast<Camera*>(dev->priv);
+}
+
+static int initialize(const camera3_device_t *dev,
+        const camera3_callback_ops_t *callback_ops)
+{
+    return camdev_to_camera(dev)->initialize(callback_ops);
+}
+
+static int configure_streams(const camera3_device_t *dev,
+        camera3_stream_configuration_t *stream_list)
+{
+    return camdev_to_camera(dev)->configureStreams(stream_list);
+}
+
+static const camera_metadata_t *construct_default_request_settings(
+        const camera3_device_t *dev, int type)
+{
+    return camdev_to_camera(dev)->constructDefaultRequestSettings(type);
+}
+
+static int process_capture_request(const camera3_device_t *dev,
+        camera3_capture_request_t *request)
+{
+    return camdev_to_camera(dev)->processCaptureRequest(request);
+}
+
+static void dump(const camera3_device_t *dev, int fd)
+{
+    camdev_to_camera(dev)->dump(fd);
+}
+
+static int flush(const camera3_device_t *dev)
+{
+    return camdev_to_camera(dev)->flush();
+}
+
+} // extern "C"
+
+const camera3_device_ops_t Camera::sOps = {
+    .initialize = default_camera_hal::initialize,
+    .configure_streams = default_camera_hal::configure_streams,
+    .register_stream_buffers = nullptr,
+    .construct_default_request_settings
+        = default_camera_hal::construct_default_request_settings,
+    .process_capture_request = default_camera_hal::process_capture_request,
+    .get_metadata_vendor_tag_ops = nullptr,
+    .dump = default_camera_hal::dump,
+    .flush = default_camera_hal::flush,
+    .reserved = {0},
+};
+
+}  // namespace default_camera_hal
diff --git a/modules/camera/3_4/camera.h b/modules/camera/3_4/camera.h
new file mode 100644
index 0000000..687c733
--- /dev/null
+++ b/modules/camera/3_4/camera.h
@@ -0,0 +1,145 @@
+/*
+ * 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.
+ */
+
+// Modified from hardware/libhardware/modules/camera/Camera.h
+
+#ifndef DEFAULT_CAMERA_HAL_CAMERA_H_
+#define DEFAULT_CAMERA_HAL_CAMERA_H_
+
+#include <camera/CameraMetadata.h>
+#include <hardware/hardware.h>
+#include <hardware/camera3.h>
+#include <utils/Mutex.h>
+
+#include "capture_request.h"
+#include "metadata/metadata.h"
+#include "request_tracker.h"
+#include "static_properties.h"
+
+namespace default_camera_hal {
+// Camera represents a physical camera on a device.
+// This is constructed when the HAL module is loaded, one per physical camera.
+// TODO(b/29185945): Support hotplugging.
+// It is opened by the framework, and must be closed before it can be opened
+// again.
+// This is an abstract class, containing all logic and data shared between all
+// camera devices (front, back, etc) and common to the ISP.
+class Camera {
+    public:
+        // id is used to distinguish cameras. 0 <= id < NUM_CAMERAS.
+        // module is a handle to the HAL module, used when the device is opened.
+        Camera(int id);
+        virtual ~Camera();
+
+        // Common Camera Device Operations (see <hardware/camera_common.h>)
+        int openDevice(const hw_module_t *module, hw_device_t **device);
+        int getInfo(struct camera_info *info);
+        int close();
+
+        // Camera v3 Device Operations (see <hardware/camera3.h>)
+        int initialize(const camera3_callback_ops_t *callback_ops);
+        int configureStreams(camera3_stream_configuration_t *stream_list);
+        const camera_metadata_t *constructDefaultRequestSettings(int type);
+        int processCaptureRequest(camera3_capture_request_t *temp_request);
+        void dump(int fd);
+        int flush();
+
+    protected:
+        // Connect to the device: open dev nodes, etc.
+        virtual int connect() = 0;
+        // Disconnect from the device: close dev nodes, etc.
+        virtual void disconnect() = 0;
+        // Initialize static camera characteristics for individual device
+        virtual int initStaticInfo(android::CameraMetadata* out) = 0;
+        // Initialize a template of the given type
+        virtual int initTemplate(int type, android::CameraMetadata* out) = 0;
+        // Initialize device info: resource cost and conflicting devices
+        // (/conflicting devices length)
+        virtual void initDeviceInfo(struct camera_info *info) = 0;
+        // Separate initialization method for individual devices when opened
+        virtual int initDevice() = 0;
+        // Verify stream configuration dataspaces and rotation values
+        virtual bool validateDataspacesAndRotations(
+            const camera3_stream_configuration_t* stream_config) = 0;
+        // Set up the streams, including seting usage & max_buffers
+        virtual int setupStreams(
+            camera3_stream_configuration_t* stream_config) = 0;
+        // Verify settings are valid for a capture or reprocessing
+        virtual bool isValidRequestSettings(
+            const android::CameraMetadata& settings) = 0;
+        // Enqueue a request to receive data from the camera
+        virtual int enqueueRequest(
+            std::shared_ptr<CaptureRequest> request) = 0;
+        // Flush in flight buffers.
+        virtual int flushBuffers() = 0;
+
+
+        // Callback for when the device has filled in the requested data.
+        // Fills in the result struct, validates the data, sends appropriate
+        // notifications, and returns the result to the framework.
+        void completeRequest(
+            std::shared_ptr<CaptureRequest> request, int err);
+        // Prettyprint template names
+        const char* templateToString(int type);
+
+    private:
+        // Camera device handle returned to framework for use
+        camera3_device_t mDevice;
+        // Get static info from the device and store it in mStaticInfo.
+        int loadStaticInfo();
+        // Confirm that a stream configuration is valid.
+        int validateStreamConfiguration(
+            const camera3_stream_configuration_t* stream_config);
+        // Verify settings are valid for reprocessing an input buffer
+        bool isValidReprocessSettings(const camera_metadata_t *settings);
+        // Pre-process an output buffer
+        int preprocessCaptureBuffer(camera3_stream_buffer_t *buffer);
+        // Send a shutter notify message with start of exposure time
+        void notifyShutter(uint32_t frame_number, uint64_t timestamp);
+        // Send an error message and return the errored out result.
+        void completeRequestWithError(std::shared_ptr<CaptureRequest> request);
+        // Send a capture result for a request.
+        void sendResult(std::shared_ptr<CaptureRequest> request);
+        // Is type a valid template type (and valid index into mTemplates)
+        bool isValidTemplateType(int type);
+
+        // Identifier used by framework to distinguish cameras
+        const int mId;
+        // CameraMetadata containing static characteristics
+        std::unique_ptr<StaticProperties> mStaticInfo;
+        // Flag indicating if settings have been set since
+        // the last configure_streams() call.
+        bool mSettingsSet;
+        // Busy flag indicates camera is in use
+        bool mBusy;
+        // Camera device operations handle shared by all devices
+        const static camera3_device_ops_t sOps;
+        // Methods used to call back into the framework
+        const camera3_callback_ops_t *mCallbackOps;
+        // Lock protecting the Camera object for modifications
+        android::Mutex mDeviceLock;
+        // Lock protecting only static camera characteristics, which may
+        // be accessed without the camera device open
+        android::Mutex mStaticInfoLock;
+        android::Mutex mFlushLock;
+        // Standard camera settings templates
+        std::unique_ptr<const android::CameraMetadata> mTemplates[CAMERA3_TEMPLATE_COUNT];
+        // Track in flight requests.
+        std::unique_ptr<RequestTracker> mInFlightTracker;
+};
+}  // namespace default_camera_hal
+
+#endif  // DEFAULT_CAMERA_HAL_CAMERA_H_
diff --git a/modules/camera/3_4/capture_request.cpp b/modules/camera/3_4/capture_request.cpp
new file mode 100644
index 0000000..00b20cd
--- /dev/null
+++ b/modules/camera/3_4/capture_request.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright 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 "capture_request.h"
+
+#include <set>
+
+namespace default_camera_hal {
+
+CaptureRequest::CaptureRequest() : CaptureRequest(nullptr) {}
+
+CaptureRequest::CaptureRequest(const camera3_capture_request_t* request) {
+  if (!request) {
+    return;
+  }
+
+  frame_number = request->frame_number;
+
+  // CameraMetadata makes copies of camera_metadata_t through the
+  // assignment operator (the constructor taking a camera_metadata_t*
+  // takes ownership instead).
+  settings = request->settings;
+
+  // camera3_stream_buffer_t can be default copy constructed,
+  // as its pointer values are handles, not ownerships.
+
+  // Copy the input buffer.
+  if (request->input_buffer) {
+    input_buffer =
+        std::make_unique<camera3_stream_buffer_t>(*request->input_buffer);
+  }
+
+  // Safely copy all the output buffers.
+  uint32_t num_output_buffers = request->num_output_buffers;
+  if (num_output_buffers < 0 || !request->output_buffers) {
+    num_output_buffers = 0;
+  }
+  output_buffers.insert(output_buffers.end(),
+                        request->output_buffers,
+                        request->output_buffers + num_output_buffers);
+}
+
+}  // namespace default_camera_hal
diff --git a/modules/camera/3_4/capture_request.h b/modules/camera/3_4/capture_request.h
new file mode 100644
index 0000000..e2f95fa
--- /dev/null
+++ b/modules/camera/3_4/capture_request.h
@@ -0,0 +1,43 @@
+/*
+ * 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 DEFAULT_CAMERA_HAL_CAPTURE_REQUEST_H_
+#define DEFAULT_CAMERA_HAL_CAPTURE_REQUEST_H_
+
+#include <memory>
+#include <vector>
+
+#include <camera/CameraMetadata.h>
+#include <hardware/camera3.h>
+
+namespace default_camera_hal {
+
+// A simple wrapper for camera3_capture_request_t,
+// with a constructor that makes a deep copy from the original struct.
+struct CaptureRequest {
+  uint32_t frame_number;
+  android::CameraMetadata settings;
+  std::unique_ptr<camera3_stream_buffer_t> input_buffer;
+  std::vector<camera3_stream_buffer_t> output_buffers;
+
+  CaptureRequest();
+  // Create a deep copy of |request|.
+  CaptureRequest(const camera3_capture_request_t* request);
+};
+
+}  // namespace default_camera_hal
+
+#endif  // DEFAULT_CAMERA_HAL_CAPTURE_REQUEST_H_
diff --git a/modules/camera/3_4/common.h b/modules/camera/3_4/common.h
new file mode 100644
index 0000000..ca5151d
--- /dev/null
+++ b/modules/camera/3_4/common.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2015 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_COMMON_H_
+#define V4L2_CAMERA_HAL_COMMON_H_
+
+// #define LOG_NDEBUG 0
+#include <cutils/log.h>
+
+#define LOG_TAG "V4L2CameraHAL"
+
+// Helpers of logging (showing function name and line number).
+#define HAL_LOGE(fmt, args...) do { \
+    ALOGE("%s:%d: " fmt, __func__, __LINE__, ##args);   \
+  } while(0)
+
+#define HAL_LOGE_IF(cond, fmt, args...) do { \
+    ALOGE_IF(cond, "%s:%d: " fmt, __func__, __LINE__, ##args);  \
+  } while(0)
+
+#define HAL_LOGW(fmt, args...) do { \
+    ALOGW("%s:%d: " fmt, __func__, __LINE__, ##args);   \
+  } while(0)
+
+#define HAL_LOGW_IF(cond, fmt, args...) do { \
+    ALOGW_IF(cond, "%s:%d: " fmt, __func__, __LINE__, ##args);  \
+  } while(0)
+
+#define HAL_LOGI(fmt, args...) do { \
+    ALOGI("%s:%d: " fmt, __func__, __LINE__, ##args);   \
+  } while(0)
+
+#define HAL_LOGI_IF(cond, fmt, args...) do { \
+    ALOGI_IF(cond, "%s:%d: " fmt, __func__, __LINE__, ##args);  \
+  } while(0)
+
+#define HAL_LOGD(fmt, args...) do { \
+    ALOGD("%s:%d: " fmt, __func__, __LINE__, ##args);   \
+  } while(0)
+
+#define HAL_LOGV(fmt, args...) do { \
+    ALOGV("%s:%d: " fmt, __func__, __LINE__, ##args);   \
+  } while(0)
+
+// Log enter/exit of methods.
+#define HAL_LOG_ENTER() HAL_LOGV("enter")
+#define HAL_LOG_EXIT() HAL_LOGV("exit")
+
+// Fix confliction in case it's defined elsewhere.
+#ifndef DISALLOW_COPY_AND_ASSIGN
+#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
+  TypeName(const TypeName&);  \
+  void operator=(const TypeName&);
+#endif
+
+#endif  // V4L2_CAMERA_HAL_COMMON_H_
diff --git a/modules/camera/3_4/format_metadata_factory.cpp b/modules/camera/3_4/format_metadata_factory.cpp
new file mode 100644
index 0000000..a08f9a8
--- /dev/null
+++ b/modules/camera/3_4/format_metadata_factory.cpp
@@ -0,0 +1,219 @@
+/*
+ * 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 "format_metadata_factory.h"
+
+#include "metadata/array_vector.h"
+#include "metadata/partial_metadata_factory.h"
+#include "metadata/property.h"
+
+namespace v4l2_camera_hal {
+
+static int GetHalFormats(const std::shared_ptr<V4L2Wrapper>& device,
+                         std::set<int32_t>* result_formats) {
+  if (!result_formats) {
+    HAL_LOGE("Null result formats pointer passed");
+    return -EINVAL;
+  }
+
+  std::set<uint32_t> v4l2_formats;
+  int res = device->GetFormats(&v4l2_formats);
+  if (res) {
+    HAL_LOGE("Failed to get device formats.");
+    return res;
+  }
+  for (auto v4l2_format : v4l2_formats) {
+    int32_t hal_format = StreamFormat::V4L2ToHalPixelFormat(v4l2_format);
+    if (hal_format < 0) {
+      // Unrecognized/unused format. Skip it.
+      continue;
+    }
+    result_formats->insert(hal_format);
+  }
+
+  // In addition to well-defined formats, there may be an
+  // "Implementation Defined" format chosen by the HAL (in this
+  // case what that means is managed by the StreamFormat class).
+
+  // Get the V4L2 format for IMPLEMENTATION_DEFINED.
+  int v4l2_format = StreamFormat::HalToV4L2PixelFormat(
+      HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED);
+  // If it's available, add IMPLEMENTATION_DEFINED to the result set.
+  if (v4l2_format && v4l2_formats.count(v4l2_format) > 0) {
+    result_formats->insert(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED);
+  }
+
+  return 0;
+}
+
+int AddFormatComponents(
+    std::shared_ptr<V4L2Wrapper> device,
+    std::insert_iterator<PartialMetadataSet> insertion_point) {
+  HAL_LOG_ENTER();
+
+  // Get all supported formats.
+  std::set<int32_t> hal_formats;
+  int res = GetHalFormats(device, &hal_formats);
+  if (res) {
+    return res;
+  }
+
+  // Requirements check: need to support YCbCr_420_888, JPEG,
+  // and "Implementation Defined".
+  if (hal_formats.find(HAL_PIXEL_FORMAT_YCbCr_420_888) == hal_formats.end()) {
+    HAL_LOGE("YCbCr_420_888 not supported by device.");
+    return -ENODEV;
+  } else if (hal_formats.find(HAL_PIXEL_FORMAT_BLOB) == hal_formats.end()) {
+    HAL_LOGE("JPEG not supported by device.");
+    return -ENODEV;
+  } else if (hal_formats.find(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED) ==
+             hal_formats.end()) {
+    HAL_LOGE("HAL implementation defined format not supported by device.");
+    return -ENODEV;
+  }
+
+  // Find sizes and frame/stall durations for all formats.
+  // We also want to find the smallest max frame duration amongst all formats,
+  // And the largest min frame duration amongst YUV (i.e. largest max frame rate
+  // supported by all YUV sizes).
+  // Stream configs are {format, width, height, direction} (input or output).
+  ArrayVector<int32_t, 4> stream_configs;
+  // Frame durations are {format, width, height, duration} (duration in ns).
+  ArrayVector<int64_t, 4> min_frame_durations;
+  // Stall durations are {format, width, height, duration} (duration in ns).
+  ArrayVector<int64_t, 4> stall_durations;
+  int64_t min_max_frame_duration = std::numeric_limits<int64_t>::max();
+  int64_t max_min_frame_duration_yuv = std::numeric_limits<int64_t>::min();
+  for (auto hal_format : hal_formats) {
+    // Get the corresponding V4L2 format.
+    uint32_t v4l2_format = StreamFormat::HalToV4L2PixelFormat(hal_format);
+    if (v4l2_format == 0) {
+      // Unrecognized/unused format. Should never happen since hal_formats
+      // came from translating a bunch of V4L2 formats above.
+      HAL_LOGE("Couldn't find V4L2 format for HAL format %d", hal_format);
+      return -ENODEV;
+    }
+
+    // Get the available sizes for this format.
+    std::set<std::array<int32_t, 2>> frame_sizes;
+    res = device->GetFormatFrameSizes(v4l2_format, &frame_sizes);
+    if (res) {
+      HAL_LOGE("Failed to get all frame sizes for format %d", v4l2_format);
+      return res;
+    }
+
+    for (const auto& frame_size : frame_sizes) {
+      // Note the format and size combination in stream configs.
+      stream_configs.push_back(
+          {{hal_format,
+            frame_size[0],
+            frame_size[1],
+            ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT}});
+
+      // Find the duration range for this format and size.
+      std::array<int64_t, 2> duration_range;
+      res = device->GetFormatFrameDurationRange(
+          v4l2_format, frame_size, &duration_range);
+      if (res) {
+        HAL_LOGE(
+            "Failed to get frame duration range for format %d, "
+            "size %u x %u",
+            v4l2_format,
+            frame_size[0],
+            frame_size[1]);
+        return res;
+      }
+      int64_t size_min_frame_duration = duration_range[0];
+      int64_t size_max_frame_duration = duration_range[1];
+      min_frame_durations.push_back({{hal_format,
+                                      frame_size[0],
+                                      frame_size[1],
+                                      size_min_frame_duration}});
+
+      // Note the stall duration for this format and size.
+      // Usually 0 for non-jpeg, non-zero for JPEG.
+      // Randomly choosing absurd 1 sec for JPEG. Unsure what this breaks.
+      int64_t stall_duration = 0;
+      if (hal_format == HAL_PIXEL_FORMAT_BLOB) {
+        stall_duration = 1000000000;
+      }
+      stall_durations.push_back(
+          {{hal_format, frame_size[0], frame_size[1], stall_duration}});
+
+      // Update our search for general min & max frame durations.
+      // In theory max frame duration (min frame rate) should be consistent
+      // between all formats, but we check and only advertise the smallest
+      // available max duration just in case.
+      if (size_max_frame_duration < min_max_frame_duration) {
+        min_max_frame_duration = size_max_frame_duration;
+      }
+      // We only care about the largest min frame duration
+      // (smallest max frame rate) for YUV sizes.
+      if (hal_format == HAL_PIXEL_FORMAT_YCbCr_420_888 &&
+          size_min_frame_duration > max_min_frame_duration_yuv) {
+        max_min_frame_duration_yuv = size_min_frame_duration;
+      }
+    }
+  }
+
+  // Convert from frame durations measured in ns.
+  // Min fps supported by all formats.
+  int32_t min_fps = 1000000000 / min_max_frame_duration;
+  if (min_fps > 15) {
+    HAL_LOGE("Minimum FPS %d is larger than HAL max allowable value of 15",
+             min_fps);
+    return -ENODEV;
+  }
+  // Max fps supported by all YUV formats.
+  int32_t max_yuv_fps = 1000000000 / max_min_frame_duration_yuv;
+  // ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES should be at minimum
+  // {mi, ma}, {ma, ma} where mi and ma are min and max frame rates for
+  // YUV_420_888. Min should be at most 15.
+  std::vector<std::array<int32_t, 2>> fps_ranges;
+  fps_ranges.push_back({{min_fps, max_yuv_fps}});
+
+  std::array<int32_t, 2> video_fps_range;
+  int32_t video_fps = 30;
+  if (video_fps >= max_yuv_fps) {
+    video_fps_range = {{max_yuv_fps, max_yuv_fps}};
+  } else {
+    video_fps_range = {{video_fps, video_fps}};
+  }
+  fps_ranges.push_back(video_fps_range);
+
+  // Construct the metadata components.
+  insertion_point = std::make_unique<Property<ArrayVector<int32_t, 4>>>(
+      ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS,
+      std::move(stream_configs));
+  insertion_point = std::make_unique<Property<ArrayVector<int64_t, 4>>>(
+      ANDROID_SCALER_AVAILABLE_MIN_FRAME_DURATIONS,
+      std::move(min_frame_durations));
+  insertion_point = std::make_unique<Property<ArrayVector<int64_t, 4>>>(
+      ANDROID_SCALER_AVAILABLE_STALL_DURATIONS, std::move(stall_durations));
+  insertion_point = std::make_unique<Property<int64_t>>(
+      ANDROID_SENSOR_INFO_MAX_FRAME_DURATION, min_max_frame_duration);
+  // TODO(b/31019725): This should probably not be a NoEffect control.
+  insertion_point = NoEffectMenuControl<std::array<int32_t, 2>>(
+      ANDROID_CONTROL_AE_TARGET_FPS_RANGE,
+      ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES,
+      fps_ranges,
+      {{CAMERA3_TEMPLATE_VIDEO_RECORD, video_fps_range},
+       {OTHER_TEMPLATES, fps_ranges[0]}});
+
+  return 0;
+}
+
+}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/format_metadata_factory.h b/modules/camera/3_4/format_metadata_factory.h
new file mode 100644
index 0000000..4cf5952
--- /dev/null
+++ b/modules/camera/3_4/format_metadata_factory.h
@@ -0,0 +1,38 @@
+/*
+ * 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_FORMAT_METADATA_FACTORY_H_
+#define V4L2_CAMERA_HAL_FORMAT_METADATA_FACTORY_H_
+
+#include <iterator>
+#include <memory>
+#include <set>
+
+#include "common.h"
+#include "metadata/metadata_common.h"
+#include "v4l2_wrapper.h"
+
+namespace v4l2_camera_hal {
+
+// A factory method to construct all the format-related
+// partial metadata for a V4L2 device.
+int AddFormatComponents(
+    std::shared_ptr<V4L2Wrapper> device,
+    std::insert_iterator<PartialMetadataSet> insertion_point);
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_FORMAT_METADATA_FACTORY_H_
diff --git a/modules/camera/3_4/format_metadata_factory_test.cpp b/modules/camera/3_4/format_metadata_factory_test.cpp
new file mode 100644
index 0000000..d37b09f
--- /dev/null
+++ b/modules/camera/3_4/format_metadata_factory_test.cpp
@@ -0,0 +1,157 @@
+/*
+ * 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 "format_metadata_factory.h"
+#include "metadata/test_common.h"
+#include "v4l2_wrapper_mock.h"
+
+using testing::AtLeast;
+using testing::Expectation;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::Test;
+using testing::_;
+
+namespace v4l2_camera_hal {
+
+class FormatMetadataFactoryTest : public Test {
+ protected:
+  virtual void SetUp() { mock_device_.reset(new V4L2WrapperMock()); }
+
+  virtual void ExpectMetadataTagCount(const android::CameraMetadata& metadata,
+                                      uint32_t tag,
+                                      size_t count) {
+    camera_metadata_ro_entry_t entry = metadata.find(tag);
+    EXPECT_EQ(entry.count, count);
+  }
+
+  std::shared_ptr<V4L2WrapperMock> mock_device_;
+};
+
+TEST_F(FormatMetadataFactoryTest, GetFormatMetadata) {
+  std::set<uint32_t> formats{V4L2_PIX_FMT_JPEG, V4L2_PIX_FMT_YUV420};
+  std::map<uint32_t, std::set<std::array<int32_t, 2>>> sizes{
+      {V4L2_PIX_FMT_JPEG, {{{10, 20}}, {{30, 60}}, {{120, 240}}}},
+      {V4L2_PIX_FMT_YUV420, {{{1, 2}}, {{3, 6}}, {{12, 24}}}}};
+  // These need to be on the correct order of magnitude,
+  // as there is a check for min fps > 15.
+  std::map<uint32_t, std::map<std::array<int32_t, 2>, std::array<int64_t, 2>>>
+      durations{{V4L2_PIX_FMT_JPEG,
+                 {{{{10, 20}}, {{100000000, 200000000}}},
+                  {{{30, 60}}, {{1000000000, 2000000000}}},
+                  {{{120, 240}}, {{700000000, 1200000000}}}}},
+                {V4L2_PIX_FMT_YUV420,
+                 {{{{1, 2}}, {{10000000000, 20000000000}}},
+                  {{{3, 6}}, {{11000000000, 21000000000}}},
+                  {{{12, 24}}, {{10500000000, 19000000000}}}}}};
+
+  // Device must support IMPLEMENTATION_DEFINED (as well as JPEG & YUV).
+  // Just duplicate the values from another format.
+  uint32_t imp_defined_format = StreamFormat::HalToV4L2PixelFormat(
+      HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED);
+  formats.insert(imp_defined_format);
+  sizes[imp_defined_format] = sizes[V4L2_PIX_FMT_YUV420];
+  durations[imp_defined_format] = durations[V4L2_PIX_FMT_YUV420];
+
+  EXPECT_CALL(*mock_device_, GetFormats(_))
+      .WillOnce(DoAll(SetArgPointee<0>(formats), Return(0)));
+
+  for (auto format : formats) {
+    std::set<std::array<int32_t, 2>> format_sizes = sizes[format];
+    EXPECT_CALL(*mock_device_, GetFormatFrameSizes(format, _))
+        .Times(AtLeast(1))
+        .WillRepeatedly(DoAll(SetArgPointee<1>(format_sizes), Return(0)));
+    for (auto size : format_sizes) {
+      EXPECT_CALL(*mock_device_, GetFormatFrameDurationRange(format, size, _))
+          .Times(AtLeast(1))
+          .WillRepeatedly(
+              DoAll(SetArgPointee<2>(durations[format][size]), Return(0)));
+    }
+  }
+
+  PartialMetadataSet components;
+  ASSERT_EQ(AddFormatComponents(mock_device_,
+                                std::inserter(components, components.end())),
+            0);
+
+  for (auto& component : components) {
+    android::CameraMetadata metadata;
+    component->PopulateStaticFields(&metadata);
+    ASSERT_EQ(metadata.entryCount(), 1);
+    int32_t tag = component->StaticTags()[0];
+    switch (tag) {
+      case ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS:  // Fall through.
+      case ANDROID_SCALER_AVAILABLE_MIN_FRAME_DURATIONS:    // Fall through.
+      case ANDROID_SCALER_AVAILABLE_STALL_DURATIONS:        // Fall through.
+        // 3 sizes per format, 4 elements per config.
+        // # formats + 1 for IMPLEMENTATION_DEFINED.
+        ExpectMetadataTagCount(metadata, tag, (formats.size() + 1) * 3 * 4);
+        break;
+      case ANDROID_SENSOR_INFO_MAX_FRAME_DURATION:
+        // The lowest max duration from above.
+        ExpectMetadataEq(metadata, tag, 200000000);
+        break;
+      case ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES:
+        // 2 ranges ({min, max} and {max, max}), each with a min and max.
+        ExpectMetadataTagCount(metadata, tag, 4);
+        break;
+      default:
+        FAIL() << "Unexpected component created.";
+        break;
+    }
+  }
+}
+
+TEST_F(FormatMetadataFactoryTest, GetFormatMetadataMissingJpeg) {
+  uint32_t imp_defined_format = StreamFormat::HalToV4L2PixelFormat(
+      HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED);
+  std::set<uint32_t> formats{V4L2_PIX_FMT_YUV420, imp_defined_format};
+  EXPECT_CALL(*mock_device_, GetFormats(_))
+      .WillOnce(DoAll(SetArgPointee<0>(formats), Return(0)));
+  PartialMetadataSet components;
+  ASSERT_EQ(AddFormatComponents(mock_device_,
+                                std::inserter(components, components.end())),
+            -ENODEV);
+}
+
+TEST_F(FormatMetadataFactoryTest, GetFormatMetadataMissingYuv) {
+  uint32_t imp_defined_format = StreamFormat::HalToV4L2PixelFormat(
+      HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED);
+  std::set<uint32_t> formats{V4L2_PIX_FMT_JPEG, imp_defined_format};
+  EXPECT_CALL(*mock_device_, GetFormats(_))
+      .WillOnce(DoAll(SetArgPointee<0>(formats), Return(0)));
+  PartialMetadataSet components;
+  ASSERT_EQ(AddFormatComponents(mock_device_,
+                                std::inserter(components, components.end())),
+            -ENODEV);
+}
+
+TEST_F(FormatMetadataFactoryTest,
+       GetFormatMetadataMissingImplementationDefined) {
+  std::set<uint32_t> formats{V4L2_PIX_FMT_JPEG, V4L2_PIX_FMT_YUV420};
+  EXPECT_CALL(*mock_device_, GetFormats(_))
+      .WillOnce(DoAll(SetArgPointee<0>(formats), Return(0)));
+  PartialMetadataSet components;
+  ASSERT_EQ(AddFormatComponents(mock_device_,
+                                std::inserter(components, components.end())),
+            -ENODEV);
+}
+
+}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/function_thread.h b/modules/camera/3_4/function_thread.h
new file mode 100644
index 0000000..7a7b1e3
--- /dev/null
+++ b/modules/camera/3_4/function_thread.h
@@ -0,0 +1,43 @@
+/*
+ * 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_FUNCTION_THREAD_H_
+#define V4L2_CAMERA_HAL_FUNCTION_THREAD_H_
+
+#include <functional>
+
+#include <utils/Thread.h>
+
+#include "common.h"
+
+namespace v4l2_camera_hal {
+
+class FunctionThread : public android::Thread {
+ public:
+  FunctionThread(std::function<bool()> function) : function_(function){};
+
+ private:
+  bool threadLoop() override {
+    bool result = function_();
+    return result;
+  };
+
+  std::function<bool()> function_;
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_FUNCTION_THREAD_H_
diff --git a/modules/camera/3_4/metadata/array_vector.h b/modules/camera/3_4/metadata/array_vector.h
new file mode 100644
index 0000000..0481ed4
--- /dev/null
+++ b/modules/camera/3_4/metadata/array_vector.h
@@ -0,0 +1,52 @@
+/*
+ * 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_ARRAY_VECTOR_H_
+#define V4L2_CAMERA_HAL_ARRAY_VECTOR_H_
+
+#include <array>
+#include <vector>
+
+namespace v4l2_camera_hal {
+// ArrayVector behaves like a std::vector of fixed length C arrays,
+// with push_back accepting std::arrays to standardize length.
+// Specific methods to get number of arrays/number of elements
+// are provided and an ambiguous "size" is not, to avoid accidental
+// incorrect use.
+template <class T, size_t N>
+class ArrayVector {
+ public:
+  const T* data() const { return mItems.data(); }
+  // The number of arrays.
+  size_t num_arrays() const { return mItems.size() / N; }
+  // The number of elements amongst all arrays.
+  size_t total_num_elements() const { return mItems.size(); }
+
+  // Access the ith array.
+  const T* operator[](int i) const { return mItems.data() + (i * N); }
+  T* operator[](int i) { return mItems.data() + (i * N); }
+
+  void push_back(const std::array<T, N>& values) {
+    mItems.insert(mItems.end(), values.begin(), values.end());
+  }
+
+ private:
+  std::vector<T> mItems;
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_ARRAY_VECTOR_H_
diff --git a/modules/camera/3_4/metadata/boottime_state_delegate.cpp b/modules/camera/3_4/metadata/boottime_state_delegate.cpp
new file mode 100644
index 0000000..5024cb2
--- /dev/null
+++ b/modules/camera/3_4/metadata/boottime_state_delegate.cpp
@@ -0,0 +1,39 @@
+/*
+ * 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 <errno.h>
+#include <string.h>
+
+#include "boottime_state_delegate.h"
+
+namespace v4l2_camera_hal {
+
+int BoottimeStateDelegate::GetValue(int64_t* value) {
+  struct timespec ts;
+
+  int res = clock_gettime(CLOCK_BOOTTIME, &ts);
+  if (res) {
+    HAL_LOGE("Failed to get BOOTTIME for state delegate: %d (%s)",
+             errno,
+             strerror(errno));
+    return -errno;
+  }
+  *value = ts.tv_sec * 1000000000ULL + ts.tv_nsec;
+
+  return 0;
+}
+
+}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/metadata/boottime_state_delegate.h b/modules/camera/3_4/metadata/boottime_state_delegate.h
new file mode 100644
index 0000000..0a5c4b9
--- /dev/null
+++ b/modules/camera/3_4/metadata/boottime_state_delegate.h
@@ -0,0 +1,37 @@
+/*
+ * 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_BOOTTIME_STATE_DELEGATE_H_
+#define V4L2_CAMERA_HAL_METADATA_BOOTTIME_STATE_DELEGATE_H_
+
+#include "../common.h"
+#include "state_delegate_interface.h"
+
+namespace v4l2_camera_hal {
+
+// A StateDelegate is simply a dynamic value that can be queried.
+// The value may change between queries.
+class BoottimeStateDelegate : public StateDelegateInterface<int64_t> {
+ public:
+  BoottimeStateDelegate(){};
+  ~BoottimeStateDelegate(){};
+
+  int GetValue(int64_t* value) override;
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_BOOTTIME_STATE_DELEGATE_H_
diff --git a/modules/camera/3_4/metadata/control.h b/modules/camera/3_4/metadata/control.h
new file mode 100644
index 0000000..ad3f87b
--- /dev/null
+++ b/modules/camera/3_4/metadata/control.h
@@ -0,0 +1,196 @@
+/*
+ * 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_H_
+#define V4L2_CAMERA_HAL_METADATA_CONTROL_H_
+
+#include <vector>
+
+#include <system/camera_metadata.h>
+
+#include "../common.h"
+#include "metadata_common.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 PartialMetadataInterface {
+ public:
+  // Options are optional (i.e. nullable), delegate is not.
+  Control(std::unique_ptr<TaggedControlDelegate<T>> delegate,
+          std::unique_ptr<TaggedControlOptions<T>> options = nullptr);
+
+  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 int PopulateTemplateRequest(
+      int template_type, android::CameraMetadata* metadata) const override;
+  virtual bool SupportsRequestValues(
+      const android::CameraMetadata& metadata) const override;
+  virtual int SetRequestValues(
+      const android::CameraMetadata& metadata) override;
+
+ private:
+  std::unique_ptr<TaggedControlDelegate<T>> delegate_;
+  std::unique_ptr<TaggedControlOptions<T>> options_;
+
+  DISALLOW_COPY_AND_ASSIGN(Control);
+};
+
+// -----------------------------------------------------------------------------
+
+template <typename T>
+Control<T>::Control(std::unique_ptr<TaggedControlDelegate<T>> delegate,
+                    std::unique_ptr<TaggedControlOptions<T>> options)
+    : delegate_(std::move(delegate)), options_(std::move(options)) {}
+
+template <typename T>
+std::vector<int32_t> Control<T>::StaticTags() const {
+  std::vector<int32_t> result;
+  if (options_ && options_->tag() != DO_NOT_REPORT_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 {
+  if (!options_) {
+    HAL_LOGV("No options for control %d, nothing to populate.",
+             delegate_->tag());
+    return 0;
+  } else if (options_->tag() == DO_NOT_REPORT_OPTIONS) {
+    HAL_LOGV(
+        "Options for control %d are not reported, "
+        "probably are set values defined and already known by the API.",
+        delegate_->tag());
+    return 0;
+  }
+
+  return UpdateMetadata(
+      metadata, options_->tag(), options_->MetadataRepresentation());
+}
+
+template <typename T>
+int Control<T>::PopulateDynamicFields(android::CameraMetadata* metadata) const {
+  // Populate the current setting.
+  T value;
+  int res = delegate_->GetValue(&value);
+  if (res) {
+    return res;
+  }
+  return UpdateMetadata(metadata, delegate_->tag(), value);
+}
+
+template <typename T>
+int Control<T>::PopulateTemplateRequest(
+    int template_type, android::CameraMetadata* metadata) const {
+  // Populate with a default.
+  T value;
+  int res;
+  if (options_) {
+    res = options_->DefaultValueForTemplate(template_type, &value);
+  } else {
+    // If there's no options (and thus no default option),
+    // fall back to whatever the current value is.
+    res = delegate_->GetValue(&value);
+  }
+  if (res) {
+    return res;
+  }
+
+  return UpdateMetadata(metadata, delegate_->tag(), value);
+}
+
+template <typename T>
+bool Control<T>::SupportsRequestValues(
+    const android::CameraMetadata& metadata) const {
+  if (metadata.isEmpty()) {
+    // Implicitly supported.
+    return true;
+  }
+
+  // Get the requested setting for this control.
+  T 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",
+             delegate_->tag());
+    return false;
+  }
+
+  // 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>
+int Control<T>::SetRequestValues(const android::CameraMetadata& metadata) {
+  if (metadata.isEmpty()) {
+    // No changes necessary.
+    return 0;
+  }
+
+  // Get the requested value.
+  T 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",
+             delegate_->tag());
+    return res;
+  }
+
+  // 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);
+}
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_CONTROL_H_
diff --git a/modules/camera/3_4/metadata/control_delegate_interface.h b/modules/camera/3_4/metadata/control_delegate_interface.h
new file mode 100644
index 0000000..8896e72
--- /dev/null
+++ b/modules/camera/3_4/metadata/control_delegate_interface.h
@@ -0,0 +1,41 @@
+/*
+ * 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_DELEGATE_INTERFACE_H_
+#define V4L2_CAMERA_HAL_METADATA_CONTROL_DELEGATE_INTERFACE_H_
+
+#include "state_delegate_interface.h"
+
+namespace v4l2_camera_hal {
+
+// A ControlDelegate extends StateDelegate with a setter method.
+template <typename T>
+class ControlDelegateInterface : public StateDelegateInterface<T> {
+ public:
+  virtual ~ControlDelegateInterface(){};
+
+  // ControlDelegates 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).
+  // Returns 0 on success, error code on failure.
+  virtual int SetValue(const T& value) = 0;
+  // Children must also override GetValue from StateDelegateInterface.
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_CONTROL_DELEGATE_INTERFACE_H_
diff --git a/modules/camera/3_4/metadata/control_delegate_interface_mock.h b/modules/camera/3_4/metadata/control_delegate_interface_mock.h
new file mode 100644
index 0000000..7ed05ed
--- /dev/null
+++ b/modules/camera/3_4/metadata/control_delegate_interface_mock.h
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+// Mock for control delegate interfaces.
+
+#ifndef V4L2_CAMERA_HAL_METADATA_CONTROL_DELEGATE_INTERFACE_MOCK_H_
+#define V4L2_CAMERA_HAL_METADATA_CONTROL_DELEGATE_INTERFACE_MOCK_H_
+
+#include <gmock/gmock.h>
+
+#include "control_delegate_interface.h"
+
+namespace v4l2_camera_hal {
+
+template <typename T>
+class ControlDelegateInterfaceMock : public ControlDelegateInterface<T> {
+ public:
+  ControlDelegateInterfaceMock(){};
+  MOCK_METHOD1_T(GetValue, int(T*));
+  MOCK_METHOD1_T(SetValue, int(const T&));
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_CONTROL_DELEGATE_INTERFACE_MOCK_H_
diff --git a/modules/camera/3_4/metadata/control_options_interface.h b/modules/camera/3_4/metadata/control_options_interface.h
new file mode 100644
index 0000000..438cefa
--- /dev/null
+++ b/modules/camera/3_4/metadata/control_options_interface.h
@@ -0,0 +1,44 @@
+/*
+ * 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_OPTIONS_INTERFACE_H_
+#define V4L2_CAMERA_HAL_METADATA_CONTROL_OPTIONS_INTERFACE_H_
+
+#include <vector>
+
+namespace v4l2_camera_hal {
+
+// A ControlOptions defines acceptable values for a control.
+template <typename T>
+class ControlOptionsInterface {
+ public:
+  virtual ~ControlOptionsInterface(){};
+
+  // Get a metadata-acceptable representation of the options.
+  // For enums this will be a list of values, for ranges this
+  // will be min and max, etc.
+  virtual std::vector<T> MetadataRepresentation() = 0;
+  // Get whether or not a given value is acceptable.
+  virtual bool IsSupported(const T& option);
+  // Get a default option for a given template type, from the available options.
+  // Because a default must be available, any ControlOptions should have at
+  // least one supported value.
+  virtual int DefaultValueForTemplate(int template_type, T* default_value);
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_CONTROL_OPTIONS_INTERFACE_H_
diff --git a/modules/camera/3_4/metadata/control_options_interface_mock.h b/modules/camera/3_4/metadata/control_options_interface_mock.h
new file mode 100644
index 0000000..ab8f6ee
--- /dev/null
+++ b/modules/camera/3_4/metadata/control_options_interface_mock.h
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+// Mock for control options interfaces.
+
+#ifndef V4L2_CAMERA_HAL_METADATA_CONTROL_OPTIONS_INTERFACE_MOCK_H_
+#define V4L2_CAMERA_HAL_METADATA_CONTROL_OPTIONS_INTERFACE_MOCK_H_
+
+#include <gmock/gmock.h>
+
+#include "control_options_interface.h"
+
+namespace v4l2_camera_hal {
+
+template <typename T>
+class ControlOptionsInterfaceMock : public ControlOptionsInterface<T> {
+ public:
+  ControlOptionsInterfaceMock(){};
+  MOCK_METHOD0_T(MetadataRepresentation, std::vector<T>());
+  MOCK_METHOD1_T(IsSupported, bool(const T&));
+  MOCK_METHOD2_T(DefaultValueForTemplate, int(int, T*));
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_CONTROL_OPTIONS_INTERFACE_MOCK_H_
diff --git a/modules/camera/3_4/metadata/control_test.cpp b/modules/camera/3_4/metadata/control_test.cpp
new file mode 100644
index 0000000..f76376c
--- /dev/null
+++ b/modules/camera/3_4/metadata/control_test.cpp
@@ -0,0 +1,457 @@
+/*
+ * 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 "control.h"
+
+#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::SetArgPointee;
+using testing::Test;
+using testing::_;
+
+namespace v4l2_camera_hal {
+
+class ControlTest : public Test {
+ protected:
+  virtual void SetUp() {
+    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();
+  }
+
+  virtual void PrepareControl() {
+    // 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>>(
+            report_options_ ? options_tag_ : DO_NOT_REPORT_OPTIONS,
+            std::move(mock_options_));
+    if (use_options_) {
+      control_.reset(
+          new Control<uint8_t>(std::move(delegate), std::move(options)));
+    } else {
+      control_.reset(new Control<uint8_t>(std::move(delegate)));
+    }
+  }
+
+  virtual void ExpectTags() {
+    if (use_options_ && report_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);
+    if (use_options_ && report_options_) {
+      EXPECT_EQ(metadata.entryCount(), 1);
+      ExpectMetadataEq(metadata, options_tag_, options);
+    } else {
+      EXPECT_EQ(metadata.entryCount(), 0);
+      // Shouldn't be expecting any options.
+      EXPECT_TRUE(options.empty());
+    }
+  }
+
+  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_;
+  bool use_options_ = true;
+  bool report_options_ = true;
+
+  // 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(ControlTest, Tags) {
+  PrepareControl();
+  ExpectTags();
+}
+
+TEST_F(ControlTest, TagsNoOptions) {
+  use_options_ = false;
+  PrepareControl();
+  ExpectTags();
+}
+
+TEST_F(ControlTest, TagsUnreportedOptions) {
+  report_options_ = false;
+  PrepareControl();
+  ExpectTags();
+}
+
+TEST_F(ControlTest, PopulateStatic) {
+  std::vector<uint8_t> expected{1, 10, 20};
+  EXPECT_CALL(*mock_options_, MetadataRepresentation())
+      .WillOnce(Return(expected));
+  PrepareControl();
+  ExpectOptions(expected);
+}
+
+TEST_F(ControlTest, PopulateStaticNoOptions) {
+  use_options_ = false;
+  PrepareControl();
+  ExpectOptions({});
+}
+
+TEST_F(ControlTest, PopulateStaticUnreportedOptions) {
+  report_options_ = false;
+  PrepareControl();
+  ExpectOptions({});
+}
+
+TEST_F(ControlTest, PopulateDynamic) {
+  uint8_t test_option = 99;
+  EXPECT_CALL(*mock_delegate_, GetValue(_))
+      .WillOnce(DoAll(SetArgPointee<0>(test_option), Return(0)));
+  PrepareControl();
+  ExpectValue(test_option);
+}
+
+TEST_F(ControlTest, PopulateDynamicNoOptions) {
+  // Lack of options shouldn't change anything for PopulateDynamic.
+  use_options_ = false;
+  uint8_t test_option = 99;
+  EXPECT_CALL(*mock_delegate_, GetValue(_))
+      .WillOnce(DoAll(SetArgPointee<0>(test_option), Return(0)));
+  PrepareControl();
+  ExpectValue(test_option);
+}
+
+TEST_F(ControlTest, PopulateDynamicUnreportedOptions) {
+  // Lack of reported options shouldn't change anything for PopulateDynamic.
+  report_options_ = false;
+  uint8_t test_option = 99;
+  EXPECT_CALL(*mock_delegate_, GetValue(_))
+      .WillOnce(DoAll(SetArgPointee<0>(test_option), Return(0)));
+  PrepareControl();
+  ExpectValue(test_option);
+}
+
+TEST_F(ControlTest, PopulateDynamicFail) {
+  int err = -99;
+  EXPECT_CALL(*mock_delegate_, GetValue(_)).WillOnce(Return(err));
+  PrepareControl();
+
+  android::CameraMetadata metadata;
+  EXPECT_EQ(control_->PopulateDynamicFields(&metadata), err);
+
+  // Should not have added an entry.
+  EXPECT_TRUE(metadata.isEmpty());
+}
+
+TEST_F(ControlTest, PopulateTemplate) {
+  int template_type = 3;
+  uint8_t default_value = 123;
+  EXPECT_CALL(*mock_options_, DefaultValueForTemplate(template_type, _))
+      .WillOnce(DoAll(SetArgPointee<1>(default_value), Return(0)));
+  PrepareControl();
+
+  android::CameraMetadata metadata;
+  EXPECT_EQ(control_->PopulateTemplateRequest(template_type, &metadata), 0);
+  ExpectMetadataEq(metadata, delegate_tag_, default_value);
+}
+
+TEST_F(ControlTest, PopulateTemplateFail) {
+  int template_type = 3;
+  int err = 10;
+  EXPECT_CALL(*mock_options_, DefaultValueForTemplate(template_type, _))
+      .WillOnce(Return(err));
+  PrepareControl();
+
+  android::CameraMetadata metadata;
+  EXPECT_EQ(control_->PopulateTemplateRequest(template_type, &metadata), err);
+}
+
+TEST_F(ControlTest, PopulateTemplateOptionless) {
+  use_options_ = false;
+  int template_type = 3;
+  uint8_t value = 12;
+  // Should use delegate instead of options if no options.
+  EXPECT_CALL(*mock_delegate_, GetValue(_))
+      .WillOnce(DoAll(SetArgPointee<0>(value), Return(0)));
+  PrepareControl();
+
+  android::CameraMetadata metadata;
+  EXPECT_EQ(control_->PopulateTemplateRequest(template_type, &metadata), 0);
+  ExpectMetadataEq(metadata, delegate_tag_, value);
+}
+
+TEST_F(ControlTest, PopulateTemplateOptionlessFail) {
+  use_options_ = false;
+  int template_type = 3;
+  int err = 10;
+  // Should use delegate instead of options if no options.
+  EXPECT_CALL(*mock_delegate_, GetValue(_)).WillOnce(Return(err));
+  PrepareControl();
+
+  android::CameraMetadata metadata;
+  EXPECT_EQ(control_->PopulateTemplateRequest(template_type, &metadata), err);
+}
+
+TEST_F(ControlTest, PopulateTemplateUnreportedOptions) {
+  report_options_ = false;
+  int template_type = 3;
+  uint8_t default_value = 123;
+  // Unreported options should behave just like reported ones for templating.
+  EXPECT_CALL(*mock_options_, DefaultValueForTemplate(template_type, _))
+      .WillOnce(DoAll(SetArgPointee<1>(default_value), Return(0)));
+  PrepareControl();
+
+  android::CameraMetadata metadata;
+  EXPECT_EQ(control_->PopulateTemplateRequest(template_type, &metadata), 0);
+  ExpectMetadataEq(metadata, delegate_tag_, default_value);
+}
+
+TEST_F(ControlTest, PopulateTemplateUnreportedOptionsFail) {
+  report_options_ = false;
+  int template_type = 3;
+  int err = 10;
+  // Unreported options should behave just like reported ones for templating.
+  EXPECT_CALL(*mock_options_, DefaultValueForTemplate(template_type, _))
+      .WillOnce(Return(err));
+  PrepareControl();
+
+  android::CameraMetadata metadata;
+  EXPECT_EQ(control_->PopulateTemplateRequest(template_type, &metadata), err);
+}
+
+TEST_F(ControlTest, SupportsRequest) {
+  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(true));
+  PrepareControl();
+
+  EXPECT_EQ(control_->SupportsRequestValues(metadata), true);
+}
+
+TEST_F(ControlTest, SupportsRequestNoOptions) {
+  use_options_ = false;
+  android::CameraMetadata metadata;
+  uint8_t test_option = 123;
+  ASSERT_EQ(UpdateMetadata(&metadata, delegate_tag_, test_option), 0);
+  PrepareControl();
+
+  EXPECT_EQ(control_->SupportsRequestValues(metadata), true);
+}
+
+TEST_F(ControlTest, SupportsRequestUnreportedOptions) {
+  report_options_ = false;
+  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(true));
+  PrepareControl();
+
+  EXPECT_EQ(control_->SupportsRequestValues(metadata), true);
+}
+
+TEST_F(ControlTest, SupportsRequestFail) {
+  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_->SupportsRequestValues(metadata), false);
+}
+
+TEST_F(ControlTest, SupportsRequestUnreportedOptionsFail) {
+  report_options_ = false;
+  android::CameraMetadata metadata;
+  uint8_t test_option = 123;
+  ASSERT_EQ(UpdateMetadata(&metadata, delegate_tag_, test_option), 0);
+
+  // Unreported options should still be checked against.
+  EXPECT_CALL(*mock_options_, IsSupported(test_option)).WillOnce(Return(false));
+  PrepareControl();
+
+  EXPECT_EQ(control_->SupportsRequestValues(metadata), false);
+}
+
+TEST_F(ControlTest, SupportsRequestInvalidNumber) {
+  // Start with a request for multiple values.
+  android::CameraMetadata metadata;
+  std::vector<uint8_t> test_data = {1, 2, 3};
+  ASSERT_EQ(UpdateMetadata(&metadata, delegate_tag_, test_data), 0);
+  PrepareControl();
+  EXPECT_EQ(control_->SupportsRequestValues(metadata), false);
+}
+
+TEST_F(ControlTest, SupportsRequestInvalidNumberNoOptions) {
+  use_options_ = false;
+  // Start with a request for multiple values.
+  android::CameraMetadata metadata;
+  std::vector<uint8_t> test_data = {1, 2, 3};
+  ASSERT_EQ(UpdateMetadata(&metadata, delegate_tag_, test_data), 0);
+  PrepareControl();
+  // 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;
+  uint8_t test_option = 123;
+  ASSERT_EQ(UpdateMetadata(&metadata, delegate_tag_, test_option), 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, SetRequestNoOptions) {
+  use_options_ = false;
+  android::CameraMetadata metadata;
+  uint8_t test_option = 123;
+  ASSERT_EQ(UpdateMetadata(&metadata, delegate_tag_, test_option), 0);
+
+  // No options, no validation check.
+  EXPECT_CALL(*mock_delegate_, SetValue(test_option)).WillOnce(Return(0));
+  PrepareControl();
+
+  // Make the request.
+  ASSERT_EQ(control_->SetRequestValues(metadata), 0);
+}
+
+TEST_F(ControlTest, SetRequestUnreportedOptions) {
+  report_options_ = false;
+  android::CameraMetadata metadata;
+  uint8_t test_option = 123;
+  ASSERT_EQ(UpdateMetadata(&metadata, delegate_tag_, test_option), 0);
+
+  // Unreported options still get a validation check.
+  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, SetRequestSettingFail) {
+  android::CameraMetadata metadata;
+  uint8_t test_option = 123;
+  ASSERT_EQ(UpdateMetadata(&metadata, delegate_tag_, test_option), 0);
+
+  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, delegate_tag_, test_data), 0);
+
+  PrepareControl();
+  EXPECT_EQ(control_->SetRequestValues(metadata), -EINVAL);
+}
+
+TEST_F(ControlTest, SetRequestInvalidNumberNoOptions) {
+  use_options_ = false;
+  // Start with a request for multiple values.
+  android::CameraMetadata metadata;
+  std::vector<uint8_t> test_data = {1, 2, 3};
+  ASSERT_EQ(UpdateMetadata(&metadata, delegate_tag_, test_data), 0);
+
+  PrepareControl();
+  // 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);
+}
+
+}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/metadata/converter_interface.h b/modules/camera/3_4/metadata/converter_interface.h
new file mode 100644
index 0000000..ca6a0f2
--- /dev/null
+++ b/modules/camera/3_4/metadata/converter_interface.h
@@ -0,0 +1,37 @@
+/*
+ * 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_CONVERTER_INTERFACE_H_
+#define V4L2_CAMERA_HAL_METADATA_CONVERTER_INTERFACE_H_
+
+#include "../common.h"
+
+namespace v4l2_camera_hal {
+
+// A ConverterInterface converts metadata values to V4L2 values vice-versa.
+template <typename TMetadata, typename TV4L2>
+class ConverterInterface {
+ public:
+  virtual ~ConverterInterface(){};
+
+  // Convert.
+  virtual int MetadataToV4L2(TMetadata value, TV4L2* conversion) = 0;
+  virtual int V4L2ToMetadata(TV4L2 value, TMetadata* conversion) = 0;
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_CONVERTER_INTERFACE_H_
diff --git a/modules/camera/3_4/metadata/converter_interface_mock.h b/modules/camera/3_4/metadata/converter_interface_mock.h
new file mode 100644
index 0000000..3f7e6f7
--- /dev/null
+++ b/modules/camera/3_4/metadata/converter_interface_mock.h
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+// Mock for converter interfaces.
+
+#ifndef V4L2_CAMERA_HAL_METADATA_CONVERTER_INTERFACE_MOCK_H_
+#define V4L2_CAMERA_HAL_METADATA_CONVERTER_INTERFACE_MOCK_H_
+
+#include <gmock/gmock.h>
+
+#include "converter_interface.h"
+
+namespace v4l2_camera_hal {
+
+template <typename TMetadata, typename TV4L2>
+class ConverterInterfaceMock : public ConverterInterface<TMetadata, TV4L2> {
+ public:
+  ConverterInterfaceMock(){};
+  MOCK_METHOD2_T(MetadataToV4L2, int(TMetadata, TV4L2*));
+  MOCK_METHOD2_T(V4L2ToMetadata, int(TV4L2, TMetadata*));
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_CONVERTER_INTERFACE_MOCK_H_
diff --git a/modules/camera/3_4/metadata/default_option_delegate.h b/modules/camera/3_4/metadata/default_option_delegate.h
new file mode 100644
index 0000000..f290318
--- /dev/null
+++ b/modules/camera/3_4/metadata/default_option_delegate.h
@@ -0,0 +1,62 @@
+/*
+ * 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_DEFAULT_OPTION_DELEGATE_H_
+#define V4L2_CAMERA_HAL_METADATA_DEFAULT_OPTION_DELEGATE_H_
+
+#include <errno.h>
+
+#include <map>
+
+#include <hardware/camera3.h>
+
+namespace v4l2_camera_hal {
+
+// A constant that can be used to identify an overall default.
+static constexpr int OTHER_TEMPLATES = CAMERA3_TEMPLATE_COUNT;
+
+// DefaultingOptionDelegate provides an interface to get default options from.
+template <typename T>
+class DefaultOptionDelegate {
+ public:
+  // |defaults| maps template types to default values
+  DefaultOptionDelegate(std::map<int, T> defaults)
+      : defaults_(std::move(defaults)){};
+  virtual ~DefaultOptionDelegate(){};
+
+  // Get a default value for a template type. Returns false if no default
+  // provided.
+  virtual bool DefaultValueForTemplate(int template_type, T* default_value) {
+    if (defaults_.count(template_type) > 0) {
+      // Best option is template-specific.
+      *default_value = defaults_[template_type];
+      return true;
+    } else if (defaults_.count(OTHER_TEMPLATES)) {
+      // Fall back to a general default.
+      *default_value = defaults_[OTHER_TEMPLATES];
+      return true;
+    }
+
+    return false;
+  };
+
+ private:
+  std::map<int, T> defaults_;
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_DEFAULT_OPTION_DELEGATE_H_
diff --git a/modules/camera/3_4/metadata/default_option_delegate_mock.h b/modules/camera/3_4/metadata/default_option_delegate_mock.h
new file mode 100644
index 0000000..84ec740
--- /dev/null
+++ b/modules/camera/3_4/metadata/default_option_delegate_mock.h
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+// Mock for default option delegates.
+
+#ifndef V4L2_CAMERA_HAL_METADATA_DEFAULT_OPTION_DELEGATE_MOCK_H_
+#define V4L2_CAMERA_HAL_METADATA_DEFAULT_OPTION_DELEGATE_MOCK_H_
+
+#include <gmock/gmock.h>
+
+#include "default_option_delegate.h"
+
+namespace v4l2_camera_hal {
+
+template <typename T>
+class DefaultOptionDelegateMock : public DefaultOptionDelegate<T> {
+ public:
+  DefaultOptionDelegateMock() : DefaultOptionDelegate<T>({}){};
+  MOCK_METHOD2_T(DefaultValueForTemplate, bool(int, T*));
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_DEFAULT_OPTION_DELEGATE_MOCK_H_
diff --git a/modules/camera/3_4/metadata/default_option_delegate_test.cpp b/modules/camera/3_4/metadata/default_option_delegate_test.cpp
new file mode 100644
index 0000000..7b61dd4
--- /dev/null
+++ b/modules/camera/3_4/metadata/default_option_delegate_test.cpp
@@ -0,0 +1,62 @@
+/*
+ * 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 "default_option_delegate.h"
+
+#include <memory>
+
+#include <gtest/gtest.h>
+#include <hardware/camera3.h>
+
+using testing::Test;
+
+namespace v4l2_camera_hal {
+
+class DefaultOptionDelegateTest : public Test {
+ protected:
+  virtual void SetUp() {
+    dut_.reset(new DefaultOptionDelegate<int>(defaults_));
+  }
+
+  std::unique_ptr<DefaultOptionDelegate<int>> dut_;
+  std::map<int, int> defaults_{{CAMERA3_TEMPLATE_STILL_CAPTURE, 10},
+                               {OTHER_TEMPLATES, 20},
+                               {CAMERA3_TEMPLATE_VIDEO_SNAPSHOT, 30}};
+};
+
+TEST_F(DefaultOptionDelegateTest, SpecificDefault) {
+  int actual = 0;
+  EXPECT_TRUE(
+      dut_->DefaultValueForTemplate(CAMERA3_TEMPLATE_STILL_CAPTURE, &actual));
+  EXPECT_EQ(actual, defaults_[CAMERA3_TEMPLATE_STILL_CAPTURE]);
+}
+
+TEST_F(DefaultOptionDelegateTest, GeneralDefault) {
+  int actual = 0;
+  // No ZSL default; should fall back to the OTHER_TEMPLATES default.
+  EXPECT_TRUE(dut_->DefaultValueForTemplate(CAMERA3_TEMPLATE_ZERO_SHUTTER_LAG,
+                                            &actual));
+  EXPECT_EQ(actual, defaults_[OTHER_TEMPLATES]);
+}
+
+TEST_F(DefaultOptionDelegateTest, NoDefaults) {
+  dut_.reset(new DefaultOptionDelegate<int>({}));
+  int actual = 0;
+  EXPECT_FALSE(dut_->DefaultValueForTemplate(CAMERA3_TEMPLATE_ZERO_SHUTTER_LAG,
+                                             &actual));
+}
+
+}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/metadata/enum_converter.cpp b/modules/camera/3_4/metadata/enum_converter.cpp
new file mode 100644
index 0000000..d5e0a87
--- /dev/null
+++ b/modules/camera/3_4/metadata/enum_converter.cpp
@@ -0,0 +1,78 @@
+/*
+ * 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 "enum_converter.h"
+
+#include <errno.h>
+
+#include "../common.h"
+
+namespace v4l2_camera_hal {
+
+EnumConverter::EnumConverter(
+    const std::multimap<int32_t, uint8_t>& v4l2_to_metadata)
+    : v4l2_to_metadata_(v4l2_to_metadata) {
+  HAL_LOG_ENTER();
+}
+
+int EnumConverter::MetadataToV4L2(uint8_t value, int32_t* conversion) {
+  // Unfortunately no bi-directional map lookup in C++.
+  // Breaking on second, not first found so that a warning
+  // can be given if there are multiple values.
+  size_t count = 0;
+  for (auto kv : v4l2_to_metadata_) {
+    if (kv.second == value) {
+      ++count;
+      if (count == 1) {
+        // First match.
+        *conversion = kv.first;
+      } else {
+        // second match.
+        break;
+      }
+    }
+  }
+
+  if (count == 0) {
+    HAL_LOGV("Couldn't find V4L2 conversion of metadata value %d.", value);
+    return -EINVAL;
+  } else if (count > 1) {
+    HAL_LOGV(
+        "Multiple V4L2 conversions found for metadata value %d, using first.",
+        value);
+  }
+  return 0;
+}
+
+int EnumConverter::V4L2ToMetadata(int32_t value, uint8_t* conversion) {
+  auto element_range = v4l2_to_metadata_.equal_range(value);
+  if (element_range.first == element_range.second) {
+    HAL_LOGV("Couldn't find metadata conversion of V4L2 value %d.", value);
+    return -EINVAL;
+  }
+
+  auto element = element_range.first;
+  *conversion = element->second;
+
+  if (++element != element_range.second) {
+    HAL_LOGV(
+        "Multiple metadata conversions found for V4L2 value %d, using first.",
+        value);
+  }
+  return 0;
+}
+
+}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/metadata/enum_converter.h b/modules/camera/3_4/metadata/enum_converter.h
new file mode 100644
index 0000000..df5cabb
--- /dev/null
+++ b/modules/camera/3_4/metadata/enum_converter.h
@@ -0,0 +1,43 @@
+/*
+ * 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_ENUM_CONVERTER_H_
+#define V4L2_CAMERA_HAL_METADATA_ENUM_CONVERTER_H_
+
+#include <map>
+
+#include "../common.h"
+#include "converter_interface.h"
+
+namespace v4l2_camera_hal {
+
+// An EnumConverter converts between enum values.
+class EnumConverter : public ConverterInterface<uint8_t, int32_t> {
+ public:
+  EnumConverter(const std::multimap<int32_t, uint8_t>& v4l2_to_metadata);
+
+  virtual int MetadataToV4L2(uint8_t value, int32_t* conversion) override;
+  virtual int V4L2ToMetadata(int32_t value, uint8_t* conversion) override;
+
+ private:
+  const std::multimap<int32_t, uint8_t> v4l2_to_metadata_;
+
+  DISALLOW_COPY_AND_ASSIGN(EnumConverter);
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_ENUM_CONVERTER_H_
diff --git a/modules/camera/3_4/metadata/enum_converter_test.cpp b/modules/camera/3_4/metadata/enum_converter_test.cpp
new file mode 100644
index 0000000..9ba7ffc
--- /dev/null
+++ b/modules/camera/3_4/metadata/enum_converter_test.cpp
@@ -0,0 +1,100 @@
+/*
+ * 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 "enum_converter.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using testing::Test;
+
+namespace v4l2_camera_hal {
+
+class EnumConverterTest : public Test {
+ protected:
+  virtual void SetUp() {
+    converter_.reset(
+        new EnumConverter({{one_to_one_v4l2_, one_to_one_metadata_},
+                           {one_to_many_v4l2_, many_to_one_metadata_1_},
+                           {one_to_many_v4l2_, many_to_one_metadata_2_},
+                           {many_to_one_v4l2_1_, one_to_many_metadata_},
+                           {many_to_one_v4l2_2_, one_to_many_metadata_},
+                           {unused_v4l2_, unused_metadata_}}));
+  }
+
+  std::unique_ptr<EnumConverter> converter_;
+
+  const int32_t one_to_one_v4l2_ = 12;
+  const int32_t one_to_many_v4l2_ = 34;
+  const int32_t many_to_one_v4l2_1_ = 56;
+  const int32_t many_to_one_v4l2_2_ = 78;
+  const int32_t unused_v4l2_ = 910;
+  const uint8_t one_to_one_metadata_ = 109;
+  const uint8_t one_to_many_metadata_ = 87;
+  const uint8_t many_to_one_metadata_1_ = 65;
+  const uint8_t many_to_one_metadata_2_ = 43;
+  const uint8_t unused_metadata_ = 21;
+};
+
+// Convert single.
+TEST_F(EnumConverterTest, OneToOneConversion) {
+  uint8_t metadata_val = 1;
+  ASSERT_EQ(converter_->V4L2ToMetadata(one_to_one_v4l2_, &metadata_val), 0);
+  EXPECT_EQ(metadata_val, one_to_one_metadata_);
+
+  int32_t v4l2_val = 1;
+  ASSERT_EQ(converter_->MetadataToV4L2(one_to_one_metadata_, &v4l2_val), 0);
+  EXPECT_EQ(v4l2_val, one_to_one_v4l2_);
+}
+
+TEST_F(EnumConverterTest, OneToManyConversion) {
+  // Should be one of the acceptable values.
+  uint8_t metadata_val = 1;
+  ASSERT_EQ(converter_->V4L2ToMetadata(one_to_many_v4l2_, &metadata_val), 0);
+  EXPECT_TRUE(metadata_val == many_to_one_metadata_1_ ||
+              metadata_val == many_to_one_metadata_2_);
+
+  int32_t v4l2_val = 1;
+  ASSERT_EQ(converter_->MetadataToV4L2(one_to_many_metadata_, &v4l2_val), 0);
+  EXPECT_TRUE(v4l2_val == many_to_one_v4l2_1_ ||
+              v4l2_val == many_to_one_v4l2_2_);
+}
+
+TEST_F(EnumConverterTest, ManyToOneConversion) {
+  uint8_t metadata_val = 1;
+  ASSERT_EQ(converter_->V4L2ToMetadata(many_to_one_v4l2_1_, &metadata_val), 0);
+  EXPECT_EQ(metadata_val, one_to_many_metadata_);
+  metadata_val = 1;  // Reset.
+  ASSERT_EQ(converter_->V4L2ToMetadata(many_to_one_v4l2_2_, &metadata_val), 0);
+  EXPECT_EQ(metadata_val, one_to_many_metadata_);
+
+  int32_t v4l2_val = 1;
+  ASSERT_EQ(converter_->MetadataToV4L2(many_to_one_metadata_1_, &v4l2_val), 0);
+  EXPECT_EQ(v4l2_val, one_to_many_v4l2_);
+  v4l2_val = 1;  // Reset.
+  ASSERT_EQ(converter_->MetadataToV4L2(many_to_one_metadata_2_, &v4l2_val), 0);
+  EXPECT_EQ(v4l2_val, one_to_many_v4l2_);
+}
+
+TEST_F(EnumConverterTest, InvalidConversion) {
+  uint8_t metadata_val = 1;
+  EXPECT_EQ(converter_->V4L2ToMetadata(1, &metadata_val), -EINVAL);
+
+  int32_t v4l2_val = 1;
+  EXPECT_EQ(converter_->MetadataToV4L2(1, &v4l2_val), -EINVAL);
+}
+
+}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/metadata/ignored_control_delegate.h b/modules/camera/3_4/metadata/ignored_control_delegate.h
new file mode 100644
index 0000000..f1d5da1
--- /dev/null
+++ b/modules/camera/3_4/metadata/ignored_control_delegate.h
@@ -0,0 +1,43 @@
+/*
+ * 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_DELEGATE_H_
+#define V4L2_CAMERA_HAL_METADATA_IGNORED_CONTROL_DELEGATE_H_
+
+#include "control_delegate_interface.h"
+
+namespace v4l2_camera_hal {
+
+// An IgnoredControlDelegate, as the name implies,
+// has a fixed value and ignores all requests to set it.
+template <typename T>
+class IgnoredControlDelegate : public ControlDelegateInterface<T> {
+ public:
+  IgnoredControlDelegate(T value) : value_(value){};
+
+  int GetValue(T* value) override {
+    *value = value_;
+    return 0;
+  };
+  int SetValue(const T& value) override { return 0; };
+
+ private:
+  const T value_;
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_IGNORED_CONTROL_DELEGATE_H_
diff --git a/modules/camera/3_4/metadata/ignored_control_delegate_test.cpp b/modules/camera/3_4/metadata/ignored_control_delegate_test.cpp
new file mode 100644
index 0000000..80c30df
--- /dev/null
+++ b/modules/camera/3_4/metadata/ignored_control_delegate_test.cpp
@@ -0,0 +1,44 @@
+/*
+ * 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_delegate.h"
+
+#include <gtest/gtest.h>
+
+using testing::Test;
+
+namespace v4l2_camera_hal {
+
+TEST(IgnoredControlDelegateTest, DefaultGet) {
+  int32_t value = 12;
+  IgnoredControlDelegate<int32_t> control(value);
+  int32_t actual = 0;
+  ASSERT_EQ(control.GetValue(&actual), 0);
+  EXPECT_EQ(actual, value);
+}
+
+TEST(IgnoredControlDelegateTest, GetAndSet) {
+  int32_t value = 12;
+  IgnoredControlDelegate<int32_t> control(value);
+  int32_t new_value = 13;
+  ASSERT_EQ(control.SetValue(new_value), 0);
+  int32_t actual = 0;
+  ASSERT_EQ(control.GetValue(&actual), 0);
+  // Should still be the default.
+  EXPECT_EQ(actual, value);
+}
+
+}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/metadata/map_converter.h b/modules/camera/3_4/metadata/map_converter.h
new file mode 100644
index 0000000..b1734b5
--- /dev/null
+++ b/modules/camera/3_4/metadata/map_converter.h
@@ -0,0 +1,139 @@
+/*
+ * 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_MAP_CONVERTER_H_
+#define V4L2_CAMERA_HAL_METADATA_MAP_CONVERTER_H_
+
+#include <errno.h>
+
+#include <map>
+#include <memory>
+
+#include "../common.h"
+#include "converter_interface.h"
+
+namespace v4l2_camera_hal {
+
+// A MapConverter fits values converted by a wrapped converter
+// to a map entry corresponding to the key with the nearest value.
+template <typename TMetadata, typename TV4L2, typename TMapKey>
+class MapConverter : public ConverterInterface<TMetadata, TV4L2> {
+ public:
+  MapConverter(
+      std::shared_ptr<ConverterInterface<TMetadata, TMapKey>> wrapped_converter,
+      std::map<TMapKey, TV4L2> conversion_map);
+
+  virtual int MetadataToV4L2(TMetadata value, TV4L2* conversion) override;
+  virtual int V4L2ToMetadata(TV4L2 value, TMetadata* conversion) override;
+
+ private:
+  std::shared_ptr<ConverterInterface<TMetadata, TMapKey>> wrapped_converter_;
+  std::map<TMapKey, TV4L2> conversion_map_;
+
+  DISALLOW_COPY_AND_ASSIGN(MapConverter);
+};
+
+// -----------------------------------------------------------------------------
+
+template <typename TMetadata, typename TV4L2, typename TMapKey>
+MapConverter<TMetadata, TV4L2, TMapKey>::MapConverter(
+    std::shared_ptr<ConverterInterface<TMetadata, TMapKey>> wrapped_converter,
+    std::map<TMapKey, TV4L2> conversion_map)
+    : wrapped_converter_(std::move(wrapped_converter)),
+      conversion_map_(conversion_map) {
+  HAL_LOG_ENTER();
+}
+
+template <typename TMetadata, typename TV4L2, typename TMapKey>
+int MapConverter<TMetadata, TV4L2, TMapKey>::MetadataToV4L2(TMetadata value,
+                                                            TV4L2* conversion) {
+  HAL_LOG_ENTER();
+
+  if (conversion_map_.empty()) {
+    HAL_LOGE("Empty conversion map.");
+    return -EINVAL;
+  }
+
+  TMapKey raw_conversion = 0;
+  int res = wrapped_converter_->MetadataToV4L2(value, &raw_conversion);
+  if (res) {
+    HAL_LOGE("Failed to perform underlying conversion.");
+    return res;
+  }
+
+  // Find nearest key.
+  auto kv = conversion_map_.lower_bound(raw_conversion);
+  // lower_bound finds the first >= element.
+  if (kv == conversion_map_.begin()) {
+    // Searching for less than the smallest key, so that will be the nearest.
+    *conversion = kv->second;
+  } else if (kv == conversion_map_.end()) {
+    // Searching for greater than the largest key, so that will be the nearest.
+    --kv;
+    *conversion = kv->second;
+  } else {
+    // Since kv points to the first >= element, either that or the previous
+    // element will be nearest.
+    *conversion = kv->second;
+    TMapKey diff = kv->first - raw_conversion;
+
+    // Now compare to the previous. This element will be < raw conversion,
+    // so reverse the order of the subtraction.
+    --kv;
+    if (raw_conversion - kv->first < diff) {
+      *conversion = kv->second;
+    }
+  }
+
+  return 0;
+}
+
+template <typename TMetadata, typename TV4L2, typename TMapKey>
+int MapConverter<TMetadata, TV4L2, TMapKey>::V4L2ToMetadata(
+    TV4L2 value, TMetadata* conversion) {
+  HAL_LOG_ENTER();
+
+  // Unfortunately no bi-directional map lookup in C++.
+  // Breaking on second, not first found so that a warning
+  // can be given if there are multiple values.
+  size_t count = 0;
+  int res;
+  for (auto kv : conversion_map_) {
+    if (kv.second == value) {
+      ++count;
+      if (count == 1) {
+        // First match.
+        res = wrapped_converter_->V4L2ToMetadata(kv.first, conversion);
+      } else {
+        // second match.
+        break;
+      }
+    }
+  }
+
+  if (count == 0) {
+    HAL_LOGE("Couldn't find map conversion of V4L2 value %d.", value);
+    return -EINVAL;
+  } else if (count > 1) {
+    HAL_LOGW("Multiple map conversions found for V4L2 value %d, using first.",
+             value);
+  }
+  return res;
+}
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_MAP_CONVERTER_H_
diff --git a/modules/camera/3_4/metadata/map_converter_test.cpp b/modules/camera/3_4/metadata/map_converter_test.cpp
new file mode 100644
index 0000000..0361810
--- /dev/null
+++ b/modules/camera/3_4/metadata/map_converter_test.cpp
@@ -0,0 +1,110 @@
+/*
+ * 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 "map_converter.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "converter_interface_mock.h"
+
+using testing::Return;
+using testing::SetArgPointee;
+using testing::Test;
+using testing::_;
+
+namespace v4l2_camera_hal {
+
+class MapConverterTest : public Test {
+ protected:
+  virtual void SetUp() {
+    converter_.reset(new ConverterInterfaceMock<int, int32_t>());
+    dut_.reset(new MapConverter<int, int32_t, int32_t>(converter_, map_));
+  }
+
+  virtual void ExpectConvertToV4L2(int32_t converted, int32_t expected) {
+    int initial = 99;
+    EXPECT_CALL(*converter_, MetadataToV4L2(initial, _))
+        .WillOnce(DoAll(SetArgPointee<1>(converted), Return(0)));
+
+    int32_t actual = expected + 1;  // Initialize to non-expected value.
+    ASSERT_EQ(dut_->MetadataToV4L2(initial, &actual), 0);
+    EXPECT_EQ(actual, expected);
+  }
+
+  std::shared_ptr<ConverterInterfaceMock<int, int32_t>> converter_;
+  std::unique_ptr<MapConverter<int, int32_t, int32_t>> dut_;
+
+  const std::map<int32_t, int32_t> map_{{10, 1}, {40, 4}, {20, 2}, {30, 3}};
+};
+
+TEST_F(MapConverterTest, NormalConversionToV4L2) {
+  // A value that matches the map perfectly.
+  auto kv = map_.begin();
+  ExpectConvertToV4L2(kv->first, kv->second);
+}
+
+TEST_F(MapConverterTest, RoundingDownConversionToV4L2) {
+  // A value that's in range but not an exact key value.
+  auto kv = map_.begin();
+  ExpectConvertToV4L2(kv->first + 1, kv->second);
+}
+
+TEST_F(MapConverterTest, RoundingUpConversionToV4L2) {
+  // A value that's in range but not an exact key value.
+  auto kv = map_.begin();
+  ++kv;
+  ExpectConvertToV4L2(kv->first - 1, kv->second);
+}
+
+TEST_F(MapConverterTest, ClampUpConversionToV4L2) {
+  // A value that's below range.
+  auto kv = map_.begin();
+  ExpectConvertToV4L2(kv->first - 1, kv->second);
+}
+
+TEST_F(MapConverterTest, ClampDownConversionToV4L2) {
+  // A value that's above range (even after fitting to step).
+  auto kv = map_.rbegin();
+  ExpectConvertToV4L2(kv->first + 1, kv->second);
+}
+
+TEST_F(MapConverterTest, ConversionErrorToV4L2) {
+  int initial = 99;
+  int err = -99;
+  EXPECT_CALL(*converter_, MetadataToV4L2(initial, _)).WillOnce(Return(err));
+
+  int32_t unused;
+  EXPECT_EQ(dut_->MetadataToV4L2(initial, &unused), err);
+}
+
+TEST_F(MapConverterTest, NormalConversionToMetadata) {
+  auto kv = map_.begin();
+  int expected = 99;
+  EXPECT_CALL(*converter_, V4L2ToMetadata(kv->first, _))
+      .WillOnce(DoAll(SetArgPointee<1>(expected), Return(0)));
+
+  int actual = expected + 1;  // Initialize to non-expected value.
+  ASSERT_EQ(dut_->V4L2ToMetadata(kv->second, &actual), 0);
+  EXPECT_EQ(actual, expected);
+}
+
+TEST_F(MapConverterTest, NotFoundConversionToMetadata) {
+  int unused;
+  ASSERT_EQ(dut_->V4L2ToMetadata(100, &unused), -EINVAL);
+}
+
+}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/metadata/menu_control_options.h b/modules/camera/3_4/metadata/menu_control_options.h
new file mode 100644
index 0000000..c972dc6
--- /dev/null
+++ b/modules/camera/3_4/metadata/menu_control_options.h
@@ -0,0 +1,71 @@
+/*
+ * 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_MENU_CONTROL_OPTIONS_H_
+#define V4L2_CAMERA_HAL_METADATA_MENU_CONTROL_OPTIONS_H_
+
+#include <errno.h>
+
+#include "../common.h"
+#include "control_options_interface.h"
+#include "default_option_delegate.h"
+
+namespace v4l2_camera_hal {
+
+// MenuControlOptions offer a fixed list of acceptable values.
+template <typename T>
+class MenuControlOptions : public ControlOptionsInterface<T> {
+ public:
+  // |options| must be non-empty.
+  MenuControlOptions(std::vector<T> options,
+                     std::shared_ptr<DefaultOptionDelegate<T>> defaults)
+      : options_(options), defaults_(defaults){};
+  MenuControlOptions(std::vector<T> options, std::map<int, T> defaults)
+      : options_(options),
+        defaults_(std::make_shared<DefaultOptionDelegate<T>>(defaults)){};
+
+  virtual std::vector<T> MetadataRepresentation() override { return options_; };
+  virtual bool IsSupported(const T& option) override {
+    return (std::find(options_.begin(), options_.end(), option) !=
+            options_.end());
+  };
+  virtual int DefaultValueForTemplate(int template_type,
+                                      T* default_value) override {
+    // Default to the first option.
+    if (options_.empty()) {
+      HAL_LOGE("Can't get default value, options are empty.");
+      return -ENODEV;
+    }
+
+    // Try to get it from the defaults delegate.
+    if (defaults_->DefaultValueForTemplate(template_type, default_value) &&
+        IsSupported(*default_value)) {
+      return 0;
+    }
+
+    // Fall back to the first available.
+    *default_value = options_[0];
+    return 0;
+  };
+
+ private:
+  std::vector<T> options_;
+  std::shared_ptr<DefaultOptionDelegate<T>> defaults_;
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_MENU_CONTROL_OPTIONS_H_
diff --git a/modules/camera/3_4/metadata/menu_control_options_test.cpp b/modules/camera/3_4/metadata/menu_control_options_test.cpp
new file mode 100644
index 0000000..1a6ce6e
--- /dev/null
+++ b/modules/camera/3_4/metadata/menu_control_options_test.cpp
@@ -0,0 +1,109 @@
+/*
+ * 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 "menu_control_options.h"
+
+#include <memory>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <hardware/camera3.h>
+
+#include "default_option_delegate_mock.h"
+
+using testing::Return;
+using testing::SetArgPointee;
+using testing::Test;
+using testing::_;
+
+namespace v4l2_camera_hal {
+
+class MenuControlOptionsTest : public Test {
+ protected:
+  virtual void SetUp() {
+    mock_defaults_.reset(new DefaultOptionDelegateMock<int>());
+    dut_.reset(new MenuControlOptions<int>(options_, mock_defaults_));
+  }
+
+  std::unique_ptr<MenuControlOptions<int>> dut_;
+  const std::vector<int> options_{1, 10, 19, 30};
+  std::shared_ptr<DefaultOptionDelegateMock<int>> mock_defaults_;
+};
+
+TEST_F(MenuControlOptionsTest, MetadataRepresentation) {
+  // Technically order doesn't matter, but this is faster to write,
+  // and still passes.
+  EXPECT_EQ(dut_->MetadataRepresentation(), options_);
+}
+
+TEST_F(MenuControlOptionsTest, IsSupported) {
+  for (auto option : options_) {
+    EXPECT_TRUE(dut_->IsSupported(option));
+  }
+  // And at least one unsupported.
+  EXPECT_FALSE(dut_->IsSupported(99));
+}
+
+TEST_F(MenuControlOptionsTest, DelegateDefaultValue) {
+  int template_index = 3;
+  int expected = options_[2];
+  ASSERT_TRUE(dut_->IsSupported(expected));
+  EXPECT_CALL(*mock_defaults_, DefaultValueForTemplate(template_index, _))
+      .WillOnce(DoAll(SetArgPointee<1>(expected), Return(true)));
+  int actual = expected - 1;
+  EXPECT_EQ(dut_->DefaultValueForTemplate(template_index, &actual), 0);
+  EXPECT_EQ(actual, expected);
+}
+
+TEST_F(MenuControlOptionsTest, InvalidDelegateDefaultValue) {
+  // -1 is not a supported option.
+  int template_index = 3;
+  int default_val = -1;
+  ASSERT_FALSE(dut_->IsSupported(default_val));
+
+  EXPECT_CALL(*mock_defaults_, DefaultValueForTemplate(template_index, _))
+      .WillOnce(DoAll(SetArgPointee<1>(default_val), Return(true)));
+
+  int actual = default_val;
+  EXPECT_EQ(dut_->DefaultValueForTemplate(template_index, &actual), 0);
+  // Should just give any supported option instead.
+  EXPECT_TRUE(dut_->IsSupported(actual));
+}
+
+TEST_F(MenuControlOptionsTest, NoDelegateDefaultValue) {
+  int template_index = 3;
+  int actual = -1;
+  ASSERT_FALSE(dut_->IsSupported(actual));
+
+  // Have delegate error.
+  EXPECT_CALL(*mock_defaults_, DefaultValueForTemplate(template_index, _))
+      .WillOnce(Return(false));
+
+  // Should still give *some* supported value.
+  EXPECT_EQ(dut_->DefaultValueForTemplate(template_index, &actual), 0);
+  EXPECT_TRUE(dut_->IsSupported(actual));
+}
+
+TEST_F(MenuControlOptionsTest, NoDefaultValue) {
+  // Invalid options don't have a valid default.
+  MenuControlOptions<int> bad_options({}, mock_defaults_);
+  for (int i = 1; i < CAMERA3_TEMPLATE_COUNT; ++i) {
+    int value = -1;
+    EXPECT_EQ(bad_options.DefaultValueForTemplate(i, &value), -ENODEV);
+  }
+}
+
+}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/metadata/metadata.cpp b/modules/camera/3_4/metadata/metadata.cpp
new file mode 100644
index 0000000..efc9959
--- /dev/null
+++ b/modules/camera/3_4/metadata/metadata.cpp
@@ -0,0 +1,210 @@
+/*
+ * Copyright 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 "metadata.h"
+
+#include <camera/CameraMetadata.h>
+#include <hardware/camera3.h>
+
+#include "../common.h"
+#include "metadata_common.h"
+
+namespace v4l2_camera_hal {
+
+Metadata::Metadata(PartialMetadataSet components)
+    : components_(std::move(components)) {
+  HAL_LOG_ENTER();
+}
+
+Metadata::~Metadata() {
+  HAL_LOG_ENTER();
+}
+
+int Metadata::FillStaticMetadata(android::CameraMetadata* metadata) {
+  HAL_LOG_ENTER();
+  if (!metadata) {
+    HAL_LOGE("Can't fill null metadata.");
+    return -EINVAL;
+  }
+
+  std::vector<int32_t> static_tags;
+  std::vector<int32_t> control_tags;
+  std::vector<int32_t> dynamic_tags;
+  int res = 0;
+
+  for (auto& component : components_) {
+    // Prevent components from potentially overriding others.
+    android::CameraMetadata additional_metadata;
+    // Populate the fields.
+    res = component->PopulateStaticFields(&additional_metadata);
+    if (res) {
+      HAL_LOGE("Failed to get all static properties.");
+      return res;
+    }
+    // Add it to the overall result.
+    if (!additional_metadata.isEmpty()) {
+      res = metadata->append(additional_metadata);
+      if (res != android::OK) {
+        HAL_LOGE("Failed to append all static properties.");
+        return res;
+      }
+    }
+
+    // Note what tags the component adds.
+    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 = 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 = 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 = UpdateMetadata(
+      metadata, ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS, static_tags);
+  if (res != android::OK) {
+    HAL_LOGE("Failed to add characteristics keys meta key.");
+    return -ENODEV;
+  }
+
+  // TODO(b/31018853): cache result.
+  return 0;
+}
+
+bool Metadata::IsValidRequest(const android::CameraMetadata& metadata) {
+  HAL_LOG_ENTER();
+
+  // Empty means "use previous settings", which are inherently valid.
+  if (metadata.isEmpty())
+    return true;
+
+  for (auto& component : components_) {
+    // Check that all components support the values requested of them.
+    bool valid_request = component->SupportsRequestValues(metadata);
+    if (!valid_request) {
+      // Exit early if possible.
+      return false;
+    }
+  }
+
+  return true;
+}
+
+int Metadata::GetRequestTemplate(int template_type,
+                                 android::CameraMetadata* template_metadata) {
+  HAL_LOG_ENTER();
+  if (!template_metadata) {
+    HAL_LOGE("Can't fill null template.");
+    return -EINVAL;
+  }
+
+  // Templates are numbered 1 through COUNT-1 for some reason.
+  if (template_type < 1 || template_type >= CAMERA3_TEMPLATE_COUNT) {
+    HAL_LOGE("Unrecognized template type %d.", template_type);
+    return -EINVAL;
+  }
+
+  for (auto& component : components_) {
+    // Prevent components from potentially overriding others.
+    android::CameraMetadata additional_metadata;
+    int res =
+        component->PopulateTemplateRequest(template_type, &additional_metadata);
+    if (res) {
+      HAL_LOGE("Failed to get all default request fields.");
+      return res;
+    }
+    // Add it to the overall result.
+    if (!additional_metadata.isEmpty()) {
+      res = template_metadata->append(additional_metadata);
+      if (res != android::OK) {
+        HAL_LOGE("Failed to append all default request fields.");
+        return res;
+      }
+    }
+  }
+
+  // TODO(b/31018853): cache result.
+  return 0;
+}
+
+int Metadata::SetRequestSettings(const android::CameraMetadata& metadata) {
+  HAL_LOG_ENTER();
+
+  // Empty means "use previous settings".
+  if (metadata.isEmpty())
+    return 0;
+
+  for (auto& component : components_) {
+    int res = component->SetRequestValues(metadata);
+    if (res) {
+      HAL_LOGE("Failed to set all requested settings.");
+      return res;
+    }
+  }
+
+  return 0;
+}
+
+int Metadata::FillResultMetadata(android::CameraMetadata* metadata) {
+  HAL_LOG_ENTER();
+  if (!metadata) {
+    HAL_LOGE("Can't fill null metadata.");
+    return -EINVAL;
+  }
+
+  for (auto& component : components_) {
+    // Prevent components from potentially overriding others.
+    android::CameraMetadata additional_metadata;
+    int res = component->PopulateDynamicFields(&additional_metadata);
+    if (res) {
+      HAL_LOGE("Failed to get all dynamic result fields.");
+      return res;
+    }
+    // Add it to the overall result.
+    if (!additional_metadata.isEmpty()) {
+      res = metadata->append(additional_metadata);
+      if (res != android::OK) {
+        HAL_LOGE("Failed to append all dynamic result fields.");
+        return res;
+      }
+    }
+  }
+
+  return 0;
+}
+
+}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/metadata/metadata.h b/modules/camera/3_4/metadata/metadata.h
new file mode 100644
index 0000000..e2232b5
--- /dev/null
+++ b/modules/camera/3_4/metadata/metadata.h
@@ -0,0 +1,51 @@
+/*
+ * 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_H_
+#define V4L2_CAMERA_HAL_METADATA_H_
+
+#include <set>
+
+#include <camera/CameraMetadata.h>
+#include <hardware/camera3.h>
+
+#include "../common.h"
+#include "metadata_common.h"
+
+namespace v4l2_camera_hal {
+class Metadata {
+ public:
+  Metadata(PartialMetadataSet components);
+  virtual ~Metadata();
+
+  int FillStaticMetadata(android::CameraMetadata* metadata);
+  bool IsValidRequest(const android::CameraMetadata& metadata);
+  int GetRequestTemplate(int template_type,
+                         android::CameraMetadata* template_metadata);
+  int SetRequestSettings(const android::CameraMetadata& metadata);
+  int FillResultMetadata(android::CameraMetadata* metadata);
+
+ private:
+  // The overall metadata is broken down into several distinct pieces.
+  // Note: it is undefined behavior if multiple components share tags.
+  PartialMetadataSet components_;
+
+  DISALLOW_COPY_AND_ASSIGN(Metadata);
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_V4L2_METADATA_H_
diff --git a/modules/camera/3_4/metadata/metadata_common.h b/modules/camera/3_4/metadata/metadata_common.h
new file mode 100644
index 0000000..34b7777
--- /dev/null
+++ b/modules/camera/3_4/metadata/metadata_common.h
@@ -0,0 +1,313 @@
+/*
+ * 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_METADATA_COMMON_H_
+#define V4L2_CAMERA_HAL_METADATA_METADATA_COMMON_H_
+
+#include <array>
+#include <memory>
+#include <set>
+#include <vector>
+
+#include <camera/CameraMetadata.h>
+
+#include "array_vector.h"
+#include "partial_metadata_interface.h"
+
+namespace v4l2_camera_hal {
+
+typedef std::set<std::unique_ptr<PartialMetadataInterface>> PartialMetadataSet;
+
+// Templated helper functions effectively extending android::CameraMetadata.
+// Will cause a compile-time errors if CameraMetadata doesn't support
+// using the templated type. Templates are provided to extend this support
+// to std::arrays, std::vectors, and ArrayVectors of supported types as
+// appropriate.
+
+// UpdateMetadata(metadata, tag, data):
+//
+// Updates the entry for |tag| in |metadata| (functionally similar to
+// android::CameraMetadata::update).
+//
+// Args:
+//   metadata: the android::CameraMetadata to update.
+//   tag: the tag within |metadata| to update.
+//   data: A reference to the data to update |tag| with.
+//
+// Returns:
+//   0: Success.
+//   -ENODEV: The type of |data| does not match the expected type for |tag|,
+//     or another error occured. Note: no errors are given for updating a
+//     metadata entry with an incorrect amount of data (e.g. filling a tag
+//     that expects to have only one value with multiple values), as this
+//     information is not encoded in the type associated with the tag by
+//     get_camera_metadata_tag_type (from <system/camera_metadata.h>).
+
+// Generic (pointer & size).
+template <typename T>
+static int UpdateMetadata(android::CameraMetadata* metadata,
+                          int32_t tag,
+                          const T* data,
+                          size_t count) {
+  int res = metadata->update(tag, data, count);
+  if (res) {
+    HAL_LOGE("Failed to update metadata tag %d", tag);
+    return -ENODEV;
+  }
+  return 0;
+}
+
+// Generic (single item reference).
+template <typename T>
+static int UpdateMetadata(android::CameraMetadata* metadata,
+                          int32_t tag,
+                          const T& val) {
+  return UpdateMetadata(metadata, tag, &val, 1);
+}
+
+// Specialization for vectors.
+template <typename T>
+static int UpdateMetadata(android::CameraMetadata* metadata,
+                          int32_t tag,
+                          const std::vector<T>& val) {
+  return UpdateMetadata(metadata, tag, val.data(), val.size());
+}
+
+// Specialization for arrays.
+template <typename T, size_t N>
+static int UpdateMetadata(android::CameraMetadata* metadata,
+                          int32_t tag,
+                          const std::array<T, N>& val) {
+  return UpdateMetadata(metadata, tag, val.data(), N);
+}
+
+// Specialization for ArrayVectors.
+template <typename T, size_t N>
+static int UpdateMetadata(android::CameraMetadata* metadata,
+                          int32_t tag,
+                          const ArrayVector<T, N>& val) {
+  return UpdateMetadata(metadata, tag, val.data(), val.total_num_elements());
+}
+
+// Specialization for vectors of arrays.
+template <typename T, size_t N>
+static int UpdateMetadata(android::CameraMetadata* metadata,
+                          int32_t tag,
+                          const std::vector<std::array<T, N>>& val) {
+  // Convert to array vector so we know all the elements are contiguous.
+  ArrayVector<T, N> array_vector;
+  for (const auto& array : val) {
+    array_vector.push_back(array);
+  }
+  return UpdateMetadata(metadata, tag, array_vector);
+}
+
+// GetDataPointer(entry, val)
+//
+// A helper for other methods in this file.
+// Gets the data pointer of a given metadata entry into |*val|.
+
+static void GetDataPointer(camera_metadata_ro_entry_t& entry,
+                           const uint8_t** val) {
+  *val = entry.data.u8;
+}
+
+static void GetDataPointer(camera_metadata_ro_entry_t& entry,
+                           const int32_t** val) {
+  *val = entry.data.i32;
+}
+
+static void GetDataPointer(camera_metadata_ro_entry_t& entry,
+                           const float** val) {
+  *val = entry.data.f;
+}
+
+static void GetDataPointer(camera_metadata_ro_entry_t& entry,
+                           const int64_t** val) {
+  *val = entry.data.i64;
+}
+
+static void GetDataPointer(camera_metadata_ro_entry_t& entry,
+                           const double** val) {
+  *val = entry.data.d;
+}
+
+static void GetDataPointer(camera_metadata_ro_entry_t& entry,
+                           const camera_metadata_rational_t** val) {
+  *val = entry.data.r;
+}
+
+// SingleTagValue(metadata, tag, val)
+//
+// Get the value of the |tag| entry in |metadata|.
+// |tag| is expected to refer to an entry with a single item
+// of the templated type (a "single item" is exactly N values
+// if the templated type is an array of size N). An error will be
+// returned if it the wrong number of items are present.
+//
+// Returns:
+//   -ENOENT: The tag couldn't be found or was empty.
+//   -EINVAL: The tag contained more than one item, or |val| is null.
+//   -ENODEV: The tag claims to be non-empty, but the data pointer is null.
+//   0: Success. |*val| will contain the value for |tag|.
+
+// Singleton.
+template <typename T>
+static int SingleTagValue(const android::CameraMetadata& metadata,
+                          int32_t tag,
+                          T* val) {
+  if (!val) {
+    HAL_LOGE("Null pointer passed to SingleTagValue.");
+    return -EINVAL;
+  }
+  camera_metadata_ro_entry_t entry = metadata.find(tag);
+  if (entry.count == 0) {
+    HAL_LOGE("Metadata tag %d is empty.", tag);
+    return -ENOENT;
+  } else if (entry.count != 1) {
+    HAL_LOGE(
+        "Error: expected metadata tag %d to contain exactly 1 value "
+        "(had %d).",
+        tag,
+        entry.count);
+    return -EINVAL;
+  }
+  const T* data = nullptr;
+  GetDataPointer(entry, &data);
+  if (data == nullptr) {
+    HAL_LOGE("Metadata tag %d is empty.", tag);
+    return -ENODEV;
+  }
+  *val = *data;
+  return 0;
+}
+
+// Specialization for std::array.
+template <typename T, size_t N>
+static int SingleTagValue(const android::CameraMetadata& metadata,
+                          int32_t tag,
+                          std::array<T, N>* val) {
+  if (!val) {
+    HAL_LOGE("Null pointer passed to SingleTagValue.");
+    return -EINVAL;
+  }
+  camera_metadata_ro_entry_t entry = metadata.find(tag);
+  if (entry.count == 0) {
+    HAL_LOGE("Metadata tag %d is empty.", tag);
+    return -ENOENT;
+  } else if (entry.count != N) {
+    HAL_LOGE(
+        "Error: expected metadata tag %d to contain a single array of "
+        "exactly %d values (had %d).",
+        tag,
+        N,
+        entry.count);
+    return -EINVAL;
+  }
+  const T* data = nullptr;
+  GetDataPointer(entry, &data);
+  if (data == nullptr) {
+    HAL_LOGE("Metadata tag %d is empty.", tag);
+    return -ENODEV;
+  }
+  // Fill in the array.
+  for (size_t i = 0; i < N; ++i) {
+    (*val)[i] = data[i];
+  }
+  return 0;
+}
+
+// VectorTagValue(metadata, tag, val)
+//
+// Get the value of the |tag| entry in |metadata|.
+// |tag| is expected to refer to an entry with a vector
+// of the templated type. For arrays, an error will be
+// returned if it the wrong number of items are present.
+//
+// Returns:
+//   -ENOENT: The tag couldn't be found or was empty. While technically an
+//            empty vector may be valid, this error is returned for consistency
+//            with SingleTagValue.
+//   -EINVAL: The tag contained an invalid number of entries (e.g. 6 entries for
+//            a vector of length 4 arrays), or |val| is null.
+//   -ENODEV: The tag claims to be non-empty, but the data pointer is null.
+//   0: Success. |*val| will contain the values for |tag|.
+template <typename T>
+static int VectorTagValue(const android::CameraMetadata& metadata,
+                          int32_t tag,
+                          std::vector<T>* val) {
+  if (!val) {
+    HAL_LOGE("Null pointer passed to VectorTagValue.");
+    return -EINVAL;
+  }
+  camera_metadata_ro_entry_t entry = metadata.find(tag);
+  if (entry.count == 0) {
+    return -ENOENT;
+  }
+  const T* data = nullptr;
+  GetDataPointer(entry, &data);
+  if (data == nullptr) {
+    HAL_LOGE("Metadata tag %d claims to have elements but is empty.", tag);
+    return -ENODEV;
+  }
+  // Copy the data for |tag| into the output vector.
+  *val = std::vector<T>(data, data + entry.count);
+  return 0;
+}
+
+// Specialization for std::array.
+template <typename T, size_t N>
+static int VectorTagValue(const android::CameraMetadata& metadata,
+                          int32_t tag,
+                          std::vector<std::array<T, N>>* val) {
+  if (!val) {
+    HAL_LOGE("Null pointer passed to VectorTagValue.");
+    return -EINVAL;
+  }
+  camera_metadata_ro_entry_t entry = metadata.find(tag);
+  if (entry.count == 0) {
+    return -ENOENT;
+  }
+  if (entry.count % N != 0) {
+    HAL_LOGE(
+        "Error: expected metadata tag %d to contain a vector of arrays of "
+        "length %d (had %d entries, which is not divisible by %d).",
+        tag,
+        N,
+        entry.count,
+        N);
+    return -EINVAL;
+  }
+  const T* data = nullptr;
+  GetDataPointer(entry, &data);
+  if (data == nullptr) {
+    HAL_LOGE("Metadata tag %d claims to have elements but is empty.", tag);
+    return -ENODEV;
+  }
+  // Copy the data for |tag| into separate arrays for the output vector.
+  size_t num_arrays = entry.count / N;
+  *val = std::vector<std::array<T, N>>(num_arrays);
+  for (size_t i = 0; i < num_arrays; ++i) {
+    for (size_t j = 0; j < N; ++j) {
+      val->at(i)[j] = data[i * N + j];
+    }
+  }
+  return 0;
+}
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_METADATA_COMMON_H_
diff --git a/modules/camera/3_4/metadata/metadata_reader.cpp b/modules/camera/3_4/metadata/metadata_reader.cpp
new file mode 100644
index 0000000..fe2ff85
--- /dev/null
+++ b/modules/camera/3_4/metadata/metadata_reader.cpp
@@ -0,0 +1,264 @@
+/*
+ * 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 "metadata_reader.h"
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "MetadataReader"
+#include <cutils/log.h>
+#include <system/camera.h>
+
+#include "metadata_common.h"
+
+namespace default_camera_hal {
+
+MetadataReader::MetadataReader(
+    std::unique_ptr<const android::CameraMetadata> metadata)
+    : metadata_(std::move(metadata)) {}
+
+MetadataReader::~MetadataReader() {}
+
+int MetadataReader::Facing(int* facing) const {
+  uint8_t metadata_facing = 0;
+  int res = v4l2_camera_hal::SingleTagValue(
+      *metadata_, ANDROID_LENS_FACING, &metadata_facing);
+  if (res) {
+    ALOGE("%s: Failed to get facing from static metadata.", __func__);
+    return res;
+  }
+
+  switch (metadata_facing) {
+    case (ANDROID_LENS_FACING_FRONT):
+      *facing = CAMERA_FACING_FRONT;
+      break;
+    case (ANDROID_LENS_FACING_BACK):
+      *facing = CAMERA_FACING_BACK;
+      break;
+    case (ANDROID_LENS_FACING_EXTERNAL):
+      *facing = CAMERA_FACING_EXTERNAL;
+      break;
+    default:
+      ALOGE("%s: Invalid facing from static metadata: %d.",
+            __func__,
+            metadata_facing);
+      return -EINVAL;
+  }
+  return 0;
+}
+
+int MetadataReader::Orientation(int* orientation) const {
+  int32_t metadata_orientation = 0;
+  int res = v4l2_camera_hal::SingleTagValue(
+      *metadata_, ANDROID_SENSOR_ORIENTATION, &metadata_orientation);
+  if (res) {
+    ALOGE("%s: Failed to get orientation from static metadata.", __func__);
+    return res;
+  }
+
+  // Orientation must be 0, 90, 180, or 270.
+  if (metadata_orientation < 0 || metadata_orientation > 270 ||
+      metadata_orientation % 90 != 0) {
+    ALOGE(
+        "%s: Invalid orientation %d "
+        "(must be a 90-degree increment in [0, 360)).",
+        __func__,
+        metadata_orientation);
+    return -EINVAL;
+  }
+
+  *orientation = static_cast<int>(metadata_orientation);
+  return 0;
+}
+
+int MetadataReader::MaxInputStreams(int32_t* max_input) const {
+  int res = v4l2_camera_hal::SingleTagValue(
+      *metadata_, ANDROID_REQUEST_MAX_NUM_INPUT_STREAMS, max_input);
+  if (res == -ENOENT) {
+    // Not required; default to 0.
+    *max_input = 0;
+  } else if (res) {
+    ALOGE("%s: Failed to get max output streams from static metadata.",
+          __func__);
+    return res;
+  }
+  return 0;
+}
+
+int MetadataReader::MaxOutputStreams(int32_t* max_raw,
+                                     int32_t* max_non_stalling,
+                                     int32_t* max_stalling) const {
+  std::array<int32_t, 3> max_output_streams;
+  int res = v4l2_camera_hal::SingleTagValue(
+      *metadata_, ANDROID_REQUEST_MAX_NUM_OUTPUT_STREAMS, &max_output_streams);
+  if (res) {
+    ALOGE("%s: Failed to get max output streams from static metadata.",
+          __func__);
+    return res;
+  }
+  *max_raw = max_output_streams[0];
+  *max_non_stalling = max_output_streams[1];
+  *max_stalling = max_output_streams[2];
+  return 0;
+}
+
+int MetadataReader::RequestCapabilities(std::set<uint8_t>* capabilities) const {
+  std::vector<uint8_t> raw_capabilities;
+  int res = v4l2_camera_hal::VectorTagValue(
+      *metadata_, ANDROID_REQUEST_AVAILABLE_CAPABILITIES, &raw_capabilities);
+  if (res) {
+    ALOGE("%s: Failed to get request capabilities from static metadata.",
+          __func__);
+    return res;
+  }
+
+  // Move from vector to set.
+  capabilities->insert(raw_capabilities.begin(), raw_capabilities.end());
+  return 0;
+}
+
+int MetadataReader::StreamConfigurations(
+    std::vector<StreamConfiguration>* configs) const {
+  std::vector<RawStreamConfiguration> raw_stream_configs;
+  int res = v4l2_camera_hal::VectorTagValue(
+      *metadata_,
+      ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS,
+      &raw_stream_configs);
+  if (res) {
+    ALOGE("%s: Failed to get stream configs from static metadata.", __func__);
+    return res;
+  }
+
+  // TODO(b/31384253): check for required configs.
+
+  // Convert from raw.
+  configs->insert(
+      configs->end(), raw_stream_configs.begin(), raw_stream_configs.end());
+
+  // Check that all configs are valid.
+  for (const auto& config : *configs) {
+    // Must have positive dimensions.
+    if (config.spec.width < 1 || config.spec.height < 1) {
+      ALOGE("%s: Invalid stream config: non-positive dimensions (%d, %d).",
+            __func__,
+            config.spec.width,
+            config.spec.height);
+      return -EINVAL;
+    }
+    // Must have a known direction enum.
+    switch (config.direction) {
+      case ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT:
+      case ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT:
+        break;
+      default:
+        ALOGE("%s: Invalid stream config direction: %d.",
+              __func__,
+              config.direction);
+        return -EINVAL;
+    }
+  }
+  return 0;
+}
+
+int MetadataReader::StreamStallDurations(
+    std::vector<StreamStallDuration>* stalls) const {
+  std::vector<RawStreamStallDuration> raw_stream_stall_durations;
+  int res =
+      v4l2_camera_hal::VectorTagValue(*metadata_,
+                                      ANDROID_SCALER_AVAILABLE_STALL_DURATIONS,
+                                      &raw_stream_stall_durations);
+  if (res) {
+    ALOGE("%s: Failed to get stall durations from static metadata.", __func__);
+    return res;
+  }
+
+  // Convert from raw.
+  stalls->insert(stalls->end(),
+                 raw_stream_stall_durations.begin(),
+                 raw_stream_stall_durations.end());
+  // Check that all stalls are valid.
+  for (const auto& stall : *stalls) {
+    // Must have positive dimensions.
+    if (stall.spec.width < 1 || stall.spec.height < 1) {
+      ALOGE("%s: Invalid stall duration: non-positive dimensions (%d, %d).",
+            __func__,
+            stall.spec.width,
+            stall.spec.height);
+      return -EINVAL;
+    }
+    // Must have a non-negative stall.
+    if (stall.duration < 0) {
+      ALOGE("%s: Invalid stall duration: negative stall %d.",
+            __func__,
+            stall.duration);
+      return -EINVAL;
+    }
+    // TODO(b/31384253): YUV_420_888, RAW10, RAW12, RAW_OPAQUE,
+    // and IMPLEMENTATION_DEFINED must have 0 stall duration.
+  }
+
+  return 0;
+}
+
+int MetadataReader::ReprocessFormats(ReprocessFormatMap* reprocess_map) const {
+  std::vector<int32_t> input_output_formats;
+  int res = v4l2_camera_hal::VectorTagValue(
+      *metadata_,
+      ANDROID_SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP,
+      &input_output_formats);
+  if (res) {
+    ALOGE("%s: Failed to get input output format map from static metadata.",
+          __func__);
+    return res;
+  }
+
+  // Convert from the raw vector.
+  for (size_t i = 0; i < input_output_formats.size();) {
+    // The map is represented as variable-length entries of the format
+    // input, num_outputs, <outputs>.
+
+    // Get the input format.
+    int32_t input_format = input_output_formats[i++];
+
+    // Find the output begin and end for this format.
+    int32_t num_output_formats = input_output_formats[i++];
+    if (num_output_formats < 1) {
+      ALOGE(
+          "%s: No output formats for input format %d.", __func__, input_format);
+      return -EINVAL;
+    }
+    size_t outputs_end = i + num_output_formats;
+    if (outputs_end > input_output_formats.size()) {
+      ALOGE("%s: Input format %d requests more data than available.",
+            __func__,
+            input_format);
+      return -EINVAL;
+    }
+
+    // Copy all the output formats into the map.
+    (*reprocess_map)[input_format].insert(
+        input_output_formats.data() + i,
+        input_output_formats.data() + outputs_end);
+
+    // Move on to the next entry.
+    i = outputs_end;
+  }
+
+  // TODO(b/31384253): check for required mappings.
+
+  return 0;
+}
+
+}  // namespace default_camera_hal
diff --git a/modules/camera/3_4/metadata/metadata_reader.h b/modules/camera/3_4/metadata/metadata_reader.h
new file mode 100644
index 0000000..996bf8b
--- /dev/null
+++ b/modules/camera/3_4/metadata/metadata_reader.h
@@ -0,0 +1,77 @@
+/*
+ * 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 DEFAULT_CAMERA_HAL_METADATA_METADATA_READER_H_
+#define DEFAULT_CAMERA_HAL_METADATA_METADATA_READER_H_
+
+#include <map>
+#include <memory>
+#include <set>
+#include <vector>
+
+#include <camera/CameraMetadata.h>
+
+#include "../common.h"
+#include "types.h"
+
+namespace default_camera_hal {
+
+// A MetadataReader reads and converts/validates various metadata entries.
+class MetadataReader {
+ public:
+  MetadataReader(std::unique_ptr<const android::CameraMetadata> metadata);
+  virtual ~MetadataReader();
+
+  // Get a pointer to the underlying metadata being read.
+  // The pointer is valid only as long as this object is alive.
+  // The "locking" here only causes non-const methods to fail,
+  // which is not a problem since the CameraMetadata being locked
+  // is already const. This could be a problem if the metadata was
+  // shared more widely, but |metadata_| is a unique_ptr,
+  // guaranteeing the safety of this. Destructing automatically "unlocks".
+  virtual const camera_metadata_t* raw_metadata() const {
+    return metadata_->getAndLock();
+  }
+
+  // All accessor methods must be given a valid pointer. They will return:
+  // 0: Success.
+  // -ENOENT: The necessary entry is missing.
+  // -EINVAL: The entry value is invalid.
+  // -ENODEV: Some other error occured.
+
+  // The |facing| returned will be one of the enum values from system/camera.h.
+  virtual int Facing(int* facing) const;
+  virtual int Orientation(int* orientation) const;
+  virtual int MaxInputStreams(int32_t* max_input_streams) const;
+  virtual int MaxOutputStreams(int32_t* max_raw_output_streams,
+                               int32_t* max_non_stalling_output_streams,
+                               int32_t* max_stalling_output_streams) const;
+  virtual int RequestCapabilities(std::set<uint8_t>* capabilites) const;
+  virtual int StreamConfigurations(
+      std::vector<StreamConfiguration>* configs) const;
+  virtual int StreamStallDurations(
+      std::vector<StreamStallDuration>* stalls) const;
+  virtual int ReprocessFormats(ReprocessFormatMap* reprocess_map) const;
+
+ private:
+  std::unique_ptr<const android::CameraMetadata> metadata_;
+
+  DISALLOW_COPY_AND_ASSIGN(MetadataReader);
+};
+
+}  // namespace default_camera_hal
+
+#endif  // DEFAULT_CAMERA_HAL_METADATA_METADATA_READER_H_
diff --git a/modules/camera/3_4/metadata/metadata_reader_mock.h b/modules/camera/3_4/metadata/metadata_reader_mock.h
new file mode 100644
index 0000000..19895a7
--- /dev/null
+++ b/modules/camera/3_4/metadata/metadata_reader_mock.h
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+// Mock for metadata readers.
+
+#ifndef DEFAULT_CAMERA_HAL_METADATA_METADATA_READER_MOCK_H_
+#define DEFAULT_CAMERA_HAL_METADATA_METADATA_READER_MOCK_H_
+
+#include <gmock/gmock.h>
+
+#include "metadata_reader.h"
+
+namespace default_camera_hal {
+
+class MetadataReaderMock : public MetadataReader {
+ public:
+  MetadataReaderMock() : MetadataReader(nullptr){};
+  MOCK_CONST_METHOD0(raw_metadata, const camera_metadata_t*());
+  MOCK_CONST_METHOD1(Facing, int(int*));
+  MOCK_CONST_METHOD1(Orientation, int(int*));
+  MOCK_CONST_METHOD1(MaxInputStreams, int(int32_t*));
+  MOCK_CONST_METHOD3(MaxOutputStreams, int(int32_t*, int32_t*, int32_t*));
+  MOCK_CONST_METHOD1(RequestCapabilities, int(std::set<uint8_t>*));
+  MOCK_CONST_METHOD1(StreamConfigurations,
+                     int(std::vector<StreamConfiguration>*));
+  MOCK_CONST_METHOD1(StreamStallDurations,
+                     int(std::vector<StreamStallDuration>*));
+  MOCK_CONST_METHOD1(ReprocessFormats, int(ReprocessFormatMap*));
+};
+
+}  // namespace default_camera_hal
+
+#endif  // DEFAULT_CAMERA_HAL_METADATA_METADATA_READER_MOCK_H_
diff --git a/modules/camera/3_4/metadata/metadata_reader_test.cpp b/modules/camera/3_4/metadata/metadata_reader_test.cpp
new file mode 100644
index 0000000..5b9cc63
--- /dev/null
+++ b/modules/camera/3_4/metadata/metadata_reader_test.cpp
@@ -0,0 +1,371 @@
+/*
+ * 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 "metadata_reader.h"
+
+#include <camera/CameraMetadata.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <system/camera.h>
+
+#include "array_vector.h"
+#include "metadata_common.h"
+
+using testing::AtMost;
+using testing::Expectation;
+using testing::Return;
+using testing::Test;
+
+namespace default_camera_hal {
+
+class MetadataReaderTest : public Test {
+ protected:
+  void SetUp() {
+    ResetMetadata();
+    // FillDUT should be called before using the device under test.
+    dut_.reset();
+  }
+
+  void ResetMetadata() {
+    metadata_ = std::make_unique<android::CameraMetadata>();
+  }
+
+  void FillDUT() {
+    dut_ = std::make_unique<MetadataReader>(std::move(metadata_));
+    ResetMetadata();
+  }
+
+  std::unique_ptr<MetadataReader> dut_;
+  std::unique_ptr<android::CameraMetadata> metadata_;
+
+  const int32_t facing_tag_ = ANDROID_LENS_FACING;
+  const int32_t orientation_tag_ = ANDROID_SENSOR_ORIENTATION;
+  const int32_t max_inputs_tag_ = ANDROID_REQUEST_MAX_NUM_INPUT_STREAMS;
+  const int32_t max_outputs_tag_ = ANDROID_REQUEST_MAX_NUM_OUTPUT_STREAMS;
+  const int32_t configs_tag_ = ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS;
+  const int32_t stalls_tag_ = ANDROID_SCALER_AVAILABLE_STALL_DURATIONS;
+  const int32_t reprocess_formats_tag_ =
+      ANDROID_SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP;
+
+  const std::vector<int32_t> valid_orientations_ = {0, 90, 180, 270};
+  // TODO(b/31384253): check for required configs/reprocess formats.
+};
+
+TEST_F(MetadataReaderTest, FacingTranslations) {
+  // Check that the enums are converting properly.
+  std::map<uint8_t, int> translations{
+      {ANDROID_LENS_FACING_FRONT, CAMERA_FACING_FRONT},
+      {ANDROID_LENS_FACING_BACK, CAMERA_FACING_BACK},
+      {ANDROID_LENS_FACING_EXTERNAL, CAMERA_FACING_EXTERNAL}};
+  for (const auto& translation : translations) {
+    ASSERT_EQ(v4l2_camera_hal::UpdateMetadata(
+                  metadata_.get(), facing_tag_, translation.first),
+              0);
+    FillDUT();
+
+    int expected = translation.second;
+    int actual = expected + 1;
+    EXPECT_EQ(dut_->Facing(&actual), 0);
+    EXPECT_EQ(actual, expected);
+  }
+}
+
+TEST_F(MetadataReaderTest, InvalidFacing) {
+  uint8_t invalid = 99;
+  ASSERT_EQ(
+      v4l2_camera_hal::UpdateMetadata(metadata_.get(), facing_tag_, invalid),
+      0);
+  FillDUT();
+  int actual = 0;
+  EXPECT_EQ(dut_->Facing(&actual), -EINVAL);
+}
+
+TEST_F(MetadataReaderTest, EmptyFacing) {
+  FillDUT();
+  int actual = 0;
+  EXPECT_EQ(dut_->Facing(&actual), -ENOENT);
+}
+
+TEST_F(MetadataReaderTest, ValidOrientations) {
+  for (int32_t orientation : valid_orientations_) {
+    ASSERT_EQ(v4l2_camera_hal::UpdateMetadata(
+                  metadata_.get(), orientation_tag_, orientation),
+              0);
+    FillDUT();
+
+    int actual = orientation + 1;
+    EXPECT_EQ(dut_->Orientation(&actual), 0);
+    EXPECT_EQ(actual, orientation);
+  }
+}
+
+TEST_F(MetadataReaderTest, InvalidOrientations) {
+  // High.
+  for (int32_t orientation : valid_orientations_) {
+    ASSERT_EQ(v4l2_camera_hal::UpdateMetadata(
+                  metadata_.get(), orientation_tag_, orientation + 1),
+              0);
+    FillDUT();
+    int actual = 0;
+    EXPECT_EQ(dut_->Orientation(&actual), -EINVAL);
+  }
+  // Low.
+  for (int32_t orientation : valid_orientations_) {
+    ASSERT_EQ(v4l2_camera_hal::UpdateMetadata(
+                  metadata_.get(), orientation_tag_, orientation - 1),
+              0);
+    FillDUT();
+    int actual = 0;
+    EXPECT_EQ(dut_->Orientation(&actual), -EINVAL);
+  }
+}
+
+TEST_F(MetadataReaderTest, EmptyOrientation) {
+  FillDUT();
+  int actual = 0;
+  EXPECT_EQ(dut_->Orientation(&actual), -ENOENT);
+}
+
+TEST_F(MetadataReaderTest, MaxInputs) {
+  int32_t expected = 12;
+  ASSERT_EQ(v4l2_camera_hal::UpdateMetadata(
+                metadata_.get(), max_inputs_tag_, expected),
+            0);
+  FillDUT();
+  int32_t actual = expected + 1;
+  ASSERT_EQ(dut_->MaxInputStreams(&actual), 0);
+  EXPECT_EQ(actual, expected);
+}
+
+TEST_F(MetadataReaderTest, EmptyMaxInputs) {
+  FillDUT();
+  // Max inputs is an optional key; if not present the default is 0.
+  int32_t expected = 0;
+  int32_t actual = expected + 1;
+  ASSERT_EQ(dut_->MaxInputStreams(&actual), 0);
+  EXPECT_EQ(actual, expected);
+}
+
+TEST_F(MetadataReaderTest, MaxOutputs) {
+  std::array<int32_t, 3> expected = {{12, 34, 56}};
+  ASSERT_EQ(v4l2_camera_hal::UpdateMetadata(
+                metadata_.get(), max_outputs_tag_, expected),
+            0);
+  FillDUT();
+  std::array<int32_t, 3> actual;
+  ASSERT_EQ(dut_->MaxOutputStreams(&actual[0], &actual[1], &actual[2]), 0);
+  EXPECT_EQ(actual, expected);
+}
+
+TEST_F(MetadataReaderTest, InvalidMaxOutputs) {
+  // Must be a 3-tuple to be valid.
+  std::array<int32_t, 4> invalid = {{12, 34, 56, 78}};
+  ASSERT_EQ(v4l2_camera_hal::UpdateMetadata(
+                metadata_.get(), max_outputs_tag_, invalid),
+            0);
+  FillDUT();
+  int32_t actual;
+  // Don't mind the aliasing since we don't care about the value.
+  ASSERT_EQ(dut_->MaxOutputStreams(&actual, &actual, &actual), -EINVAL);
+}
+
+TEST_F(MetadataReaderTest, EmptyMaxOutputs) {
+  FillDUT();
+  int32_t actual;
+  // Don't mind the aliasing since we don't care about the value.
+  ASSERT_EQ(dut_->MaxOutputStreams(&actual, &actual, &actual), -ENOENT);
+}
+
+TEST_F(MetadataReaderTest, StreamConfigurations) {
+  v4l2_camera_hal::ArrayVector<int32_t, 4> configs;
+  std::array<int32_t, 4> config1{
+      {1, 2, 3, ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT}};
+  std::array<int32_t, 4> config2{
+      {5, 6, 7, ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT}};
+  configs.push_back(config1);
+  configs.push_back(config2);
+  ASSERT_EQ(
+      v4l2_camera_hal::UpdateMetadata(metadata_.get(), configs_tag_, configs),
+      0);
+  FillDUT();
+  std::vector<StreamConfiguration> actual;
+  ASSERT_EQ(dut_->StreamConfigurations(&actual), 0);
+  ASSERT_EQ(actual.size(), configs.num_arrays());
+  EXPECT_EQ(actual[0].spec.format, config1[0]);
+  EXPECT_EQ(actual[0].spec.width, config1[1]);
+  EXPECT_EQ(actual[0].spec.height, config1[2]);
+  EXPECT_EQ(actual[0].direction, config1[3]);
+  EXPECT_EQ(actual[1].spec.format, config2[0]);
+  EXPECT_EQ(actual[1].spec.width, config2[1]);
+  EXPECT_EQ(actual[1].spec.height, config2[2]);
+  EXPECT_EQ(actual[1].direction, config2[3]);
+}
+
+TEST_F(MetadataReaderTest, InvalidStreamConfigurationDirection) {
+  // -1 is not a valid direction.
+  std::array<int32_t, 4> config{{1, 2, 3, -1}};
+  ASSERT_EQ(
+      v4l2_camera_hal::UpdateMetadata(metadata_.get(), configs_tag_, config),
+      0);
+  FillDUT();
+  std::vector<StreamConfiguration> actual;
+  ASSERT_EQ(dut_->StreamConfigurations(&actual), -EINVAL);
+}
+
+TEST_F(MetadataReaderTest, InvalidStreamConfigurationSize) {
+  // Both size dimensions must be > 0.
+  std::array<int32_t, 4> config{
+      {1, 2, 0, ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT}};
+  ASSERT_EQ(
+      v4l2_camera_hal::UpdateMetadata(metadata_.get(), configs_tag_, config),
+      0);
+  FillDUT();
+  std::vector<StreamConfiguration> actual;
+  ASSERT_EQ(dut_->StreamConfigurations(&actual), -EINVAL);
+}
+
+TEST_F(MetadataReaderTest, InvalidStreamConfigurationNumElements) {
+  // Should be a multiple of 4.
+  std::array<int32_t, 5> config{
+      {1, 2, 3, ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT, 5}};
+  ASSERT_EQ(
+      v4l2_camera_hal::UpdateMetadata(metadata_.get(), configs_tag_, config),
+      0);
+  FillDUT();
+  std::vector<StreamConfiguration> actual;
+  ASSERT_EQ(dut_->StreamConfigurations(&actual), -EINVAL);
+}
+
+// TODO(b/31384253): Test that failure occurs if
+// required configurations are not present.
+
+TEST_F(MetadataReaderTest, NoStreamConfigurations) {
+  FillDUT();
+  std::vector<StreamConfiguration> actual;
+  ASSERT_EQ(dut_->StreamConfigurations(&actual), -ENOENT);
+}
+
+TEST_F(MetadataReaderTest, StreamStallDurations) {
+  v4l2_camera_hal::ArrayVector<int64_t, 4> stalls;
+  std::array<int64_t, 4> stall1{{1, 2, 3, 4}};
+  std::array<int64_t, 4> stall2{{5, 6, 7, 8}};
+  stalls.push_back(stall1);
+  stalls.push_back(stall2);
+  ASSERT_EQ(
+      v4l2_camera_hal::UpdateMetadata(metadata_.get(), stalls_tag_, stalls), 0);
+  FillDUT();
+  std::vector<StreamStallDuration> actual;
+  ASSERT_EQ(dut_->StreamStallDurations(&actual), 0);
+  ASSERT_EQ(actual.size(), stalls.num_arrays());
+  EXPECT_EQ(actual[0].spec.format, stall1[0]);
+  EXPECT_EQ(actual[0].spec.width, stall1[1]);
+  EXPECT_EQ(actual[0].spec.height, stall1[2]);
+  EXPECT_EQ(actual[0].duration, stall1[3]);
+  EXPECT_EQ(actual[1].spec.format, stall2[0]);
+  EXPECT_EQ(actual[1].spec.width, stall2[1]);
+  EXPECT_EQ(actual[1].spec.height, stall2[2]);
+  EXPECT_EQ(actual[1].duration, stall2[3]);
+}
+
+TEST_F(MetadataReaderTest, InvalidStreamStallDurationDuration) {
+  // -1 is not a valid duration.
+  std::array<int64_t, 4> stall{{1, 2, 3, -1}};
+  ASSERT_EQ(
+      v4l2_camera_hal::UpdateMetadata(metadata_.get(), stalls_tag_, stall), 0);
+  FillDUT();
+  std::vector<StreamStallDuration> actual;
+  ASSERT_EQ(dut_->StreamStallDurations(&actual), -EINVAL);
+}
+
+TEST_F(MetadataReaderTest, InvalidStreamStallDurationSize) {
+  // Both size dimensions must be > 0.
+  std::array<int64_t, 4> stall{{1, 2, 0, 3}};
+  ASSERT_EQ(
+      v4l2_camera_hal::UpdateMetadata(metadata_.get(), stalls_tag_, stall), 0);
+  FillDUT();
+  std::vector<StreamStallDuration> actual;
+  ASSERT_EQ(dut_->StreamStallDurations(&actual), -EINVAL);
+}
+
+TEST_F(MetadataReaderTest, InvalidStreamStallDurationNumElements) {
+  // Should be a multiple of 4.
+  std::array<int64_t, 5> stall{{1, 2, 3, 4, 5}};
+  ASSERT_EQ(
+      v4l2_camera_hal::UpdateMetadata(metadata_.get(), stalls_tag_, stall), 0);
+  FillDUT();
+  std::vector<StreamStallDuration> actual;
+  ASSERT_EQ(dut_->StreamStallDurations(&actual), -EINVAL);
+}
+
+// TODO(b/31384253): Test that failure occurs if
+// YUV_420_888, RAW10, RAW12, RAW_OPAQUE, or IMPLEMENTATION_DEFINED
+// formats have stall durations > 0.
+
+TEST_F(MetadataReaderTest, NoStreamStallDurations) {
+  FillDUT();
+  std::vector<StreamStallDuration> actual;
+  ASSERT_EQ(dut_->StreamStallDurations(&actual), -ENOENT);
+}
+
+TEST_F(MetadataReaderTest, ReprocessFormats) {
+  ReprocessFormatMap expected{{1, {4}}, {2, {5, 6}}, {3, {7, 8, 9}}};
+  std::vector<int32_t> raw;
+  for (const auto& input_outputs : expected) {
+    raw.push_back(input_outputs.first);
+    raw.push_back(input_outputs.second.size());
+    raw.insert(
+        raw.end(), input_outputs.second.begin(), input_outputs.second.end());
+  }
+  ASSERT_EQ(v4l2_camera_hal::UpdateMetadata(
+                metadata_.get(), reprocess_formats_tag_, raw),
+            0);
+  FillDUT();
+  ReprocessFormatMap actual;
+  ASSERT_EQ(dut_->ReprocessFormats(&actual), 0);
+  EXPECT_EQ(actual, expected);
+}
+
+TEST_F(MetadataReaderTest, ReprocessFormatsNoOutputs) {
+  // 0 indicates that there are 0 output formats for input format 1,
+  // which is not ok.
+  std::vector<int32_t> raw{1, 0};
+  ASSERT_EQ(v4l2_camera_hal::UpdateMetadata(
+                metadata_.get(), reprocess_formats_tag_, raw),
+            0);
+  FillDUT();
+  ReprocessFormatMap actual;
+  ASSERT_EQ(dut_->ReprocessFormats(&actual), -EINVAL);
+}
+
+TEST_F(MetadataReaderTest, ReprocessFormatsPastEnd) {
+  // 3 indicates that there are 3 output formats for input format 1,
+  // which is not ok since there are only 2 here.
+  std::vector<int32_t> raw{1, 3, 0, 0};
+  ASSERT_EQ(v4l2_camera_hal::UpdateMetadata(
+                metadata_.get(), reprocess_formats_tag_, raw),
+            0);
+  FillDUT();
+  ReprocessFormatMap actual;
+  ASSERT_EQ(dut_->ReprocessFormats(&actual), -EINVAL);
+}
+
+TEST_F(MetadataReaderTest, EmptyReprocessFormats) {
+  FillDUT();
+  ReprocessFormatMap actual;
+  ASSERT_EQ(dut_->ReprocessFormats(&actual), -ENOENT);
+}
+
+}  // namespace default_camera_hal
diff --git a/modules/camera/3_4/metadata/metadata_test.cpp b/modules/camera/3_4/metadata/metadata_test.cpp
new file mode 100644
index 0000000..508884c
--- /dev/null
+++ b/modules/camera/3_4/metadata/metadata_test.cpp
@@ -0,0 +1,322 @@
+/*
+ * 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 "metadata.h"
+
+#include <memory>
+#include <set>
+#include <vector>
+
+#include <camera/CameraMetadata.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "metadata_common.h"
+#include "partial_metadata_interface_mock.h"
+
+using testing::AtMost;
+using testing::Return;
+using testing::Test;
+using testing::_;
+
+namespace v4l2_camera_hal {
+
+class MetadataTest : public Test {
+ protected:
+  virtual void SetUp() {
+    // Clear the DUT. AddComponents must be called before using it.
+    dut_.reset();
+
+    component1_.reset(new PartialMetadataInterfaceMock());
+    component2_.reset(new PartialMetadataInterfaceMock());
+    metadata_.reset(new android::CameraMetadata());
+    non_empty_metadata_.reset(new android::CameraMetadata());
+    uint8_t val = 1;
+    non_empty_metadata_->update(ANDROID_COLOR_CORRECTION_MODE, &val, 1);
+  }
+
+  // Once the component mocks have had expectations set,
+  // add them to the device under test.
+  virtual void AddComponents() {
+    // Don't mind moving; Gmock/Gtest fails on leaked mocks unless disabled by
+    // runtime flags.
+    PartialMetadataSet components;
+    components.insert(std::move(component1_));
+    components.insert(std::move(component2_));
+    dut_.reset(new Metadata(std::move(components)));
+  }
+
+  virtual void CompareTags(const std::set<int32_t>& expected,
+                           const camera_metadata_entry_t& actual) {
+    ASSERT_EQ(expected.size(), actual.count);
+    for (size_t i = 0; i < actual.count; ++i) {
+      EXPECT_NE(expected.find(actual.data.i32[i]), expected.end());
+    }
+  }
+
+  // Device under test.
+  std::unique_ptr<Metadata> dut_;
+  // Mocks.
+  std::unique_ptr<PartialMetadataInterfaceMock> component1_;
+  std::unique_ptr<PartialMetadataInterfaceMock> component2_;
+  // Metadata.
+  std::unique_ptr<android::CameraMetadata> metadata_;
+  std::unique_ptr<android::CameraMetadata> non_empty_metadata_;
+  // An empty vector to use as necessary.
+  std::vector<int32_t> empty_tags_;
+};
+
+TEST_F(MetadataTest, FillStaticSuccess) {
+  // Should populate all the component static pieces.
+  EXPECT_CALL(*component1_, PopulateStaticFields(_)).WillOnce(Return(0));
+  EXPECT_CALL(*component2_, PopulateStaticFields(_)).WillOnce(Return(0));
+
+  // Should populate the meta keys, by polling each component's keys.
+  std::vector<int32_t> static_tags_1({1, 2});
+  std::vector<int32_t> static_tags_2({3, 4});
+  std::vector<int32_t> control_tags_1({5, 6});
+  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(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.
+  ASSERT_EQ(dut_->FillStaticMetadata(metadata_.get()), 0);
+
+  // Meta keys should be filled correctly.
+  // Note: sets are used here, but it is undefined behavior if
+  // the class has multiple componenets reporting overlapping tags.
+
+  // Get the expected tags = combined tags of all components.
+  std::set<int32_t> static_tags(static_tags_1.begin(), static_tags_1.end());
+  static_tags.insert(static_tags_2.begin(), static_tags_2.end());
+  std::set<int32_t> control_tags(control_tags_1.begin(), control_tags_1.end());
+  control_tags.insert(control_tags_2.begin(), control_tags_2.end());
+  std::set<int32_t> dynamic_tags(dynamic_tags_1.begin(), dynamic_tags_1.end());
+  dynamic_tags.insert(dynamic_tags_2.begin(), dynamic_tags_2.end());
+
+  // Static tags includes not only all component static tags, but also
+  // the meta AVAILABLE_*_KEYS (* = [REQUEST, RESULT, CHARACTERISTICS]).
+  static_tags.emplace(ANDROID_REQUEST_AVAILABLE_REQUEST_KEYS);
+  static_tags.emplace(ANDROID_REQUEST_AVAILABLE_RESULT_KEYS);
+  static_tags.emplace(ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS);
+
+  // Check against what was filled in in the metadata.
+  CompareTags(static_tags,
+              metadata_->find(ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS));
+  CompareTags(control_tags,
+              metadata_->find(ANDROID_REQUEST_AVAILABLE_REQUEST_KEYS));
+  CompareTags(dynamic_tags,
+              metadata_->find(ANDROID_REQUEST_AVAILABLE_RESULT_KEYS));
+}
+
+TEST_F(MetadataTest, FillStaticFail) {
+  int err = -99;
+  // Order undefined, and may or may not exit early; use AtMost.
+  EXPECT_CALL(*component1_, PopulateStaticFields(_))
+      .Times(AtMost(1))
+      .WillOnce(Return(0));
+  EXPECT_CALL(*component2_, PopulateStaticFields(_)).WillOnce(Return(err));
+
+  // May or may not exit early, may still try to populate meta tags.
+  EXPECT_CALL(*component1_, StaticTags())
+      .Times(AtMost(1))
+      .WillOnce(Return(empty_tags_));
+  EXPECT_CALL(*component1_, ControlTags())
+      .Times(AtMost(1))
+      .WillOnce(Return(empty_tags_));
+  EXPECT_CALL(*component1_, DynamicTags())
+      .Times(AtMost(1))
+      .WillOnce(Return(empty_tags_));
+  EXPECT_CALL(*component2_, StaticTags())
+      .Times(AtMost(1))
+      .WillOnce(Return(empty_tags_));
+  EXPECT_CALL(*component2_, ControlTags())
+      .Times(AtMost(1))
+      .WillOnce(Return(empty_tags_));
+  EXPECT_CALL(*component2_, DynamicTags())
+      .Times(AtMost(1))
+      .WillOnce(Return(empty_tags_));
+
+  AddComponents();
+  // If any component errors, error should be returned
+  EXPECT_EQ(dut_->FillStaticMetadata(metadata_.get()), err);
+}
+
+TEST_F(MetadataTest, FillStaticNull) {
+  AddComponents();
+  EXPECT_EQ(dut_->FillStaticMetadata(nullptr), -EINVAL);
+}
+
+TEST_F(MetadataTest, IsValidSuccess) {
+  // Should check if all the component request values are valid.
+  EXPECT_CALL(*component1_, SupportsRequestValues(_)).WillOnce(Return(true));
+  EXPECT_CALL(*component2_, SupportsRequestValues(_)).WillOnce(Return(true));
+
+  AddComponents();
+  // Should succeed.
+  // Note: getAndLock is a lock against pointer invalidation, not concurrency,
+  // and unlocks on object destruction.
+  EXPECT_TRUE(dut_->IsValidRequest(*non_empty_metadata_));
+}
+
+TEST_F(MetadataTest, IsValidFail) {
+  // Should check if all the component request values are valid.
+  // Order undefined, and may or may not exit early; use AtMost.
+  EXPECT_CALL(*component1_, SupportsRequestValues(_))
+      .Times(AtMost(1))
+      .WillOnce(Return(true));
+  EXPECT_CALL(*component2_, SupportsRequestValues(_)).WillOnce(Return(false));
+
+  AddComponents();
+  // Should fail since one of the components failed.
+  // Note: getAndLock is a lock against pointer invalidation, not concurrency,
+  // and unlocks on object destruction.
+  EXPECT_FALSE(dut_->IsValidRequest(*non_empty_metadata_));
+}
+
+TEST_F(MetadataTest, IsValidEmpty) {
+  // Setting null settings is a special case indicating to use the
+  // previous (valid) settings. As such it is inherently valid.
+  // Should not try to check any components.
+  EXPECT_CALL(*component1_, SupportsRequestValues(_)).Times(0);
+  EXPECT_CALL(*component2_, SupportsRequestValues(_)).Times(0);
+
+  AddComponents();
+  EXPECT_TRUE(dut_->IsValidRequest(*metadata_));
+}
+
+TEST_F(MetadataTest, GetTemplateSuccess) {
+  int template_type = 3;
+
+  // Should check if all the components fill the template successfully.
+  EXPECT_CALL(*component1_, PopulateTemplateRequest(template_type, _))
+      .WillOnce(Return(0));
+  EXPECT_CALL(*component2_, PopulateTemplateRequest(template_type, _))
+      .WillOnce(Return(0));
+
+  AddComponents();
+  // Should succeed.
+  EXPECT_EQ(dut_->GetRequestTemplate(template_type, metadata_.get()), 0);
+}
+
+TEST_F(MetadataTest, GetTemplateFail) {
+  int err = -99;
+  int template_type = 3;
+
+  // Should check if all the components fill the template successfully.
+  // Order undefined, and may or may not exit early; use AtMost.
+  EXPECT_CALL(*component1_, PopulateTemplateRequest(template_type, _))
+      .Times(AtMost(1))
+      .WillOnce(Return(0));
+  EXPECT_CALL(*component2_, PopulateTemplateRequest(template_type, _))
+      .WillOnce(Return(err));
+
+  AddComponents();
+  // Should fail since one of the components failed.
+  EXPECT_EQ(dut_->GetRequestTemplate(template_type, metadata_.get()), err);
+}
+
+TEST_F(MetadataTest, GetTemplateNull) {
+  AddComponents();
+  EXPECT_EQ(dut_->GetRequestTemplate(1, nullptr), -EINVAL);
+}
+
+TEST_F(MetadataTest, GetTemplateInvalid) {
+  int template_type = 99;  // Invalid template type.
+
+  AddComponents();
+  // Should fail fast since template type is invalid.
+  EXPECT_EQ(dut_->GetRequestTemplate(template_type, metadata_.get()), -EINVAL);
+}
+
+TEST_F(MetadataTest, SetSettingsSuccess) {
+  // Should check if all the components set successfully.
+  EXPECT_CALL(*component1_, SetRequestValues(_)).WillOnce(Return(0));
+  EXPECT_CALL(*component2_, SetRequestValues(_)).WillOnce(Return(0));
+
+  AddComponents();
+  // Should succeed.
+  // Note: getAndLock is a lock against pointer invalidation, not concurrency,
+  // and unlocks on object destruction.
+  EXPECT_EQ(dut_->SetRequestSettings(*non_empty_metadata_), 0);
+}
+
+TEST_F(MetadataTest, SetSettingsFail) {
+  int err = -99;
+
+  // Should check if all the components set successfully.
+  // Order undefined, and may or may not exit early; use AtMost.
+  EXPECT_CALL(*component1_, SetRequestValues(_))
+      .Times(AtMost(1))
+      .WillOnce(Return(0));
+  EXPECT_CALL(*component2_, SetRequestValues(_)).WillOnce(Return(err));
+
+  AddComponents();
+  // Should fail since one of the components failed.
+  // Note: getAndLock is a lock against pointer invalidation, not concurrency,
+  // and unlocks on object destruction.
+  EXPECT_EQ(dut_->SetRequestSettings(*non_empty_metadata_), err);
+}
+
+TEST_F(MetadataTest, SetSettingsEmpty) {
+  // Setting null settings is a special case indicating to use the
+  // previous settings. Should not try to set any components.
+  EXPECT_CALL(*component1_, SetRequestValues(_)).Times(0);
+  EXPECT_CALL(*component2_, SetRequestValues(_)).Times(0);
+
+  AddComponents();
+  // Should succeed.
+  EXPECT_EQ(dut_->SetRequestSettings(*metadata_), 0);
+}
+
+TEST_F(MetadataTest, FillResultSuccess) {
+  // Should check if all the components fill results successfully.
+  EXPECT_CALL(*component1_, PopulateDynamicFields(_)).WillOnce(Return(0));
+  EXPECT_CALL(*component2_, PopulateDynamicFields(_)).WillOnce(Return(0));
+
+  AddComponents();
+  // Should succeed.
+  EXPECT_EQ(dut_->FillResultMetadata(metadata_.get()), 0);
+}
+
+TEST_F(MetadataTest, FillResultFail) {
+  int err = -99;
+
+  // Should check if all the components fill results successfully.
+  // Order undefined, and may or may not exit early; use AtMost.
+  EXPECT_CALL(*component1_, PopulateDynamicFields(_))
+      .Times(AtMost(1))
+      .WillOnce(Return(0));
+  EXPECT_CALL(*component2_, PopulateDynamicFields(_)).WillOnce(Return(err));
+
+  AddComponents();
+  // Should fail since one of the components failed.
+  EXPECT_EQ(dut_->FillResultMetadata(metadata_.get()), err);
+}
+
+TEST_F(MetadataTest, FillResultNull) {
+  AddComponents();
+  EXPECT_EQ(dut_->FillResultMetadata(nullptr), -EINVAL);
+}
+
+}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/metadata/no_effect_control_delegate.h b/modules/camera/3_4/metadata/no_effect_control_delegate.h
new file mode 100644
index 0000000..e1936f1
--- /dev/null
+++ b/modules/camera/3_4/metadata/no_effect_control_delegate.h
@@ -0,0 +1,46 @@
+/*
+ * 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_NO_EFFECT_CONTROL_DELEGATE_H_
+#define V4L2_CAMERA_HAL_METADATA_NO_EFFECT_CONTROL_DELEGATE_H_
+
+#include "control_delegate_interface.h"
+
+namespace v4l2_camera_hal {
+
+// A NoEffectControlDelegate, as the name implies, has no effect.
+// The value can be gotten and set, but it does nothing.
+template <typename T>
+class NoEffectControlDelegate : public ControlDelegateInterface<T> {
+ public:
+  NoEffectControlDelegate(T default_value) : value_(default_value){};
+
+  int GetValue(T* value) override {
+    *value = value_;
+    return 0;
+  };
+  int SetValue(const T& value) override {
+    value_ = value;
+    return 0;
+  };
+
+ private:
+  T value_;
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_NO_EFFECT_CONTROL_DELEGATE_H_
diff --git a/modules/camera/3_4/metadata/no_effect_control_delegate_test.cpp b/modules/camera/3_4/metadata/no_effect_control_delegate_test.cpp
new file mode 100644
index 0000000..0a7a24c
--- /dev/null
+++ b/modules/camera/3_4/metadata/no_effect_control_delegate_test.cpp
@@ -0,0 +1,43 @@
+/*
+ * 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 "no_effect_control_delegate.h"
+
+#include <gtest/gtest.h>
+
+using testing::Test;
+
+namespace v4l2_camera_hal {
+
+TEST(NoEffectControlDelegateTest, DefaultGet) {
+  int32_t value = 12;
+  NoEffectControlDelegate<int32_t> control(value);
+  int32_t actual = 0;
+  ASSERT_EQ(control.GetValue(&actual), 0);
+  EXPECT_EQ(actual, value);
+}
+
+TEST(NoEffectControlDelegateTest, GetAndSet) {
+  int32_t value = 12;
+  NoEffectControlDelegate<int32_t> control(value);
+  int32_t new_value = 13;
+  ASSERT_EQ(control.SetValue(new_value), 0);
+  int32_t actual = 0;
+  ASSERT_EQ(control.GetValue(&actual), 0);
+  EXPECT_EQ(actual, new_value);
+}
+
+}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/metadata/partial_metadata_factory.h b/modules/camera/3_4/metadata/partial_metadata_factory.h
new file mode 100644
index 0000000..63bf2f5
--- /dev/null
+++ b/modules/camera/3_4/metadata/partial_metadata_factory.h
@@ -0,0 +1,335 @@
+/*
+ * 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 "state.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 partial metadata. Nullptr is returned on failures.
+
+// FixedState: A state that doesn't change.
+template <typename T>
+static std::unique_ptr<State<T>> FixedState(int32_t tag, T value);
+
+// NoEffectOptionlessControl: A control that accepts any value,
+// and has no effect. A default value is given.
+template <typename T>
+static std::unique_ptr<Control<T>> NoEffectOptionlessControl(
+    int32_t delegate_tag, T default_value);
+
+// NoEffectMenuControl: Some menu options, but they have no effect.
+template <typename T>
+static std::unique_ptr<Control<T>> NoEffectMenuControl(
+    int32_t delegate_tag,
+    int32_t options_tag,
+    const std::vector<T>& options,
+    std::map<int, T> default_values = {});
+
+// NoEffectSliderControl: A slider of options, but they have no effect.
+template <typename T>
+static std::unique_ptr<Control<T>> NoEffectSliderControl(
+    int32_t delegate_tag,
+    int32_t options_tag,
+    T min,
+    T max,
+    std::map<int, T> default_values = {});
+
+// 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,
+    std::map<int, T> default_values = {});
+
+// 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,
+    std::map<int, T> default_values = {});
+
+// V4L2ControlOrDefault: Like V4L2Control, but if the V4L2Control fails to
+// initialize for some reason, this method will fall back to NoEffectControl
+// with an initial value defined by |fallback_default|.
+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,
+    T fallback_default,
+    std::map<int, T> default_values = {});
+
+// -----------------------------------------------------------------------------
+
+template <typename T>
+std::unique_ptr<State<T>> FixedState(int32_t tag, T value) {
+  HAL_LOG_ENTER();
+
+  // Take advantage of ControlDelegate inheriting from StateDelegate;
+  // This will only expose GetValue, not SetValue, so the default will
+  // always be returned.
+  return std::make_unique<State<T>>(
+      tag, std::make_unique<NoEffectControlDelegate<T>>(value));
+}
+
+template <typename T>
+std::unique_ptr<Control<T>> NoEffectOptionlessControl(int32_t delegate_tag,
+                                                      T default_value) {
+  HAL_LOG_ENTER();
+
+  return std::make_unique<Control<T>>(
+      std::make_unique<TaggedControlDelegate<T>>(
+          delegate_tag,
+          std::make_unique<NoEffectControlDelegate<T>>(default_value)),
+      nullptr);
+}
+
+template <typename T>
+std::unique_ptr<Control<T>> NoEffectMenuControl(
+    int32_t delegate_tag,
+    int32_t options_tag,
+    const std::vector<T>& options,
+    std::map<int, T> default_values) {
+  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, default_values)));
+}
+
+template <typename T>
+std::unique_ptr<Control<T>> NoEffectSliderControl(
+    int32_t delegate_tag,
+    int32_t options_tag,
+    T min,
+    T max,
+    std::map<int, T> default_values) {
+  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, default_values)));
+}
+
+template <typename T>
+std::unique_ptr<Control<T>> NoEffectControl(ControlType type,
+                                            int32_t delegate_tag,
+                                            int32_t options_tag,
+                                            T value,
+                                            std::map<int, T> default_values) {
+  HAL_LOG_ENTER();
+
+  switch (type) {
+    case ControlType::kMenu:
+      return NoEffectMenuControl<T>(
+          delegate_tag, options_tag, {value}, default_values);
+    case ControlType::kSlider:
+      return NoEffectSliderControl(
+          delegate_tag, options_tag, value, value, default_values);
+  }
+}
+
+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,
+    std::map<int, T> default_values) {
+  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:
+      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, default_values));
+      // 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, default_values));
+      break;
+    default:
+      HAL_LOGE("Control %d (%s) is of unsupported type %d",
+               control_id,
+               control_query.name,
+               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,
+    T fallback_default,
+    std::map<int, T> default_values) {
+  HAL_LOG_ENTER();
+
+  std::unique_ptr<Control<T>> result = V4L2Control(type,
+                                                   delegate_tag,
+                                                   options_tag,
+                                                   device,
+                                                   control_id,
+                                                   converter,
+                                                   default_values);
+  if (!result) {
+    result = NoEffectControl(
+        type, delegate_tag, options_tag, fallback_default, default_values);
+  }
+  return result;
+}
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_CONTROL_FACTORY_H_
diff --git a/modules/camera/3_4/metadata/partial_metadata_factory_test.cpp b/modules/camera/3_4/metadata/partial_metadata_factory_test.cpp
new file mode 100644
index 0000000..9ca3a38
--- /dev/null
+++ b/modules/camera/3_4/metadata/partial_metadata_factory_test.cpp
@@ -0,0 +1,454 @@
+/*
+ * 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 "converter_interface_mock.h"
+#include "metadata_common.h"
+#include "partial_metadata_factory.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 PartialMetadataFactoryTest : 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 ExpectControlTags() {
+    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 ExpectControlOptions(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 ExpectControlValue(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;
+};
+
+class DISABLED_PartialMetadataFactoryTest : public PartialMetadataFactoryTest {
+};
+
+TEST_F(PartialMetadataFactoryTest, FixedState) {
+  uint8_t value = 13;
+  std::unique_ptr<State<uint8_t>> state = FixedState(delegate_tag_, value);
+
+  ASSERT_EQ(state->StaticTags().size(), 0);
+  ASSERT_EQ(state->ControlTags().size(), 0);
+  ASSERT_EQ(state->DynamicTags().size(), 1);
+  EXPECT_EQ(state->DynamicTags()[0], delegate_tag_);
+
+  android::CameraMetadata metadata;
+  ASSERT_EQ(state->PopulateDynamicFields(&metadata), 0);
+  EXPECT_EQ(metadata.entryCount(), 1);
+  ExpectMetadataEq(metadata, delegate_tag_, value);
+}
+
+TEST_F(PartialMetadataFactoryTest, NoEffectMenu) {
+  std::vector<uint8_t> test_options = {9, 8, 12};
+  control_ =
+      NoEffectMenuControl<uint8_t>(delegate_tag_, options_tag_, test_options);
+  ASSERT_NE(control_, nullptr);
+
+  ExpectControlTags();
+
+  // Options should be available.
+  ExpectControlOptions(test_options);
+  // Default value should be test_options[0].
+  ExpectControlValue(test_options[0]);
+}
+
+TEST_F(PartialMetadataFactoryTest, NoEffectGenericMenu) {
+  uint8_t default_val = 9;
+  control_ = NoEffectControl<uint8_t>(
+      ControlType::kMenu, delegate_tag_, options_tag_, default_val);
+  ASSERT_NE(control_, nullptr);
+
+  ExpectControlTags();
+
+  // Options should be available.
+  ExpectControlOptions({default_val});
+  // |default_val| should be default option.
+  ExpectControlValue(default_val);
+}
+
+TEST_F(PartialMetadataFactoryTest, 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);
+
+  ExpectControlTags();
+
+  // Single option should be available.
+  ExpectControlOptions(test_range);
+  // Default value should be the minimum (test_range[0]).
+  ExpectControlValue(test_range[0]);
+}
+
+TEST_F(PartialMetadataFactoryTest, NoEffectGenericSlider) {
+  uint8_t default_val = 9;
+  control_ = NoEffectControl<uint8_t>(
+      ControlType::kSlider, delegate_tag_, options_tag_, default_val);
+  ASSERT_NE(control_, nullptr);
+
+  ExpectControlTags();
+
+  // Range containing only |default_val| should be available.
+  ExpectControlOptions({default_val, default_val});
+  // |default_val| should be default option.
+  ExpectControlValue(default_val);
+}
+
+TEST_F(PartialMetadataFactoryTest, 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(PartialMetadataFactoryTest, 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(PartialMetadataFactoryTest, 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(PartialMetadataFactoryTest, 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(PartialMetadataFactoryTest, 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(DISABLED_PartialMetadataFactoryTest, V4L2FactoryMenu) {
+  // TODO(b/30921166): Correct Menu support so this can be re-enabled.
+  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);
+
+  ExpectControlTags();
+  ExpectControlOptions(expected_options);
+}
+
+TEST_F(DISABLED_PartialMetadataFactoryTest, V4L2FactoryMenuConversionFail) {
+  // TODO(b/30921166): Correct Menu support so this can be re-enabled.
+  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(DISABLED_PartialMetadataFactoryTest, V4L2FactoryMenuNoConversions) {
+  // TODO(b/30921166): Correct Menu support so this can be re-enabled.
+  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(PartialMetadataFactoryTest, 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);
+
+  ExpectControlTags();
+  ExpectControlOptions(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(PartialMetadataFactoryTest, 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(PartialMetadataFactoryTest, 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);
+
+  ExpectControlTags();
+
+  // Options should be available.
+  ExpectControlOptions({default_val});
+  // |default_val| should be default option.
+  ExpectControlValue(default_val);
+}
+
+TEST_F(PartialMetadataFactoryTest, 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);
+
+  ExpectControlTags();
+
+  // Range containing only |default_val| should be available.
+  ExpectControlOptions({default_val, default_val});
+  // |default_val| should be default option.
+  ExpectControlValue(default_val);
+}
+
+}  // 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
new file mode 100644
index 0000000..f6e9138
--- /dev/null
+++ b/modules/camera/3_4/metadata/partial_metadata_interface.h
@@ -0,0 +1,67 @@
+/*
+ * 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_PARTIAL_METADATA_INTERFACE_H_
+#define V4L2_CAMERA_HAL_METADATA_PARTIAL_METADATA_INTERFACE_H_
+
+#include <array>
+#include <vector>
+
+#include <camera/CameraMetadata.h>
+
+#include "../common.h"
+#include "array_vector.h"
+
+namespace v4l2_camera_hal {
+
+// A subset of metadata.
+class PartialMetadataInterface {
+ public:
+  virtual ~PartialMetadataInterface(){};
+
+  // The metadata tags this partial metadata is responsible for.
+  // See system/media/camera/docs/docs.html for descriptions of each tag.
+  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|.
+  virtual int PopulateStaticFields(android::CameraMetadata* metadata) const = 0;
+  // Add all the dynamic states this partial metadata
+  // is responsible for to |metadata|.
+  virtual int PopulateDynamicFields(
+      android::CameraMetadata* metadata) const = 0;
+  // Add default request values for a given template type for all the controls
+  // this partial metadata owns.
+  virtual int PopulateTemplateRequest(
+      int template_type, android::CameraMetadata* metadata) const = 0;
+  // Check if the requested control values from |metadata| (for controls
+  // this partial metadata owns) are supported. Empty/null values for owned
+  // control tags indicate no change, and are thus inherently supported.
+  // If |metadata| is empty all controls are implicitly supported.
+  virtual bool SupportsRequestValues(
+      const android::CameraMetadata& metadata) const = 0;
+  // Set all the controls this partial metadata
+  // is responsible for from |metadata|. Empty/null values for owned control
+  // tags indicate no change. If |metadata| is empty no controls should
+  // be changed.
+  virtual int SetRequestValues(const android::CameraMetadata& metadata) = 0;
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_PARTIAL_METADATA_INTERFACE_H_
diff --git a/modules/camera/3_4/metadata/partial_metadata_interface_mock.h b/modules/camera/3_4/metadata/partial_metadata_interface_mock.h
new file mode 100644
index 0000000..9e822a1
--- /dev/null
+++ b/modules/camera/3_4/metadata/partial_metadata_interface_mock.h
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+// Mock for partial metadata interfaces.
+
+#ifndef V4L2_CAMERA_HAL_PARTIAL_METADATA_INTERFACE_MOCK_H_
+#define V4L2_CAMERA_HAL_PARTIAL_METADATA_INTERFACE_MOCK_H_
+
+#include <gmock/gmock.h>
+
+#include "partial_metadata_interface.h"
+
+namespace v4l2_camera_hal {
+
+class PartialMetadataInterfaceMock : public PartialMetadataInterface {
+ public:
+  PartialMetadataInterfaceMock() : PartialMetadataInterface(){};
+  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*));
+  MOCK_CONST_METHOD1(PopulateDynamicFields, int(android::CameraMetadata*));
+  MOCK_CONST_METHOD2(PopulateTemplateRequest,
+                     int(int, android::CameraMetadata*));
+  MOCK_CONST_METHOD1(SupportsRequestValues,
+                     bool(const android::CameraMetadata&));
+  MOCK_METHOD1(SetRequestValues, int(const android::CameraMetadata&));
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_PARTIAL_METADATA_INTERFACE_MOCK_H_
diff --git a/modules/camera/3_4/metadata/property.h b/modules/camera/3_4/metadata/property.h
new file mode 100644
index 0000000..6884c7d
--- /dev/null
+++ b/modules/camera/3_4/metadata/property.h
@@ -0,0 +1,70 @@
+/*
+ * 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_PROPERTY_H_
+#define V4L2_CAMERA_HAL_METADATA_PROPERTY_H_
+
+#include "../common.h"
+#include "metadata_common.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 PartialMetadataInterface {
+ public:
+  Property(int32_t tag, T value) : tag_(tag), value_(std::move(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 {
+    return UpdateMetadata(metadata, tag_, value_);
+  };
+
+  virtual int PopulateDynamicFields(
+      android::CameraMetadata* metadata) const override {
+    return 0;
+  };
+
+  virtual int PopulateTemplateRequest(
+      int template_type, android::CameraMetadata* metadata) const override {
+    return 0;
+  };
+
+  virtual bool SupportsRequestValues(
+      const android::CameraMetadata& metadata) const override {
+    return true;
+  };
+
+  virtual int SetRequestValues(
+      const android::CameraMetadata& metadata) override {
+    return 0;
+  };
+
+ private:
+  int32_t tag_;
+  T value_;
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_PROPERTY_H_
diff --git a/modules/camera/3_4/metadata/property_test.cpp b/modules/camera/3_4/metadata/property_test.cpp
new file mode 100644
index 0000000..8e947ea
--- /dev/null
+++ b/modules/camera/3_4/metadata/property_test.cpp
@@ -0,0 +1,161 @@
+/*
+ * 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 "property.h"
+
+#include <array>
+#include <vector>
+
+#include <camera/CameraMetadata.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <hardware/camera3.h>
+
+#include "array_vector.h"
+#include "metadata_common.h"
+#include "test_common.h"
+
+using testing::AtMost;
+using testing::Return;
+using testing::ReturnRef;
+using testing::Test;
+using testing::_;
+
+namespace v4l2_camera_hal {
+
+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;
+  static constexpr int32_t float_tag_ = ANDROID_COLOR_CORRECTION_GAINS;
+  static constexpr int32_t int_tag_ = ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION;
+  static constexpr int32_t int_tag2_ = ANDROID_JPEG_ORIENTATION;
+};
+
+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);
+  EXPECT_EQ(property.DynamicTags().size(), 0);
+  ASSERT_EQ(property.StaticTags().size(), 1);
+  // The macro doesn't like the int_tag_ variable being passed in directly.
+  int32_t expected_tag = int_tag_;
+  EXPECT_EQ(property.StaticTags()[0], expected_tag);
+}
+
+TEST_F(PropertyTest, PopulateStaticSingleNumber) {
+  // Set up a fixed property.
+  int32_t data = 1234;
+  Property<int32_t> property(int_tag_, data);
+
+  // Populate static fields.
+  android::CameraMetadata metadata;
+  ASSERT_EQ(property.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, int_tag_, data);
+}
+
+// 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});
+  Property<std::vector<float>> property(float_tag_, data);
+
+  // Populate static fields.
+  android::CameraMetadata metadata;
+  ASSERT_EQ(property.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, float_tag_, data);
+}
+
+TEST_F(PropertyTest, PopulateStaticArray) {
+  // Set up a fixed property.
+  std::array<float, 4> data({{0.1, 2.3, 4.5, 6.7}});
+  Property<std::array<float, 4>> property(float_tag_, data);
+
+  // Populate static fields.
+  android::CameraMetadata metadata;
+  ASSERT_EQ(property.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, float_tag_, data);
+}
+
+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}});
+  Property<ArrayVector<uint8_t, 3>> property(byte_tag_, data);
+
+  // Populate static fields.
+  android::CameraMetadata metadata;
+  ASSERT_EQ(property.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, byte_tag_, data);
+}
+
+TEST_F(PropertyTest, PopulateDynamic) {
+  Property<int32_t> property(int_tag_, 1);
+
+  android::CameraMetadata metadata;
+  EXPECT_EQ(property.PopulateDynamicFields(&metadata), 0);
+
+  // Shouldn't have added anything.
+  EXPECT_TRUE(metadata.isEmpty());
+}
+
+TEST_F(PropertyTest, PopulateTemplate) {
+  Property<int32_t> property(int_tag_, 1);
+
+  for (int i = 1; i < CAMERA3_TEMPLATE_COUNT; ++i) {
+    android::CameraMetadata metadata;
+    EXPECT_EQ(property.PopulateTemplateRequest(i, &metadata), 0);
+    // Shouldn't have added anything.
+    EXPECT_TRUE(metadata.isEmpty());
+  }
+}
+
+TEST_F(PropertyTest, SupportsRequest) {
+  Property<int32_t> property(int_tag_, 1);
+  android::CameraMetadata metadata;
+  EXPECT_EQ(property.SupportsRequestValues(metadata), true);
+}
+
+TEST_F(PropertyTest, SetRequest) {
+  Property<int32_t> property(int_tag_, 1);
+  android::CameraMetadata metadata;
+  EXPECT_EQ(property.SetRequestValues(metadata), 0);
+}
+
+}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/metadata/ranged_converter.h b/modules/camera/3_4/metadata/ranged_converter.h
new file mode 100644
index 0000000..115ac2a
--- /dev/null
+++ b/modules/camera/3_4/metadata/ranged_converter.h
@@ -0,0 +1,102 @@
+/*
+ * 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_RANGED_CONVERTER_H_
+#define V4L2_CAMERA_HAL_METADATA_RANGED_CONVERTER_H_
+
+#include <memory>
+
+#include "../common.h"
+#include "converter_interface.h"
+
+namespace v4l2_camera_hal {
+
+// An RangedConverter fits values converted by a wrapped converter
+// to a stepped range (when going from metadata -> v4l2. The other
+// direction remains unchanged).
+template <typename TMetadata, typename TV4L2>
+class RangedConverter : public ConverterInterface<TMetadata, TV4L2> {
+ public:
+  RangedConverter(
+      std::shared_ptr<ConverterInterface<TMetadata, TV4L2>> wrapped_converter,
+      TV4L2 min,
+      TV4L2 max,
+      TV4L2 step);
+
+  virtual int MetadataToV4L2(TMetadata value, TV4L2* conversion) override;
+  virtual int V4L2ToMetadata(TV4L2 value, TMetadata* conversion) override;
+
+ private:
+  std::shared_ptr<ConverterInterface<TMetadata, TV4L2>> wrapped_converter_;
+  const TV4L2 min_;
+  const TV4L2 max_;
+  const TV4L2 step_;
+
+  DISALLOW_COPY_AND_ASSIGN(RangedConverter);
+};
+
+// -----------------------------------------------------------------------------
+
+template <typename TMetadata, typename TV4L2>
+RangedConverter<TMetadata, TV4L2>::RangedConverter(
+    std::shared_ptr<ConverterInterface<TMetadata, TV4L2>> wrapped_converter,
+    TV4L2 min,
+    TV4L2 max,
+    TV4L2 step)
+    : wrapped_converter_(std::move(wrapped_converter)),
+      min_(min),
+      max_(max),
+      step_(step) {
+  HAL_LOG_ENTER();
+}
+
+template <typename TMetadata, typename TV4L2>
+int RangedConverter<TMetadata, TV4L2>::MetadataToV4L2(TMetadata value,
+                                                      TV4L2* conversion) {
+  HAL_LOG_ENTER();
+
+  TV4L2 raw_conversion = 0;
+  int res = wrapped_converter_->MetadataToV4L2(value, &raw_conversion);
+  if (res) {
+    HAL_LOGE("Failed to perform underlying conversion.");
+    return res;
+  }
+
+  // Round down to step (steps start at min_).
+  raw_conversion -= (raw_conversion - min_) % step_;
+
+  // Clamp to range.
+  if (raw_conversion < min_) {
+    raw_conversion = min_;
+  } else if (raw_conversion > max_) {
+    raw_conversion = max_;
+  }
+
+  *conversion = raw_conversion;
+  return 0;
+}
+
+template <typename TMetadata, typename TV4L2>
+int RangedConverter<TMetadata, TV4L2>::V4L2ToMetadata(TV4L2 value,
+                                                      TMetadata* conversion) {
+  HAL_LOG_ENTER();
+
+  return wrapped_converter_->V4L2ToMetadata(value, conversion);
+}
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_RANGED_CONVERTER_H_
diff --git a/modules/camera/3_4/metadata/ranged_converter_test.cpp b/modules/camera/3_4/metadata/ranged_converter_test.cpp
new file mode 100644
index 0000000..2b5ccc6
--- /dev/null
+++ b/modules/camera/3_4/metadata/ranged_converter_test.cpp
@@ -0,0 +1,86 @@
+/*
+ * 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 "ranged_converter.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "converter_interface_mock.h"
+
+using testing::Return;
+using testing::SetArgPointee;
+using testing::Test;
+using testing::_;
+
+namespace v4l2_camera_hal {
+
+class RangedConverterTest : public Test {
+ protected:
+  virtual void SetUp() {
+    converter_.reset(new ConverterInterfaceMock<int, int32_t>());
+    dut_.reset(
+        new RangedConverter<int, int32_t>(converter_, min_, max_, step_));
+  }
+
+  virtual void ExpectConvert(int32_t converted, int32_t expected) {
+    int initial = 99;
+    EXPECT_CALL(*converter_, MetadataToV4L2(initial, _))
+        .WillOnce(DoAll(SetArgPointee<1>(converted), Return(0)));
+
+    int32_t actual = expected + 1;  // Initialize to non-expected value.
+    ASSERT_EQ(dut_->MetadataToV4L2(initial, &actual), 0);
+    EXPECT_EQ(actual, expected);
+  }
+
+  std::shared_ptr<ConverterInterfaceMock<int, int32_t>> converter_;
+  std::unique_ptr<RangedConverter<int, int32_t>> dut_;
+
+  const int32_t min_ = -11;
+  const int32_t max_ = 10;
+  const int32_t step_ = 3;
+};
+
+TEST_F(RangedConverterTest, NormalConversion) {
+  // A value that's in range and on step.
+  ExpectConvert(max_ - step_, max_ - step_);
+}
+
+TEST_F(RangedConverterTest, RoundingConversion) {
+  // A value that's in range but off step.
+  ExpectConvert(max_ - step_ + 1, max_ - step_);
+}
+
+TEST_F(RangedConverterTest, ClampUpConversion) {
+  // A value that's below range.
+  ExpectConvert(min_ - 1, min_);
+}
+
+TEST_F(RangedConverterTest, ClampDownConversion) {
+  // A value that's above range (even after fitting to step).
+  ExpectConvert(max_ + step_, max_);
+}
+
+TEST_F(RangedConverterTest, ConversionError) {
+  int initial = 99;
+  int err = -99;
+  EXPECT_CALL(*converter_, MetadataToV4L2(initial, _)).WillOnce(Return(err));
+
+  int32_t unused;
+  EXPECT_EQ(dut_->MetadataToV4L2(initial, &unused), err);
+}
+
+}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/metadata/scaling_converter.h b/modules/camera/3_4/metadata/scaling_converter.h
new file mode 100644
index 0000000..3087167
--- /dev/null
+++ b/modules/camera/3_4/metadata/scaling_converter.h
@@ -0,0 +1,75 @@
+/*
+ * 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_SCALING_CONVERTER_H_
+#define V4L2_CAMERA_HAL_METADATA_SCALING_CONVERTER_H_
+
+#include "../common.h"
+#include "converter_interface.h"
+
+namespace v4l2_camera_hal {
+
+// An ScalingConverter scales values up or down.
+template <typename TMetadata, typename TV4L2>
+class ScalingConverter : public ConverterInterface<TMetadata, TV4L2> {
+ public:
+  ScalingConverter(TMetadata v4l2_to_metadata_numerator,
+                   TMetadata v4l2_to_metadata_denominator);
+
+  virtual int MetadataToV4L2(TMetadata value, TV4L2* conversion) override;
+  virtual int V4L2ToMetadata(TV4L2 value, TMetadata* conversion) override;
+
+ private:
+  const TMetadata v4l2_to_metadata_numerator_;
+  const TMetadata v4l2_to_metadata_denominator_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScalingConverter);
+};
+
+// -----------------------------------------------------------------------------
+
+template <typename TMetadata, typename TV4L2>
+ScalingConverter<TMetadata, TV4L2>::ScalingConverter(
+    TMetadata v4l2_to_metadata_numerator,
+    TMetadata v4l2_to_metadata_denominator)
+    : v4l2_to_metadata_numerator_(v4l2_to_metadata_numerator),
+      v4l2_to_metadata_denominator_(v4l2_to_metadata_denominator) {
+  HAL_LOG_ENTER();
+}
+
+template <typename TMetadata, typename TV4L2>
+int ScalingConverter<TMetadata, TV4L2>::MetadataToV4L2(TMetadata value,
+                                                       TV4L2* conversion) {
+  HAL_LOG_ENTER();
+
+  *conversion = static_cast<TV4L2>(value * v4l2_to_metadata_denominator_ /
+                                   v4l2_to_metadata_numerator_);
+  return 0;
+}
+
+template <typename TMetadata, typename TV4L2>
+int ScalingConverter<TMetadata, TV4L2>::V4L2ToMetadata(TV4L2 value,
+                                                       TMetadata* conversion) {
+  HAL_LOG_ENTER();
+
+  *conversion = static_cast<TMetadata>(value) * v4l2_to_metadata_numerator_ /
+                v4l2_to_metadata_denominator_;
+  return 0;
+}
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_SCALING_CONVERTER_H_
diff --git a/modules/camera/3_4/metadata/slider_control_options.h b/modules/camera/3_4/metadata/slider_control_options.h
new file mode 100644
index 0000000..88c1651
--- /dev/null
+++ b/modules/camera/3_4/metadata/slider_control_options.h
@@ -0,0 +1,80 @@
+/*
+ * 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_SLIDER_CONTROL_OPTIONS_H_
+#define V4L2_CAMERA_HAL_METADATA_SLIDER_CONTROL_OPTIONS_H_
+
+#include <errno.h>
+
+#include <vector>
+
+#include "../common.h"
+#include "control_options_interface.h"
+#include "default_option_delegate.h"
+
+namespace v4l2_camera_hal {
+
+// SliderControlOptions offer a range of acceptable values, inclusive.
+template <typename T>
+class SliderControlOptions : public ControlOptionsInterface<T> {
+ public:
+  // |min| must be <= |max|.
+  SliderControlOptions(const T& min,
+                       const T& max,
+                       std::shared_ptr<DefaultOptionDelegate<T>> defaults)
+      : min_(min), max_(max), defaults_(defaults){};
+  SliderControlOptions(const T& min, const T& max, std::map<int, T> defaults)
+      : min_(min),
+        max_(max),
+        defaults_(std::make_shared<DefaultOptionDelegate<T>>(defaults)){};
+
+  virtual std::vector<T> MetadataRepresentation() override {
+    return {min_, max_};
+  };
+  virtual bool IsSupported(const T& option) override {
+    return option >= min_ && option <= max_;
+  };
+  virtual int DefaultValueForTemplate(int template_type,
+                                      T* default_value) override {
+    if (min_ > max_) {
+      HAL_LOGE("No valid default slider option, min is greater than max.");
+      return -ENODEV;
+    }
+
+    if (defaults_->DefaultValueForTemplate(template_type, default_value)) {
+      // Get as close as we can to the desired value.
+      if (*default_value < min_) {
+        *default_value = min_;
+      } else if (*default_value > max_) {
+        *default_value = max_;
+      }
+      return 0;
+    }
+
+    // No default given, just fall back to the min of the range.
+    *default_value = min_;
+    return 0;
+  };
+
+ private:
+  T min_;
+  T max_;
+  std::shared_ptr<DefaultOptionDelegate<T>> defaults_;
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_SLIDER_CONTROL_OPTIONS_H_
diff --git a/modules/camera/3_4/metadata/slider_control_options_test.cpp b/modules/camera/3_4/metadata/slider_control_options_test.cpp
new file mode 100644
index 0000000..b7cef5a
--- /dev/null
+++ b/modules/camera/3_4/metadata/slider_control_options_test.cpp
@@ -0,0 +1,129 @@
+/*
+ * 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 "slider_control_options.h"
+
+#include <memory>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <hardware/camera3.h>
+
+#include "default_option_delegate_mock.h"
+
+using testing::Return;
+using testing::SetArgPointee;
+using testing::Test;
+using testing::_;
+
+namespace v4l2_camera_hal {
+
+class SliderControlOptionsTest : public Test {
+ protected:
+  virtual void SetUp() {
+    mock_defaults_.reset(new DefaultOptionDelegateMock<int>());
+    dut_.reset(new SliderControlOptions<int>(min_, max_, mock_defaults_));
+  }
+
+  std::unique_ptr<SliderControlOptions<int>> dut_;
+  std::shared_ptr<DefaultOptionDelegateMock<int>> mock_defaults_;
+  const int min_ = 1;
+  const int max_ = 10;
+};
+
+TEST_F(SliderControlOptionsTest, MetadataRepresentation) {
+  // Technically order doesn't matter, but this is faster to write,
+  // and still passes.
+  std::vector<int> expected{min_, max_};
+  EXPECT_EQ(dut_->MetadataRepresentation(), expected);
+}
+
+TEST_F(SliderControlOptionsTest, IsSupported) {
+  for (int i = min_; i <= max_; ++i) {
+    EXPECT_TRUE(dut_->IsSupported(i));
+  }
+  // Out of range unsupported.
+  EXPECT_FALSE(dut_->IsSupported(min_ - 1));
+  EXPECT_FALSE(dut_->IsSupported(max_ + 1));
+}
+
+TEST_F(SliderControlOptionsTest, DelegateDefaultValue) {
+  int template_index = 3;
+  int expected = max_ - 1;
+  ASSERT_TRUE(dut_->IsSupported(expected));
+  EXPECT_CALL(*mock_defaults_, DefaultValueForTemplate(template_index, _))
+      .WillOnce(DoAll(SetArgPointee<1>(expected), Return(true)));
+  int actual = expected - 1;
+  EXPECT_EQ(dut_->DefaultValueForTemplate(template_index, &actual), 0);
+  EXPECT_EQ(actual, expected);
+}
+
+TEST_F(SliderControlOptionsTest, LowDelegateDefaultValue) {
+  int template_index = 3;
+  // min - 1 is below the valid range.
+  int default_val = min_ - 1;
+  // Should get bumped up into range.
+  int expected = min_;
+  ASSERT_FALSE(dut_->IsSupported(default_val));
+  ASSERT_TRUE(dut_->IsSupported(expected));
+
+  EXPECT_CALL(*mock_defaults_, DefaultValueForTemplate(template_index, _))
+      .WillOnce(DoAll(SetArgPointee<1>(default_val), Return(true)));
+  int actual = default_val;
+  EXPECT_EQ(dut_->DefaultValueForTemplate(template_index, &actual), 0);
+  EXPECT_EQ(actual, expected);
+}
+
+TEST_F(SliderControlOptionsTest, HighDelegateDefaultValue) {
+  int template_index = 3;
+  // max + 1 is above the valid range.
+  int default_val = max_ + 1;
+  // Should get bumped down into range.
+  int expected = max_;
+  ASSERT_FALSE(dut_->IsSupported(default_val));
+  ASSERT_TRUE(dut_->IsSupported(expected));
+
+  EXPECT_CALL(*mock_defaults_, DefaultValueForTemplate(template_index, _))
+      .WillOnce(DoAll(SetArgPointee<1>(default_val), Return(true)));
+  int actual = default_val;
+  EXPECT_EQ(dut_->DefaultValueForTemplate(template_index, &actual), 0);
+  EXPECT_EQ(actual, expected);
+}
+
+TEST_F(SliderControlOptionsTest, NoDelegateDefaultValue) {
+  int template_index = 3;
+  int actual = min_ - 1;
+  ASSERT_FALSE(dut_->IsSupported(actual));
+
+  // Have delegate error.
+  EXPECT_CALL(*mock_defaults_, DefaultValueForTemplate(template_index, _))
+      .WillOnce(Return(false));
+
+  // Should still give *some* supported value.
+  EXPECT_EQ(dut_->DefaultValueForTemplate(template_index, &actual), 0);
+  EXPECT_TRUE(dut_->IsSupported(actual));
+}
+
+TEST_F(SliderControlOptionsTest, NoDefaultValue) {
+  // Invalid options don't have a valid default.
+  SliderControlOptions<int> bad_options(10, 9, mock_defaults_);  // min > max.
+  for (int i = 1; i < CAMERA3_TEMPLATE_COUNT; ++i) {
+    int value = -1;
+    EXPECT_EQ(bad_options.DefaultValueForTemplate(i, &value), -ENODEV);
+  }
+}
+
+}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/metadata/state.h b/modules/camera/3_4/metadata/state.h
new file mode 100644
index 0000000..54f66e4
--- /dev/null
+++ b/modules/camera/3_4/metadata/state.h
@@ -0,0 +1,96 @@
+/*
+ * 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_STATE_H_
+#define V4L2_CAMERA_HAL_METADATA_STATE_H_
+
+#include "../common.h"
+#include "metadata_common.h"
+#include "partial_metadata_interface.h"
+#include "state_delegate_interface.h"
+
+namespace v4l2_camera_hal {
+
+// A State is a PartialMetadata that only has a single dynamic value.
+template <typename T>
+class State : public PartialMetadataInterface {
+ public:
+  State(int32_t tag, std::unique_ptr<StateDelegateInterface<T>> delegate)
+      : tag_(tag), delegate_(std::move(delegate)){};
+
+  virtual std::vector<int32_t> StaticTags() const override { return {}; };
+  virtual std::vector<int32_t> ControlTags() const override { return {}; };
+  virtual std::vector<int32_t> DynamicTags() const override { return {tag_}; };
+
+  virtual int PopulateStaticFields(
+      android::CameraMetadata* metadata) const override;
+  virtual int PopulateDynamicFields(
+      android::CameraMetadata* metadata) const override;
+  virtual int PopulateTemplateRequest(
+      int template_type, android::CameraMetadata* metadata) const override;
+  virtual bool SupportsRequestValues(
+      const android::CameraMetadata& metadata) const override;
+  virtual int SetRequestValues(
+      const android::CameraMetadata& metadata) override;
+
+ private:
+  int32_t tag_;
+  std::unique_ptr<StateDelegateInterface<T>> delegate_;
+};
+
+// -----------------------------------------------------------------------------
+
+template <typename T>
+int State<T>::PopulateStaticFields(android::CameraMetadata* metadata) const {
+  HAL_LOG_ENTER();
+  return 0;
+}
+
+template <typename T>
+int State<T>::PopulateDynamicFields(android::CameraMetadata* metadata) const {
+  HAL_LOG_ENTER();
+
+  T value;
+  int res = delegate_->GetValue(&value);
+  if (res) {
+    return res;
+  }
+  return UpdateMetadata(metadata, tag_, value);
+};
+
+template <typename T>
+int State<T>::PopulateTemplateRequest(int template_type,
+                                      android::CameraMetadata* metadata) const {
+  HAL_LOG_ENTER();
+  return 0;
+};
+
+template <typename T>
+bool State<T>::SupportsRequestValues(
+    const android::CameraMetadata& metadata) const {
+  HAL_LOG_ENTER();
+  return true;
+};
+
+template <typename T>
+int State<T>::SetRequestValues(const android::CameraMetadata& metadata) {
+  HAL_LOG_ENTER();
+  return 0;
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_STATE_H_
diff --git a/modules/camera/3_4/metadata/state_delegate_interface.h b/modules/camera/3_4/metadata/state_delegate_interface.h
new file mode 100644
index 0000000..c18ee3c
--- /dev/null
+++ b/modules/camera/3_4/metadata/state_delegate_interface.h
@@ -0,0 +1,34 @@
+/*
+ * 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_STATE_DELEGATE_INTERFACE_H_
+#define V4L2_CAMERA_HAL_METADATA_STATE_DELEGATE_INTERFACE_H_
+
+namespace v4l2_camera_hal {
+
+// A StateDelegate is simply a dynamic value that can be queried.
+// The value may change between queries.
+template <typename T>
+class StateDelegateInterface {
+ public:
+  virtual ~StateDelegateInterface(){};
+  // Returns 0 on success, error code on failure.
+  virtual int GetValue(T* value) = 0;
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_STATE_DELEGATE_INTERFACE_H_
diff --git a/modules/camera/3_4/metadata/state_delegate_interface_mock.h b/modules/camera/3_4/metadata/state_delegate_interface_mock.h
new file mode 100644
index 0000000..5064b83
--- /dev/null
+++ b/modules/camera/3_4/metadata/state_delegate_interface_mock.h
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+// Mock for state delegate interfaces.
+
+#ifndef V4L2_CAMERA_HAL_METADATA_STATE_DELEGATE_INTERFACE_MOCK_H_
+#define V4L2_CAMERA_HAL_METADATA_STATE_DELEGATE_INTERFACE_MOCK_H_
+
+#include <gmock/gmock.h>
+
+#include "state_delegate_interface.h"
+
+namespace v4l2_camera_hal {
+
+template <typename T>
+class StateDelegateInterfaceMock : public StateDelegateInterface<T> {
+ public:
+  StateDelegateInterfaceMock(){};
+  MOCK_METHOD1_T(GetValue, int(T*));
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_CONTROL_DELEGATE_INTERFACE_MOCK_H_
diff --git a/modules/camera/3_4/metadata/state_test.cpp b/modules/camera/3_4/metadata/state_test.cpp
new file mode 100644
index 0000000..5c308bc
--- /dev/null
+++ b/modules/camera/3_4/metadata/state_test.cpp
@@ -0,0 +1,116 @@
+/*
+ * 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 "state.h"
+
+#include <camera/CameraMetadata.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "metadata_common.h"
+#include "state_delegate_interface_mock.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 StateTest : public Test {
+ protected:
+  virtual void SetUp() {
+    mock_delegate_.reset(new StateDelegateInterfaceMock<uint8_t>());
+    // Nullify state so an error will be thrown if a test doesn't call
+    // PrepareState.
+    state_.reset();
+  }
+
+  virtual void PrepareState() {
+    // Use this method after all the EXPECT_CALLs to pass ownership of the mocks
+    // to the device.
+    state_.reset(new State<uint8_t>(tag_, std::move(mock_delegate_)));
+  }
+
+  std::unique_ptr<State<uint8_t>> state_;
+  std::unique_ptr<StateDelegateInterfaceMock<uint8_t>> mock_delegate_;
+
+  // Need tag that matches the data type (uint8_t) being passed.
+  const int32_t tag_ = ANDROID_CONTROL_AF_STATE;
+};
+
+TEST_F(StateTest, Tags) {
+  PrepareState();
+  EXPECT_TRUE(state_->StaticTags().empty());
+  EXPECT_TRUE(state_->ControlTags().empty());
+  ASSERT_EQ(state_->DynamicTags().size(), 1);
+  EXPECT_EQ(state_->DynamicTags()[0], tag_);
+}
+
+TEST_F(StateTest, PopulateStatic) {
+  PrepareState();
+  android::CameraMetadata metadata;
+  ASSERT_EQ(state_->PopulateStaticFields(&metadata), 0);
+  EXPECT_TRUE(metadata.isEmpty());
+}
+
+TEST_F(StateTest, PopulateDynamic) {
+  uint8_t expected = 99;
+  EXPECT_CALL(*mock_delegate_, GetValue(_))
+      .WillOnce(DoAll(SetArgPointee<0>(expected), Return(0)));
+
+  PrepareState();
+
+  android::CameraMetadata metadata;
+  ASSERT_EQ(state_->PopulateDynamicFields(&metadata), 0);
+  EXPECT_EQ(metadata.entryCount(), 1);
+  ExpectMetadataEq(metadata, tag_, expected);
+}
+
+TEST_F(StateTest, PopulateDynamicFail) {
+  int err = 123;
+  EXPECT_CALL(*mock_delegate_, GetValue(_)).WillOnce(Return(err));
+
+  PrepareState();
+
+  android::CameraMetadata metadata;
+  ASSERT_EQ(state_->PopulateDynamicFields(&metadata), err);
+}
+
+TEST_F(StateTest, PopulateTemplate) {
+  int template_type = 3;
+  PrepareState();
+  android::CameraMetadata metadata;
+  ASSERT_EQ(state_->PopulateTemplateRequest(template_type, &metadata), 0);
+  EXPECT_TRUE(metadata.isEmpty());
+}
+
+TEST_F(StateTest, SupportsRequest) {
+  PrepareState();
+  android::CameraMetadata metadata;
+  EXPECT_TRUE(state_->SupportsRequestValues(metadata));
+}
+
+TEST_F(StateTest, SetRequest) {
+  PrepareState();
+  android::CameraMetadata metadata;
+  ASSERT_EQ(state_->SetRequestValues(metadata), 0);
+}
+
+}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/metadata/tagged_control_delegate.h b/modules/camera/3_4/metadata/tagged_control_delegate.h
new file mode 100644
index 0000000..40677f9
--- /dev/null
+++ b/modules/camera/3_4/metadata/tagged_control_delegate.h
@@ -0,0 +1,50 @@
+/*
+ * 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_CONTROL_DELEGATE_H_
+#define V4L2_CAMERA_HAL_METADATA_TAGGED_CONTROL_DELEGATE_H_
+
+#include <memory>
+
+#include "control_delegate_interface.h"
+
+namespace v4l2_camera_hal {
+
+// A TaggedControlDelegate wraps a ControlDelegate and adds a tag.
+template <typename T>
+class TaggedControlDelegate : public ControlDelegateInterface<T> {
+ public:
+  TaggedControlDelegate(int32_t tag,
+                        std::unique_ptr<ControlDelegateInterface<T>> delegate)
+      : tag_(tag), delegate_(std::move(delegate)){};
+
+  int32_t tag() { return tag_; };
+
+  virtual int GetValue(T* value) override {
+    return delegate_->GetValue(value);
+  };
+  virtual int SetValue(const T& value) override {
+    return delegate_->SetValue(value);
+  };
+
+ private:
+  const int32_t tag_;
+  std::unique_ptr<ControlDelegateInterface<T>> delegate_;
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_CONTROL_DELEGATE_INTERFACE_H_
diff --git a/modules/camera/3_4/metadata/tagged_control_delegate_test.cpp b/modules/camera/3_4/metadata/tagged_control_delegate_test.cpp
new file mode 100644
index 0000000..ba29ab7
--- /dev/null
+++ b/modules/camera/3_4/metadata/tagged_control_delegate_test.cpp
@@ -0,0 +1,90 @@
+/*
+ * 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 "tagged_control_delegate.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "control_delegate_interface_mock.h"
+
+using testing::Return;
+using testing::SetArgPointee;
+using testing::Test;
+using testing::_;
+
+namespace v4l2_camera_hal {
+
+class TaggedControlDelegateTest : public Test {
+ protected:
+  virtual void SetUp() {
+    mock_delegate_.reset(new ControlDelegateInterfaceMock<uint8_t>());
+    // Nullify dut so an error will be thrown if a test doesn't call PrepareDUT.
+    dut_.reset();
+  }
+
+  virtual void PrepareDUT() {
+    // Use this method after all the EXPECT_CALLs to pass ownership of the
+    // delegate
+    // to the device.
+    dut_.reset(
+        new TaggedControlDelegate<uint8_t>(tag_, std::move(mock_delegate_)));
+  }
+
+  std::unique_ptr<TaggedControlDelegate<uint8_t>> dut_;
+  std::unique_ptr<ControlDelegateInterfaceMock<uint8_t>> mock_delegate_;
+  const int32_t tag_ = 123;
+};
+
+TEST_F(TaggedControlDelegateTest, GetTag) {
+  PrepareDUT();
+  EXPECT_EQ(dut_->tag(), tag_);
+}
+
+TEST_F(TaggedControlDelegateTest, GetSuccess) {
+  uint8_t expected = 3;
+  EXPECT_CALL(*mock_delegate_, GetValue(_))
+      .WillOnce(DoAll(SetArgPointee<0>(expected), Return(0)));
+  PrepareDUT();
+  uint8_t actual = expected + 1;  // Initialize to an incorrect value.
+  ASSERT_EQ(dut_->GetValue(&actual), 0);
+  EXPECT_EQ(actual, expected);
+}
+
+TEST_F(TaggedControlDelegateTest, GetFailure) {
+  int err = 3;
+  EXPECT_CALL(*mock_delegate_, GetValue(_)).WillOnce(Return(err));
+  PrepareDUT();
+  uint8_t unused = 0;
+  ASSERT_EQ(dut_->GetValue(&unused), err);
+}
+
+TEST_F(TaggedControlDelegateTest, SetSuccess) {
+  uint8_t value = 3;
+  EXPECT_CALL(*mock_delegate_, SetValue(value)).WillOnce(Return(0));
+  PrepareDUT();
+  ASSERT_EQ(dut_->SetValue(value), 0);
+}
+
+TEST_F(TaggedControlDelegateTest, SetFailure) {
+  int err = 3;
+  uint8_t value = 12;
+  EXPECT_CALL(*mock_delegate_, SetValue(value)).WillOnce(Return(err));
+  PrepareDUT();
+  ASSERT_EQ(dut_->SetValue(value), err);
+}
+
+}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/metadata/tagged_control_options.h b/modules/camera/3_4/metadata/tagged_control_options.h
new file mode 100644
index 0000000..3d900ae
--- /dev/null
+++ b/modules/camera/3_4/metadata/tagged_control_options.h
@@ -0,0 +1,61 @@
+/*
+ * 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_CONTROL_OPTIONS_H_
+#define V4L2_CAMERA_HAL_METADATA_TAGGED_CONTROL_OPTIONS_H_
+
+#include <memory>
+
+#include "control_options_interface.h"
+
+namespace v4l2_camera_hal {
+
+// A constant tag with a value not used as a real tag
+// (since all real tags are unsigned),  to indicate options
+// that should not be reported.
+// Any class working with TaggedControlOptions should check
+// the tag against this value before using it.
+static int32_t DO_NOT_REPORT_OPTIONS = -1;
+
+// A TaggedControlOptions wraps a ControlOptions and adds a tag.
+template <typename T>
+class TaggedControlOptions : public ControlOptionsInterface<T> {
+ public:
+  TaggedControlOptions(int32_t tag,
+                       std::unique_ptr<ControlOptionsInterface<T>> options)
+      : tag_(tag), options_(std::move(options)){};
+
+  int32_t tag() { return tag_; };
+
+  virtual std::vector<T> MetadataRepresentation() override {
+    return options_->MetadataRepresentation();
+  };
+  virtual bool IsSupported(const T& value) override {
+    return options_->IsSupported(value);
+  };
+  virtual int DefaultValueForTemplate(int template_type,
+                                      T* default_value) override {
+    return options_->DefaultValueForTemplate(template_type, default_value);
+  }
+
+ private:
+  const int32_t tag_;
+  std::unique_ptr<ControlOptionsInterface<T>> options_;
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_CONTROL_OPTIONS_INTERFACE_H_
diff --git a/modules/camera/3_4/metadata/tagged_control_options_test.cpp b/modules/camera/3_4/metadata/tagged_control_options_test.cpp
new file mode 100644
index 0000000..845426a
--- /dev/null
+++ b/modules/camera/3_4/metadata/tagged_control_options_test.cpp
@@ -0,0 +1,102 @@
+/*
+ * 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 "tagged_control_options.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "control_options_interface_mock.h"
+
+using testing::Return;
+using testing::SetArgPointee;
+using testing::Test;
+using testing::_;
+
+namespace v4l2_camera_hal {
+
+class TaggedControlOptionsTest : public Test {
+ protected:
+  virtual void SetUp() {
+    mock_options_.reset(new ControlOptionsInterfaceMock<uint8_t>());
+    // Nullify dut so an error will be thrown if a test doesn't call PrepareDUT.
+    dut_.reset();
+  }
+
+  virtual void PrepareDUT() {
+    // Use this method after all the EXPECT_CALLs to pass ownership of the
+    // options
+    // to the device.
+    dut_.reset(
+        new TaggedControlOptions<uint8_t>(tag_, std::move(mock_options_)));
+  }
+
+  std::unique_ptr<TaggedControlOptions<uint8_t>> dut_;
+  std::unique_ptr<ControlOptionsInterfaceMock<uint8_t>> mock_options_;
+  const int32_t tag_ = 123;
+};
+
+TEST_F(TaggedControlOptionsTest, GetTag) {
+  PrepareDUT();
+  EXPECT_EQ(dut_->tag(), tag_);
+}
+
+TEST_F(TaggedControlOptionsTest, MetadataRepresentation) {
+  std::vector<uint8_t> expected{3, 4, 5};
+  EXPECT_CALL(*mock_options_, MetadataRepresentation())
+      .WillOnce(Return(expected));
+  PrepareDUT();
+  ASSERT_EQ(dut_->MetadataRepresentation(), expected);
+}
+
+TEST_F(TaggedControlOptionsTest, IsSupportedTrue) {
+  bool supported = true;
+  uint8_t value = 3;
+  EXPECT_CALL(*mock_options_, IsSupported(value)).WillOnce(Return(supported));
+  PrepareDUT();
+  ASSERT_EQ(dut_->IsSupported(value), supported);
+}
+
+TEST_F(TaggedControlOptionsTest, IsSupportedFalse) {
+  bool supported = false;
+  uint8_t value = 3;
+  EXPECT_CALL(*mock_options_, IsSupported(value)).WillOnce(Return(supported));
+  PrepareDUT();
+  ASSERT_EQ(dut_->IsSupported(value), supported);
+}
+
+TEST_F(TaggedControlOptionsTest, DefaultValue) {
+  uint8_t value = 99;
+  int template_id = 3;
+  EXPECT_CALL(*mock_options_, DefaultValueForTemplate(template_id, _))
+      .WillOnce(DoAll(SetArgPointee<1>(value), Return(0)));
+  PrepareDUT();
+  uint8_t actual = value + 1;
+  EXPECT_EQ(dut_->DefaultValueForTemplate(template_id, &actual), 0);
+  EXPECT_EQ(actual, value);
+}
+
+TEST_F(TaggedControlOptionsTest, DefaultValueFail) {
+  int err = 12;
+  int template_id = 3;
+  EXPECT_CALL(*mock_options_, DefaultValueForTemplate(template_id, _))
+      .WillOnce(Return(err));
+  PrepareDUT();
+  uint8_t unused;
+  EXPECT_EQ(dut_->DefaultValueForTemplate(template_id, &unused), err);
+}
+
+}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/metadata/test_common.h b/modules/camera/3_4/metadata/test_common.h
new file mode 100644
index 0000000..489990f
--- /dev/null
+++ b/modules/camera/3_4/metadata/test_common.h
@@ -0,0 +1,96 @@
+/*
+ * 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_TEST_COMMON_H_
+#define V4L2_CAMERA_HAL_METADATA_TEST_COMMON_H_
+
+#include <array>
+#include <vector>
+
+#include <camera/CameraMetadata.h>
+#include <gtest/gtest.h>
+
+#include "array_vector.h"
+#include "metadata_common.h"
+
+namespace v4l2_camera_hal {
+
+// Check that metadata of a given tag matches expectations.
+// Generic.
+template <typename T>
+static void ExpectMetadataEq(const android::CameraMetadata& metadata,
+                             int32_t tag,
+                             const T* expected,
+                             size_t size) {
+  camera_metadata_ro_entry_t entry = metadata.find(tag);
+  ASSERT_EQ(entry.count, size);
+  const T* data = nullptr;
+  GetDataPointer(entry, &data);
+  ASSERT_NE(data, nullptr);
+  for (size_t i = 0; i < size; ++i) {
+    EXPECT_EQ(data[i], expected[i]);
+  }
+}
+
+// Single item.
+template <typename T>
+static void ExpectMetadataEq(const android::CameraMetadata& metadata,
+                             int32_t tag,
+                             T expected) {
+  ExpectMetadataEq(metadata, tag, &expected, 1);
+}
+
+// Vector of items.
+template <typename T>
+static void ExpectMetadataEq(const android::CameraMetadata& metadata,
+                             int32_t tag,
+                             const std::vector<T>& expected) {
+  ExpectMetadataEq(metadata, tag, expected.data(), expected.size());
+}
+
+// Array of items.
+template <typename T, size_t N>
+static void ExpectMetadataEq(const android::CameraMetadata& metadata,
+                             int32_t tag,
+                             const std::array<T, N>& expected) {
+  ExpectMetadataEq(metadata, tag, expected.data(), N);
+}
+
+// ArrayVector.
+template <typename T, size_t N>
+static void ExpectMetadataEq(const android::CameraMetadata& metadata,
+                             int32_t tag,
+                             const ArrayVector<T, N>& expected) {
+  ExpectMetadataEq(
+      metadata, tag, expected.data(), expected.total_num_elements());
+}
+
+// Vector of arrays.
+template <typename T, size_t N>
+static int ExpectMetadataEq(const android::CameraMetadata& metadata,
+                            int32_t tag,
+                            const std::vector<std::array<T, N>>& expected) {
+  // Convert to array vector so we know all the elements are contiguous.
+  ArrayVector<T, N> array_vector;
+  for (const auto& array : expected) {
+    array_vector.push_back(array);
+  }
+  ExpectMetadataEq(metadata, tag, array_vector);
+}
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_TEST_COMMON_H_
diff --git a/modules/camera/3_4/metadata/types.h b/modules/camera/3_4/metadata/types.h
new file mode 100644
index 0000000..093fe01
--- /dev/null
+++ b/modules/camera/3_4/metadata/types.h
@@ -0,0 +1,82 @@
+/*
+ * 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 DEFAULT_CAMERA_HAL_METADATA_TYPES_H_
+#define DEFAULT_CAMERA_HAL_METADATA_TYPES_H_
+
+#include <array>
+#include <map>
+#include <set>
+
+#include <hardware/camera3.h>
+
+namespace default_camera_hal {
+
+// A variety of Structs describing more complex metadata entries.
+
+// StreamSpec describe the attributes of a single stream.
+struct StreamSpec {
+  int32_t format;
+  int32_t width;
+  int32_t height;
+
+  StreamSpec(int32_t f, int32_t w, int32_t h)
+      : format(f), width(w), height(h) {}
+  StreamSpec(const camera3_stream_t* stream)
+      : format(stream->format), width(stream->width), height(stream->height) {}
+
+  struct Compare {
+    bool operator()(const StreamSpec& left, const StreamSpec& right) const {
+      // Base equality/comparison first on format, then on width, then height.
+      return left.format < right.format ||
+             (left.format == right.format &&
+              (left.width < right.width ||
+               (left.width == right.width && left.height < right.height)));
+    }
+  };
+};
+
+// StreamConfigurations indicate a possible direction configuration for
+// a given set of stream specifications.
+typedef std::array<int32_t, 4> RawStreamConfiguration;
+struct StreamConfiguration {
+  StreamSpec spec;
+  int32_t direction;
+
+  StreamConfiguration(const RawStreamConfiguration& raw)
+      : spec({raw[0], raw[1], raw[2]}), direction(raw[3]) {}
+};
+
+// StreamStallDurations indicate the stall duration (in ns) for
+// when a stream with a given set of specifications is used as output.
+typedef std::array<int64_t, 4> RawStreamStallDuration;
+struct StreamStallDuration {
+  StreamSpec spec;
+  int64_t duration;
+
+  StreamStallDuration(const RawStreamStallDuration& raw)
+      : spec({static_cast<int32_t>(raw[0]),
+              static_cast<int32_t>(raw[1]),
+              static_cast<int32_t>(raw[2])}),
+        duration(raw[3]) {}
+};
+
+// Map input formats to their supported reprocess output formats.
+typedef std::map<int32_t, std::set<int32_t>> ReprocessFormatMap;
+
+}  // namespace default_camera_hal
+
+#endif  // DEFAULT_CAMERA_HAL_METADATA_TYPES_H_
diff --git a/modules/camera/3_4/metadata/v4l2_control_delegate.h b/modules/camera/3_4/metadata/v4l2_control_delegate.h
new file mode 100644
index 0000000..3f45f9c
--- /dev/null
+++ b/modules/camera/3_4/metadata/v4l2_control_delegate.h
@@ -0,0 +1,66 @@
+/*
+ * 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_CONTROL_DELEGATE_H_
+#define V4L2_CAMERA_HAL_METADATA_V4L2_CONTROL_DELEGATE_H_
+
+#include "../v4l2_wrapper.h"
+#include "control_delegate_interface.h"
+#include "converter_interface.h"
+
+namespace v4l2_camera_hal {
+
+// A V4L2ControlDelegate routes getting and setting through V4L2
+template <typename TMetadata, typename TV4L2 = int32_t>
+class V4L2ControlDelegate : public ControlDelegateInterface<TMetadata> {
+ public:
+  V4L2ControlDelegate(
+      std::shared_ptr<V4L2Wrapper> device,
+      int control_id,
+      std::shared_ptr<ConverterInterface<TMetadata, TV4L2>> converter)
+      : device_(std::move(device)),
+        control_id_(control_id),
+        converter_(std::move(converter)){};
+
+  int GetValue(TMetadata* value) override {
+    TV4L2 v4l2_value;
+    int res = device_->GetControl(control_id_, &v4l2_value);
+    if (res) {
+      HAL_LOGE("Failed to get device value for control %d.", control_id_);
+      return res;
+    }
+    return converter_->V4L2ToMetadata(v4l2_value, value);
+  };
+
+  int SetValue(const TMetadata& value) override {
+    TV4L2 v4l2_value;
+    int res = converter_->MetadataToV4L2(value, &v4l2_value);
+    if (res) {
+      HAL_LOGE("Failed to convert metadata value to V4L2.");
+      return res;
+    }
+    return device_->SetControl(control_id_, v4l2_value);
+  };
+
+ private:
+  std::shared_ptr<V4L2Wrapper> device_;
+  int control_id_;
+  std::shared_ptr<ConverterInterface<TMetadata, TV4L2>> converter_;
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_METADATA_V4L2_CONTROL_DELEGATE_H_
diff --git a/modules/camera/3_4/metadata/v4l2_control_delegate_test.cpp b/modules/camera/3_4/metadata/v4l2_control_delegate_test.cpp
new file mode 100644
index 0000000..2f14d6f
--- /dev/null
+++ b/modules/camera/3_4/metadata/v4l2_control_delegate_test.cpp
@@ -0,0 +1,110 @@
+/*
+ * 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_control_delegate.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "../v4l2_wrapper_mock.h"
+#include "converter_interface_mock.h"
+
+using testing::Return;
+using testing::SetArgPointee;
+using testing::Test;
+using testing::_;
+
+namespace v4l2_camera_hal {
+
+class V4L2ControlDelegateTest : public Test {
+ protected:
+  virtual void SetUp() {
+    mock_device_.reset(new V4L2WrapperMock());
+    mock_converter_.reset(new ConverterInterfaceMock<uint8_t, int32_t>());
+    dut_.reset(new V4L2ControlDelegate<uint8_t>(
+        mock_device_, control_id_, mock_converter_));
+  }
+
+  std::unique_ptr<V4L2ControlDelegate<uint8_t>> dut_;
+  std::shared_ptr<V4L2WrapperMock> mock_device_;
+  std::shared_ptr<ConverterInterfaceMock<uint8_t, int32_t>> mock_converter_;
+  const int control_id_ = 123;
+};
+
+TEST_F(V4L2ControlDelegateTest, GetSuccess) {
+  int32_t device_result = 99;
+  uint8_t conversion_result = 10;
+  EXPECT_CALL(*mock_device_, GetControl(control_id_, _))
+      .WillOnce(DoAll(SetArgPointee<1>(device_result), Return(0)));
+  EXPECT_CALL(*mock_converter_, V4L2ToMetadata(device_result, _))
+      .WillOnce(DoAll(SetArgPointee<1>(conversion_result), Return(0)));
+
+  uint8_t actual = conversion_result + 1;  // Something incorrect.
+  ASSERT_EQ(dut_->GetValue(&actual), 0);
+  EXPECT_EQ(actual, conversion_result);
+}
+
+TEST_F(V4L2ControlDelegateTest, GetConverterFailure) {
+  int32_t device_result = 99;
+  EXPECT_CALL(*mock_device_, GetControl(control_id_, _))
+      .WillOnce(DoAll(SetArgPointee<1>(device_result), Return(0)));
+  int err = -99;
+  EXPECT_CALL(*mock_converter_, V4L2ToMetadata(device_result, _))
+      .WillOnce(Return(err));
+
+  uint8_t unused = 1;
+  ASSERT_EQ(dut_->GetValue(&unused), err);
+}
+
+TEST_F(V4L2ControlDelegateTest, GetDeviceFailure) {
+  int err = -99;
+  EXPECT_CALL(*mock_device_, GetControl(control_id_, _)).WillOnce(Return(err));
+
+  uint8_t unused = 1;
+  ASSERT_EQ(dut_->GetValue(&unused), err);
+}
+
+TEST_F(V4L2ControlDelegateTest, SetSuccess) {
+  uint8_t input = 10;
+  int32_t conversion_result = 99;
+  EXPECT_CALL(*mock_converter_, MetadataToV4L2(input, _))
+      .WillOnce(DoAll(SetArgPointee<1>(conversion_result), Return(0)));
+  EXPECT_CALL(*mock_device_, SetControl(control_id_, conversion_result, _))
+      .WillOnce(Return(0));
+
+  ASSERT_EQ(dut_->SetValue(input), 0);
+}
+
+TEST_F(V4L2ControlDelegateTest, SetConverterFailure) {
+  uint8_t input = 10;
+  int err = 12;
+  EXPECT_CALL(*mock_converter_, MetadataToV4L2(input, _)).WillOnce(Return(err));
+  ASSERT_EQ(dut_->SetValue(input), err);
+}
+
+TEST_F(V4L2ControlDelegateTest, SetDeviceFailure) {
+  uint8_t input = 10;
+  int32_t conversion_result = 99;
+  EXPECT_CALL(*mock_converter_, MetadataToV4L2(input, _))
+      .WillOnce(DoAll(SetArgPointee<1>(conversion_result), Return(0)));
+  int err = 66;
+  EXPECT_CALL(*mock_device_, SetControl(control_id_, conversion_result, _))
+      .WillOnce(Return(err));
+
+  ASSERT_EQ(dut_->SetValue(input), err);
+}
+
+}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/request_tracker.cpp b/modules/camera/3_4/request_tracker.cpp
new file mode 100644
index 0000000..09f634d
--- /dev/null
+++ b/modules/camera/3_4/request_tracker.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright 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 "request_tracker.h"
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "RequestTracker"
+#include <cutils/log.h>
+
+namespace default_camera_hal {
+
+RequestTracker::RequestTracker() {}
+
+RequestTracker::~RequestTracker() {}
+
+void RequestTracker::SetStreamConfiguration(
+    const camera3_stream_configuration_t& config) {
+  // Clear the old configuration.
+  ClearStreamConfiguration();
+  // Add an entry to the buffer tracking map for each configured stream.
+  for (size_t i = 0; i < config.num_streams; ++i) {
+    buffers_in_flight_.emplace(config.streams[i], 0);
+  }
+}
+
+void RequestTracker::ClearStreamConfiguration() {
+  // The keys of the in flight buffer map are the configured streams.
+  buffers_in_flight_.clear();
+}
+
+// Helper: get the streams used by a request.
+std::set<camera3_stream_t*> RequestStreams(const CaptureRequest& request) {
+  std::set<camera3_stream_t*> result;
+  if (request.input_buffer) {
+    result.insert(request.input_buffer->stream);
+  }
+  for (const auto& output_buffer : request.output_buffers) {
+    result.insert(output_buffer.stream);
+  }
+  return std::move(result);
+}
+
+bool RequestTracker::Add(std::shared_ptr<CaptureRequest> request) {
+  if (!CanAddRequest(*request)) {
+    return false;
+  }
+
+  // Add to the count for each stream used.
+  for (const auto stream : RequestStreams(*request)) {
+    ++buffers_in_flight_[stream];
+  }
+
+  // Store the request.
+  frames_in_flight_[request->frame_number] = request;
+
+  return true;
+}
+
+bool RequestTracker::Remove(std::shared_ptr<CaptureRequest> request) {
+  if (!request) {
+    return false;
+  }
+
+  // Get the request.
+  const auto frame_number_request =
+      frames_in_flight_.find(request->frame_number);
+  if (frame_number_request == frames_in_flight_.end()) {
+    ALOGE("%s: Frame %u is not in flight.", __func__, request->frame_number);
+    return false;
+  } else if (request != frame_number_request->second) {
+    ALOGE(
+        "%s: Request for frame %u cannot be removed: "
+        "does not matched the stored request.",
+        __func__,
+        request->frame_number);
+    return false;
+  }
+
+  frames_in_flight_.erase(frame_number_request);
+
+  // Decrement the counts of used streams.
+  for (const auto stream : RequestStreams(*request)) {
+    --buffers_in_flight_[stream];
+  }
+
+  return true;
+}
+
+void RequestTracker::Clear(
+    std::set<std::shared_ptr<CaptureRequest>>* requests) {
+  // If desired, extract all the currently in-flight requests.
+  if (requests) {
+    for (auto& frame_number_request : frames_in_flight_) {
+      requests->insert(frame_number_request.second);
+    }
+  }
+
+  // Clear out all tracking.
+  frames_in_flight_.clear();
+  // Maintain the configuration, but reset counts.
+  for (auto& stream_count : buffers_in_flight_) {
+    stream_count.second = 0;
+  }
+}
+
+bool RequestTracker::CanAddRequest(const CaptureRequest& request) const {
+  // Check that it's not a duplicate.
+  if (frames_in_flight_.count(request.frame_number) > 0) {
+    ALOGE("%s: Already tracking a request with frame number %d.",
+          __func__,
+          request.frame_number);
+    return false;
+  }
+
+  // Check that each stream has space
+  // (which implicitly checks if it is configured).
+  bool result = true;
+  for (const auto stream : RequestStreams(request)) {
+    if (StreamFull(stream)) {
+      ALOGE("%s: Stream %p is full.", __func__, stream);
+      return false;
+    }
+  }
+  return true;
+}
+
+bool RequestTracker::StreamFull(const camera3_stream_t* handle) const {
+  const auto it = buffers_in_flight_.find(handle);
+  if (it == buffers_in_flight_.end()) {
+    // Unconfigured streams are implicitly full.
+    ALOGV("%s: Stream %p is not a configured stream.", __func__, handle);
+    return true;
+  } else {
+    return it->second >= it->first->max_buffers;
+  }
+}
+
+bool RequestTracker::InFlight(uint32_t frame_number) const {
+  return frames_in_flight_.count(frame_number) > 0;
+}
+
+bool RequestTracker::Empty() const {
+  return frames_in_flight_.empty();
+}
+
+}  // namespace default_camera_hal
diff --git a/modules/camera/3_4/request_tracker.h b/modules/camera/3_4/request_tracker.h
new file mode 100644
index 0000000..a632a61
--- /dev/null
+++ b/modules/camera/3_4/request_tracker.h
@@ -0,0 +1,78 @@
+/*
+ * 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 DEFAULT_CAMERA_HAL_REQUEST_TRACKER_H_
+#define DEFAULT_CAMERA_HAL_REQUEST_TRACKER_H_
+
+#include <map>
+#include <memory>
+#include <set>
+
+#include <hardware/camera3.h>
+
+#include "capture_request.h"
+#include "common.h"
+
+namespace default_camera_hal {
+
+// Keep track of what requests and streams are in flight.
+class RequestTracker {
+ public:
+  RequestTracker();
+  virtual ~RequestTracker();
+
+  // Configuration methods. Both have undefined effects on in-flight requests,
+  // and should only be called when empty.
+  // Add configured streams. Replaces the previous configuration if any.
+  virtual void SetStreamConfiguration(
+      const camera3_stream_configuration_t& config);
+  // Reset to no configured streams.
+  virtual void ClearStreamConfiguration();
+
+  // Tracking methods.
+  // Track a request.
+  // False if a request of the same frame number is already being tracked
+  virtual bool Add(std::shared_ptr<CaptureRequest> request);
+  // Stop tracking a request.
+  // False if the given request is not being tracked.
+  virtual bool Remove(std::shared_ptr<CaptureRequest> request = nullptr);
+  // Empty out all requests being tracked.
+  virtual void Clear(
+      std::set<std::shared_ptr<CaptureRequest>>* requests = nullptr);
+
+  // Accessors to check availability.
+  // Check that a request isn't already in flight, and won't overflow any
+  // streams.
+  virtual bool CanAddRequest(const CaptureRequest& request) const;
+  // True if the given stream is already at max capacity.
+  virtual bool StreamFull(const camera3_stream_t* handle) const;
+  // True if a request is being tracked for the given frame number.
+  virtual bool InFlight(uint32_t frame_number) const;
+  // True if no requests being tracked.
+  virtual bool Empty() const;
+
+ private:
+  // Track for each stream, how many buffers are in flight.
+  std::map<const camera3_stream_t*, size_t> buffers_in_flight_;
+  // Track the frames in flight.
+  std::map<uint32_t, std::shared_ptr<CaptureRequest>> frames_in_flight_;
+
+  DISALLOW_COPY_AND_ASSIGN(RequestTracker);
+};
+
+}  // namespace default_camera_hal
+
+#endif  // DEFAULT_CAMERA_HAL_REQUEST_TRACKER_H_
diff --git a/modules/camera/3_4/request_tracker_test.cpp b/modules/camera/3_4/request_tracker_test.cpp
new file mode 100644
index 0000000..a68ff57
--- /dev/null
+++ b/modules/camera/3_4/request_tracker_test.cpp
@@ -0,0 +1,261 @@
+/*
+ * 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 "request_tracker.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using testing::AtMost;
+using testing::Expectation;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::Test;
+using testing::_;
+
+namespace default_camera_hal {
+
+class RequestTrackerTest : public Test {
+ protected:
+  void SetUp() {
+    stream1_.max_buffers = 3;
+    stream2_.max_buffers = 3;
+    dut_.reset(new RequestTracker());
+    streams_ = {&stream1_, &stream2_};
+    camera3_stream_configuration_t config{streams_.size(), streams_.data(), 0};
+    dut_->SetStreamConfiguration(config);
+  }
+
+  std::shared_ptr<CaptureRequest> GenerateCaptureRequest(
+      uint32_t frame, std::vector<camera3_stream_t*> streams) {
+    std::shared_ptr<CaptureRequest> request =
+        std::make_shared<CaptureRequest>();
+
+    // Set the frame number and buffers.
+    request->frame_number = frame;
+    for (const auto stream : streams) {
+      // All we really care about for the buffers is which stream they're for.
+      camera3_stream_buffer_t buffer{stream, nullptr, 0, -1, -1};
+      request->output_buffers.push_back(buffer);
+    }
+
+    return request;
+  }
+
+  void AddRequest(uint32_t frame,
+                  std::vector<camera3_stream_t*> streams,
+                  bool expected = true) {
+    std::shared_ptr<CaptureRequest> request =
+        GenerateCaptureRequest(frame, streams);
+    EXPECT_EQ(dut_->CanAddRequest(*request), expected);
+    if (expected) {
+      EXPECT_FALSE(dut_->InFlight(frame));
+    }
+    EXPECT_EQ(dut_->Add(request), expected);
+    if (expected) {
+      EXPECT_TRUE(dut_->InFlight(frame));
+    }
+  }
+
+  camera3_stream_t stream1_;
+  camera3_stream_t stream2_;
+  std::vector<camera3_stream_t*> streams_;
+  std::shared_ptr<RequestTracker> dut_;
+};
+
+TEST_F(RequestTrackerTest, AddValid) {
+  uint32_t frame = 34;
+  EXPECT_FALSE(dut_->InFlight(frame));
+  AddRequest(frame, {&stream1_});
+}
+
+TEST_F(RequestTrackerTest, AddInput) {
+  EXPECT_TRUE(dut_->Empty());
+
+  // Add a request
+  uint32_t frame = 42;
+  std::shared_ptr<CaptureRequest> expected = GenerateCaptureRequest(frame, {});
+  // Set the input buffer instead of any outputs.
+  expected->input_buffer.reset(
+      new camera3_stream_buffer_t{&stream1_, nullptr, 0, -1, -1});
+  stream1_.max_buffers = 1;
+
+  EXPECT_TRUE(dut_->Add(expected));
+  EXPECT_TRUE(dut_->InFlight(frame));
+  // Should have added to the count of buffers for stream 1.
+  EXPECT_TRUE(dut_->StreamFull(&stream1_));
+}
+
+TEST_F(RequestTrackerTest, AddMultipleStreams) {
+  stream1_.max_buffers = 1;
+  stream2_.max_buffers = 1;
+
+  EXPECT_FALSE(dut_->StreamFull(&stream1_));
+  EXPECT_FALSE(dut_->StreamFull(&stream2_));
+
+  // Add a request using both streams.
+  AddRequest(99, {&stream1_, &stream2_});
+
+  // Should both have been counted.
+  EXPECT_TRUE(dut_->StreamFull(&stream1_));
+  EXPECT_TRUE(dut_->StreamFull(&stream2_));
+}
+
+TEST_F(RequestTrackerTest, AddUnconfigured) {
+  camera3_stream_t stream;
+  // Unconfigured should be considered full.
+  EXPECT_TRUE(dut_->StreamFull(&stream));
+  AddRequest(1, {&stream}, false);
+}
+
+TEST_F(RequestTrackerTest, AddPastCapacity) {
+  // Set the limit of stream 2 to 1.
+  stream2_.max_buffers = 1;
+
+  for (size_t i = 0; i < stream1_.max_buffers; ++i) {
+    EXPECT_FALSE(dut_->StreamFull(&stream1_));
+    EXPECT_FALSE(dut_->StreamFull(&stream2_));
+    AddRequest(i, {&stream1_});
+  }
+  // Filled up stream 1.
+  EXPECT_TRUE(dut_->StreamFull(&stream1_));
+  // Stream 2 should still not be full since nothing was added.
+  EXPECT_FALSE(dut_->StreamFull(&stream2_));
+
+  // Limit has been hit, can't add more.
+  AddRequest(stream1_.max_buffers, {&stream1_, &stream2_}, false);
+  EXPECT_TRUE(dut_->StreamFull(&stream1_));
+  // Should not have added to the count of stream 2.
+  EXPECT_FALSE(dut_->StreamFull(&stream2_));
+}
+
+TEST_F(RequestTrackerTest, AddDuplicate) {
+  uint32_t frame = 42;
+  AddRequest(frame, {&stream1_});
+  // Can't add a duplicate.
+  AddRequest(frame, {&stream2_}, false);
+}
+
+TEST_F(RequestTrackerTest, RemoveValid) {
+  EXPECT_TRUE(dut_->Empty());
+
+  // Add a request
+  uint32_t frame = 42;
+  std::shared_ptr<CaptureRequest> request =
+      GenerateCaptureRequest(frame, {&stream1_});
+  EXPECT_TRUE(dut_->Add(request));
+  EXPECT_TRUE(dut_->InFlight(frame));
+  AddRequest(frame + 1, {&stream1_});
+  EXPECT_FALSE(dut_->Empty());
+
+  // Remove it.
+  EXPECT_TRUE(dut_->Remove(request));
+  // Should have removed only the desired request.
+  EXPECT_FALSE(dut_->Empty());
+}
+
+TEST_F(RequestTrackerTest, RemoveInvalidFrame) {
+  EXPECT_TRUE(dut_->Empty());
+
+  // Add a request
+  uint32_t frame = 42;
+  AddRequest(frame, {&stream1_});
+  EXPECT_FALSE(dut_->Empty());
+
+  // Try to remove a different one.
+  uint32_t bad_frame = frame + 1;
+  std::shared_ptr<CaptureRequest> bad_request =
+      GenerateCaptureRequest(bad_frame, {&stream1_});
+  EXPECT_FALSE(dut_->InFlight(bad_frame));
+  EXPECT_FALSE(dut_->Remove(bad_request));
+  EXPECT_FALSE(dut_->Empty());
+}
+
+TEST_F(RequestTrackerTest, RemoveInvalidData) {
+  EXPECT_TRUE(dut_->Empty());
+
+  // Add a request
+  uint32_t frame = 42;
+  AddRequest(frame, {&stream1_});
+  EXPECT_FALSE(dut_->Empty());
+
+  // Try to remove a different one.
+  // Even though this request looks the same, that fact that it is
+  // a pointer to a different object means it should fail.
+  std::shared_ptr<CaptureRequest> bad_request =
+      GenerateCaptureRequest(frame, {&stream1_});
+  EXPECT_TRUE(dut_->InFlight(frame));
+  EXPECT_FALSE(dut_->Remove(bad_request));
+  EXPECT_FALSE(dut_->Empty());
+}
+
+TEST_F(RequestTrackerTest, RemoveNull) {
+  EXPECT_FALSE(dut_->Remove(nullptr));
+}
+
+TEST_F(RequestTrackerTest, ClearRequests) {
+  // Create some requests.
+  uint32_t frame1 = 42;
+  uint32_t frame2 = frame1 + 1;
+  std::shared_ptr<CaptureRequest> request1 =
+      GenerateCaptureRequest(frame1, {&stream1_});
+  std::shared_ptr<CaptureRequest> request2 =
+      GenerateCaptureRequest(frame2, {&stream2_});
+  std::set<std::shared_ptr<CaptureRequest>> expected;
+  expected.insert(request1);
+  expected.insert(request2);
+
+  // Insert them.
+  EXPECT_TRUE(dut_->Add(request1));
+  EXPECT_TRUE(dut_->Add(request2));
+  EXPECT_TRUE(dut_->InFlight(frame1));
+  EXPECT_TRUE(dut_->InFlight(frame2));
+  EXPECT_FALSE(dut_->Empty());
+  std::set<std::shared_ptr<CaptureRequest>> actual;
+
+  // Clear them out.
+  dut_->Clear(&actual);
+  EXPECT_TRUE(dut_->Empty());
+  EXPECT_EQ(actual, expected);
+
+  // Configuration (max values) should not have been cleared.
+  EXPECT_TRUE(dut_->Add(request1));
+}
+
+TEST_F(RequestTrackerTest, ClearRequestsNoResult) {
+  // Add some requests.
+  EXPECT_TRUE(dut_->Empty());
+  AddRequest(1, {&stream1_});
+  AddRequest(2, {&stream2_});
+  EXPECT_FALSE(dut_->Empty());
+  // Don't bother getting the cleared requests.
+  dut_->Clear();
+  EXPECT_TRUE(dut_->Empty());
+}
+
+TEST_F(RequestTrackerTest, ClearConfiguration) {
+  EXPECT_FALSE(dut_->StreamFull(&stream1_));
+  EXPECT_FALSE(dut_->StreamFull(&stream2_));
+
+  // Clear the configuration.
+  dut_->ClearStreamConfiguration();
+
+  // Both streams should be considered full now, since neither is configured.
+  EXPECT_TRUE(dut_->StreamFull(&stream1_));
+  EXPECT_TRUE(dut_->StreamFull(&stream2_));
+}
+
+}  // namespace default_camera_hal
diff --git a/modules/camera/3_4/static_properties.cpp b/modules/camera/3_4/static_properties.cpp
new file mode 100644
index 0000000..5be9dcd
--- /dev/null
+++ b/modules/camera/3_4/static_properties.cpp
@@ -0,0 +1,494 @@
+/*
+ * 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 "static_properties.h"
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "StaticProperties"
+#include <cutils/log.h>
+#include <hardware/camera3.h>
+#include <system/camera.h>
+
+#include "metadata/metadata_reader.h"
+
+namespace default_camera_hal {
+
+// Build stream capabilities from configs + stall durations.
+static bool ConstructStreamCapabilities(
+    const std::vector<StreamConfiguration>& configs,
+    const std::vector<StreamStallDuration>& stalls,
+    StaticProperties::CapabilitiesMap* capabilities) {
+  // Extract directional capabilities from the configs.
+  for (const auto& config : configs) {
+    switch (config.direction) {
+      case ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT:
+        (*capabilities)[config.spec].output_supported = true;
+        break;
+      case ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT:
+        (*capabilities)[config.spec].input_supported = true;
+        break;
+      default:
+        // Should never happen when using the MetadataReader;
+        // it should validate directions.
+        ALOGE("%s: Unrecognized stream config direction %d.",
+              __func__,
+              config.direction);
+        return false;
+    }
+  }
+
+  // Extract stall durations from the stalls.
+  for (const auto& stall : stalls) {
+    (*capabilities)[stall.spec].stall_duration = stall.duration;
+  }
+
+  return true;
+}
+
+// Check that each output config has a valid corresponding stall duration
+// (extra durations not matching any output config are ignored).
+static bool ValidateStreamCapabilities(
+    StaticProperties::CapabilitiesMap capabilities) {
+  for (const auto& spec_capabilities : capabilities) {
+    // Only non-negative stall durations are valid. This should only happen
+    // due to output streams without an associated stall duration, as
+    // MetadataReader validates the metadata stall durations.
+    if (spec_capabilities.second.output_supported &&
+        spec_capabilities.second.stall_duration < 0) {
+      ALOGE(
+          "%s: Static metadata does not have a stall duration for "
+          "each output configuration. ",
+          __func__);
+      return false;
+    }
+  }
+  return true;
+}
+
+// Validate that the input/output formats map matches up with
+// the capabilities listed for all formats.
+bool ValidateReprocessFormats(
+    const StaticProperties::CapabilitiesMap& capabilities,
+    const ReprocessFormatMap& reprocess_map) {
+  // Get input formats.
+  std::set<int32_t> all_input_formats;
+  std::set<int32_t> all_output_formats;
+  for (const auto& spec_capabilities : capabilities) {
+    if (spec_capabilities.second.input_supported) {
+      all_input_formats.insert(spec_capabilities.first.format);
+    }
+    if (spec_capabilities.second.output_supported) {
+      all_output_formats.insert(spec_capabilities.first.format);
+    }
+  }
+
+  // Must be at least one input format.
+  if (all_input_formats.size() < 1) {
+    ALOGE("%s: No input formats, reprocessing can't be supported.", __func__);
+    return false;
+  }
+
+  // Check that the reprocess map input formats are exactly all available
+  // input formats (check size here, then checking for actual value
+  // matches will happen as part of the loop below).
+  if (all_input_formats.size() != reprocess_map.size()) {
+    ALOGE(
+        "%s: Stream configuration input formats do not match "
+        "input/output format map input formats.",
+        __func__);
+    return false;
+  }
+
+  // Check that each input format has at least one matching output format.
+  for (const auto& input_format : all_input_formats) {
+    const auto input_outputs_iterator = reprocess_map.find(input_format);
+    if (input_outputs_iterator == reprocess_map.end()) {
+      ALOGE(
+          "%s: No output formats for input format %d.", __func__, input_format);
+      return false;
+    }
+    // No need to check that the output formats vector is non-empty;
+    // MetadataReader validates this. Instead just check that
+    // all outputs are actually output formats.
+    for (const auto& output_format : input_outputs_iterator->second) {
+      if (all_output_formats.count(output_format) < 1) {
+        ALOGE(
+            "%s: Output format %d for input format %d "
+            "is not a supported output format.",
+            __func__,
+            input_format,
+            output_format);
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
+StaticProperties* StaticProperties::NewStaticProperties(
+    std::unique_ptr<const MetadataReader> metadata_reader) {
+  int facing = 0;
+  int orientation = 0;
+  int32_t max_input_streams = 0;
+  int32_t max_raw_output_streams = 0;
+  int32_t max_non_stalling_output_streams = 0;
+  int32_t max_stalling_output_streams = 0;
+  std::set<uint8_t> request_capabilities;
+  std::vector<StreamConfiguration> configs;
+  std::vector<StreamStallDuration> stalls;
+  CapabilitiesMap stream_capabilities;
+  ReprocessFormatMap reprocess_map;
+
+  // If reading any data returns an error, something is wrong.
+  if (metadata_reader->Facing(&facing) ||
+      metadata_reader->Orientation(&orientation) ||
+      metadata_reader->MaxInputStreams(&max_input_streams) ||
+      metadata_reader->MaxOutputStreams(&max_raw_output_streams,
+                                        &max_non_stalling_output_streams,
+                                        &max_stalling_output_streams) ||
+      metadata_reader->RequestCapabilities(&request_capabilities) ||
+      metadata_reader->StreamConfigurations(&configs) ||
+      metadata_reader->StreamStallDurations(&stalls) ||
+      !ConstructStreamCapabilities(configs, stalls, &stream_capabilities) ||
+      // MetadataReader validates configs and stall seperately,
+      // but not that they match.
+      !ValidateStreamCapabilities(stream_capabilities) ||
+      // Reprocessing metadata only necessary if input streams are allowed.
+      (max_input_streams > 0 &&
+       (metadata_reader->ReprocessFormats(&reprocess_map) ||
+        // MetadataReader validates configs and the reprocess map seperately,
+        // but not that they match.
+        !ValidateReprocessFormats(stream_capabilities, reprocess_map)))) {
+    return nullptr;
+  }
+
+  return new StaticProperties(std::move(metadata_reader),
+                              facing,
+                              orientation,
+                              max_input_streams,
+                              max_raw_output_streams,
+                              max_non_stalling_output_streams,
+                              max_stalling_output_streams,
+                              std::move(request_capabilities),
+                              std::move(stream_capabilities),
+                              std::move(reprocess_map));
+}
+
+StaticProperties::StaticProperties(
+    std::unique_ptr<const MetadataReader> metadata_reader,
+    int facing,
+    int orientation,
+    int32_t max_input_streams,
+    int32_t max_raw_output_streams,
+    int32_t max_non_stalling_output_streams,
+    int32_t max_stalling_output_streams,
+    std::set<uint8_t> request_capabilities,
+    CapabilitiesMap stream_capabilities,
+    ReprocessFormatMap supported_reprocess_outputs)
+    : metadata_reader_(std::move(metadata_reader)),
+      facing_(facing),
+      orientation_(orientation),
+      max_input_streams_(max_input_streams),
+      max_raw_output_streams_(max_raw_output_streams),
+      max_non_stalling_output_streams_(max_non_stalling_output_streams),
+      max_stalling_output_streams_(max_stalling_output_streams),
+      request_capabilities_(std::move(request_capabilities)),
+      stream_capabilities_(std::move(stream_capabilities)),
+      supported_reprocess_outputs_(std::move(supported_reprocess_outputs)) {}
+
+bool StaticProperties::TemplateSupported(int type) {
+  uint8_t required_capability = 0;
+  switch (type) {
+    case CAMERA3_TEMPLATE_PREVIEW:
+      // Preview is always supported.
+      return true;
+    case CAMERA3_TEMPLATE_MANUAL:
+      required_capability =
+          ANDROID_REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR;
+      break;
+    case CAMERA3_TEMPLATE_ZERO_SHUTTER_LAG:
+      required_capability =
+          ANDROID_REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING;
+      break;
+    default:
+      required_capability =
+          ANDROID_REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE;
+      return true;
+  }
+
+  return request_capabilities_.count(required_capability) > 0;
+}
+
+// Helper functions for checking stream properties when verifying support.
+static bool IsInputType(int stream_type) {
+  return stream_type == CAMERA3_STREAM_INPUT ||
+         stream_type == CAMERA3_STREAM_BIDIRECTIONAL;
+}
+
+static bool IsOutputType(int stream_type) {
+  return stream_type == CAMERA3_STREAM_OUTPUT ||
+         stream_type == CAMERA3_STREAM_BIDIRECTIONAL;
+}
+
+static bool IsRawFormat(int format) {
+  return format == HAL_PIXEL_FORMAT_RAW10 || format == HAL_PIXEL_FORMAT_RAW12 ||
+         format == HAL_PIXEL_FORMAT_RAW16 ||
+         format == HAL_PIXEL_FORMAT_RAW_OPAQUE;
+}
+
+bool StaticProperties::StreamConfigurationSupported(
+    const camera3_stream_configuration_t* stream_config) {
+  return SanityCheckStreamConfiguration(stream_config) &&
+         InputStreamsSupported(stream_config) &&
+         OutputStreamsSupported(stream_config) &&
+         OperationModeSupported(stream_config);
+}
+
+bool StaticProperties::SanityCheckStreamConfiguration(
+    const camera3_stream_configuration_t* stream_config) {
+  // Check for null/empty values.
+  if (stream_config == nullptr) {
+    ALOGE("%s: NULL stream configuration array", __func__);
+    return false;
+  } else if (stream_config->num_streams == 0) {
+    ALOGE("%s: Empty stream configuration array", __func__);
+    return false;
+  } else if (stream_config->streams == nullptr) {
+    ALOGE("%s: NULL stream configuration streams", __func__);
+    return false;
+  }
+
+  // Check that all streams are either inputs or outputs (or both).
+  for (size_t i = 0; i < stream_config->num_streams; ++i) {
+    const camera3_stream_t* stream = stream_config->streams[i];
+    if (stream == nullptr) {
+      ALOGE("%s: Stream %d is null", __func__, i);
+      return false;
+    } else if (!IsInputType(stream->stream_type) &&
+               !IsOutputType(stream->stream_type)) {
+      ALOGE("%s: Stream %d type %d is neither an input nor an output type",
+            __func__,
+            i,
+            stream->stream_type);
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool StaticProperties::InputStreamsSupported(
+    const camera3_stream_configuration_t* stream_config) {
+  // Find the input stream(s).
+  size_t num_input_streams = 0;
+  int input_format = -1;
+  for (size_t i = 0; i < stream_config->num_streams; ++i) {
+    const camera3_stream_t* stream = stream_config->streams[i];
+    if (IsInputType(stream->stream_type)) {
+      // Check that this stream is valid as an input.
+      const auto capabilities_iterator = stream_capabilities_.find(stream);
+      if (capabilities_iterator == stream_capabilities_.end() ||
+          !capabilities_iterator->second.input_supported) {
+        ALOGE("%s: %d x %d stream of format %d is not a supported input setup.",
+              __func__,
+              stream->width,
+              stream->height,
+              stream->format);
+        return false;
+      }
+
+      // Valid input stream; count it.
+      ++num_input_streams;
+      input_format = stream->format;
+    }
+  }
+
+  // Check the count.
+  if (num_input_streams > max_input_streams_) {
+    ALOGE(
+        "%s: Requested number of input streams %d is greater than "
+        "the maximum number supported by the device (%d).",
+        __func__,
+        num_input_streams,
+        max_input_streams_);
+    return false;
+  }
+  if (num_input_streams > 1) {
+    ALOGE("%s: Camera HAL 3.4 only supports 1 input stream max.", __func__);
+    return false;
+  }
+
+  // If there's an input stream, the configuration must have at least one
+  // supported output format for reprocessing that input.
+  if (num_input_streams > 0) {
+    const auto input_output_formats_iterator =
+        supported_reprocess_outputs_.find(input_format);
+    if (input_output_formats_iterator == supported_reprocess_outputs_.end()) {
+      // Should never happen; factory should verify that all valid inputs
+      // have one or more valid outputs.
+      ALOGE("%s: No valid output formats for input format %d.",
+            __func__,
+            input_format);
+      return false;
+    }
+    bool match_found = false;
+    // Go through outputs looking for a supported one.
+    for (size_t i = 0; i < stream_config->num_streams; ++i) {
+      const camera3_stream_t* stream = stream_config->streams[i];
+      if (IsOutputType(stream->stream_type)) {
+        if (input_output_formats_iterator->second.count(stream->format) > 0) {
+          match_found = true;
+          break;
+        }
+      }
+    }
+    if (!match_found) {
+      ALOGE("%s: No supported output format provided for input format %d.",
+            __func__,
+            input_format);
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool StaticProperties::OutputStreamsSupported(
+    const camera3_stream_configuration_t* stream_config) {
+  // Find and count output streams.
+  size_t num_raw = 0;
+  size_t num_stalling = 0;
+  size_t num_non_stalling = 0;
+  for (int i = 0; i < stream_config->num_streams; ++i) {
+    const camera3_stream_t* stream = stream_config->streams[i];
+    if (IsOutputType(stream->stream_type)) {
+      // Check that this stream is valid as an output.
+      const auto capabilities_iterator = stream_capabilities_.find(stream);
+      if (capabilities_iterator == stream_capabilities_.end() ||
+          !capabilities_iterator->second.output_supported) {
+        ALOGE(
+            "%s: %d x %d stream of format %d "
+            "is not a supported output setup.",
+            __func__,
+            stream->width,
+            stream->height,
+            stream->format);
+        return false;
+      }
+
+      // Valid output; count it.
+      if (IsRawFormat(stream->format)) {
+        ++num_raw;
+      } else if (capabilities_iterator->second.stall_duration > 0) {
+        ++num_stalling;
+      } else {
+        ++num_non_stalling;
+      }
+    }
+  }
+
+  // Check that the counts are within bounds.
+  if (num_raw > max_raw_output_streams_) {
+    ALOGE(
+        "%s: Requested stream configuration exceeds maximum supported "
+        "raw output streams %d (requested %d).",
+        __func__,
+        max_raw_output_streams_,
+        num_raw);
+    return false;
+  } else if (num_stalling > max_stalling_output_streams_) {
+    ALOGE(
+        "%s: Requested stream configuration exceeds maximum supported "
+        "stalling output streams %d (requested %d).",
+        __func__,
+        max_stalling_output_streams_,
+        num_stalling);
+    return false;
+  } else if (num_non_stalling > max_non_stalling_output_streams_) {
+    ALOGE(
+        "%s: Requested stream configuration exceeds maximum supported "
+        "non-stalling output streams %d (requested %d).",
+        __func__,
+        max_non_stalling_output_streams_,
+        num_non_stalling);
+    return false;
+  }
+
+  return true;
+}
+
+bool StaticProperties::OperationModeSupported(
+    const camera3_stream_configuration_t* stream_config) {
+  switch (stream_config->operation_mode) {
+    case CAMERA3_STREAM_CONFIGURATION_NORMAL_MODE:
+      return true;
+    case CAMERA3_STREAM_CONFIGURATION_CONSTRAINED_HIGH_SPEED_MODE:
+      // TODO(b/31370792): Check metadata for high speed support,
+      // check that requested streams have support for high speed.
+      ALOGE("%s: Support for CONSTRAINED_HIGH_SPEED not implemented", __func__);
+      return false;
+    default:
+      ALOGE("%s: Unrecognized stream configuration mode: %d",
+            __func__,
+            stream_config->operation_mode);
+      return false;
+  }
+}
+
+bool StaticProperties::ReprocessingSupported(
+    const camera3_stream_t* input_stream,
+    const std::set<const camera3_stream_t*>& output_streams) {
+  // There must be an input.
+  if (!input_stream) {
+    ALOGE("%s: No input stream.", __func__);
+    return false;
+  }
+  // There must be an output.
+  if (output_streams.size() < 1) {
+    ALOGE("%s: No output stream.", __func__);
+    return false;
+  }
+
+  const auto input_output_formats =
+      supported_reprocess_outputs_.find(input_stream->format);
+  if (input_output_formats == supported_reprocess_outputs_.end()) {
+    // Should never happen for a valid input stream.
+    ALOGE("%s: Input format %d does not support any output formats.",
+          __func__,
+          input_stream->format);
+    return false;
+  }
+
+  // Check that all output streams can be outputs for the input stream.
+  const std::set<int32_t>& supported_output_formats =
+      input_output_formats->second;
+  for (const auto output_stream : output_streams) {
+    if (supported_output_formats.count(output_stream->format) < 1) {
+      ALOGE(
+          "%s: Output format %d is not a supported output "
+          "for request input format %d.",
+          __func__,
+          output_stream->format,
+          input_stream->format);
+      return false;
+    }
+  }
+
+  return true;
+}
+
+}  // namespace default_camera_hal
diff --git a/modules/camera/3_4/static_properties.h b/modules/camera/3_4/static_properties.h
new file mode 100644
index 0000000..25c8205
--- /dev/null
+++ b/modules/camera/3_4/static_properties.h
@@ -0,0 +1,121 @@
+/*
+ * 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 DEFAULT_CAMERA_HAL_STATIC_PROPERTIES_H_
+#define DEFAULT_CAMERA_HAL_STATIC_PROPERTIES_H_
+
+#include <memory>
+#include <set>
+
+#include <hardware/camera3.h>
+
+#include "common.h"
+#include "metadata/metadata_reader.h"
+#include "metadata/types.h"
+
+namespace default_camera_hal {
+
+// StaticProperties provides a wrapper around useful static metadata entries.
+class StaticProperties {
+ public:
+  // Helpful types for interpreting some static properties.
+  struct StreamCapabilities {
+    int64_t stall_duration;
+    int32_t input_supported;
+    int32_t output_supported;
+    // Default constructor ensures no support
+    // and an invalid stall duration.
+    StreamCapabilities()
+        : stall_duration(-1), input_supported(0), output_supported(0) {}
+  };
+  // Map stream spec (format, size) to their
+  // capabilities (input, output, stall).
+  typedef std::map<StreamSpec, StreamCapabilities, StreamSpec::Compare>
+      CapabilitiesMap;
+
+  // Use this method to create StaticProperties objects.
+  // Functionally equivalent to "new StaticProperties",
+  // except that it may return nullptr in case of failure (missing entries).
+  static StaticProperties* NewStaticProperties(
+      std::unique_ptr<const MetadataReader> metadata_reader);
+  static StaticProperties* NewStaticProperties(
+      std::unique_ptr<android::CameraMetadata> metadata) {
+    return NewStaticProperties(
+        std::make_unique<MetadataReader>(std::move(metadata)));
+  }
+  virtual ~StaticProperties(){};
+
+  // Simple accessors.
+  int facing() const { return facing_; };
+  int orientation() const { return orientation_; };
+  // Carrying on the promise of the underlying reader,
+  // the returned pointer is valid only as long as this object is alive.
+  const camera_metadata_t* raw_metadata() const {
+    return metadata_reader_->raw_metadata();
+  };
+
+  // Check if a given template type is supported.
+  bool TemplateSupported(int type);
+  // Validators (check that values are consistent with the capabilities
+  // this object represents/base requirements of the camera HAL).
+  bool StreamConfigurationSupported(
+      const camera3_stream_configuration_t* stream_config);
+  // Check that the inputs and outputs for a request don't conflict.
+  bool ReprocessingSupported(
+      const camera3_stream_t* input_stream,
+      const std::set<const camera3_stream_t*>& output_streams);
+
+ private:
+  // Constructor private to allow failing on bad input.
+  // Use NewStaticProperties instead.
+  StaticProperties(std::unique_ptr<const MetadataReader> metadata_reader,
+                   int facing,
+                   int orientation,
+                   int32_t max_input_streams,
+                   int32_t max_raw_output_streams,
+                   int32_t max_non_stalling_output_streams,
+                   int32_t max_stalling_output_streams,
+                   std::set<uint8_t> request_capabilities,
+                   CapabilitiesMap stream_capabilities,
+                   ReprocessFormatMap supported_reprocess_outputs);
+
+  // Helper functions for StreamConfigurationSupported.
+  bool SanityCheckStreamConfiguration(
+      const camera3_stream_configuration_t* stream_config);
+  bool InputStreamsSupported(
+      const camera3_stream_configuration_t* stream_config);
+  bool OutputStreamsSupported(
+      const camera3_stream_configuration_t* stream_config);
+  bool OperationModeSupported(
+      const camera3_stream_configuration_t* stream_config);
+
+  const std::unique_ptr<const MetadataReader> metadata_reader_;
+  const int facing_;
+  const int orientation_;
+  const int32_t max_input_streams_;
+  const int32_t max_raw_output_streams_;
+  const int32_t max_non_stalling_output_streams_;
+  const int32_t max_stalling_output_streams_;
+  const std::set<uint8_t> request_capabilities_;
+  const CapabilitiesMap stream_capabilities_;
+  const ReprocessFormatMap supported_reprocess_outputs_;
+
+  DISALLOW_COPY_AND_ASSIGN(StaticProperties);
+};
+
+}  // namespace default_camera_hal
+
+#endif  // DEFAULT_CAMERA_HAL_STATIC_PROPERTIES_H_
diff --git a/modules/camera/3_4/static_properties_test.cpp b/modules/camera/3_4/static_properties_test.cpp
new file mode 100644
index 0000000..e78e343
--- /dev/null
+++ b/modules/camera/3_4/static_properties_test.cpp
@@ -0,0 +1,667 @@
+/*
+ * 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 "static_properties.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <hardware/camera3.h>
+#include <system/camera.h>
+
+#include "metadata/metadata_reader_mock.h"
+
+using testing::AtMost;
+using testing::Expectation;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::Test;
+using testing::_;
+
+namespace default_camera_hal {
+
+class StaticPropertiesTest : public Test {
+ protected:
+  virtual void SetUp() {
+    // Ensure tests will probably fail if PrepareDUT isn't called.
+    dut_.reset();
+    mock_reader_ = std::make_unique<MetadataReaderMock>();
+  }
+
+  void PrepareDUT() {
+    dut_.reset(StaticProperties::NewStaticProperties(std::move(mock_reader_)));
+  }
+
+  void PrepareDefaultDUT() {
+    SetDefaultExpectations();
+    PrepareDUT();
+    ASSERT_NE(dut_, nullptr);
+  }
+
+  void SetDefaultExpectations() {
+    EXPECT_CALL(*mock_reader_, Facing(_))
+        .Times(AtMost(1))
+        .WillOnce(DoAll(SetArgPointee<0>(test_facing_), Return(0)));
+    EXPECT_CALL(*mock_reader_, Orientation(_))
+        .Times(AtMost(1))
+        .WillOnce(DoAll(SetArgPointee<0>(test_orientation_), Return(0)));
+    EXPECT_CALL(*mock_reader_, MaxInputStreams(_))
+        .Times(AtMost(1))
+        .WillOnce(DoAll(SetArgPointee<0>(test_max_inputs_), Return(0)));
+    EXPECT_CALL(*mock_reader_, MaxOutputStreams(_, _, _))
+        .Times(AtMost(1))
+        .WillOnce(DoAll(SetArgPointee<0>(test_max_raw_outputs_),
+                        SetArgPointee<1>(test_max_non_stalling_outputs_),
+                        SetArgPointee<2>(test_max_stalling_outputs_),
+                        Return(0)));
+    EXPECT_CALL(*mock_reader_, RequestCapabilities(_))
+        .Times(AtMost(1))
+        .WillOnce(
+            DoAll(SetArgPointee<0>(test_request_capabilities_), Return(0)));
+    EXPECT_CALL(*mock_reader_, StreamConfigurations(_))
+        .Times(AtMost(1))
+        .WillOnce(DoAll(SetArgPointee<0>(test_configs_), Return(0)));
+    EXPECT_CALL(*mock_reader_, StreamStallDurations(_))
+        .Times(AtMost(1))
+        .WillOnce(DoAll(SetArgPointee<0>(test_stalls_), Return(0)));
+    EXPECT_CALL(*mock_reader_, ReprocessFormats(_))
+        .Times(AtMost(1))
+        .WillOnce(DoAll(SetArgPointee<0>(test_reprocess_map_), Return(0)));
+  }
+
+  camera3_stream_t MakeStream(int32_t format,
+                              bool output = true,
+                              bool input = false,
+                              int32_t width = kWidth,
+                              int32_t height = kHeight) {
+    int type = -1;
+    if (output && input) {
+      type = CAMERA3_STREAM_BIDIRECTIONAL;
+    } else if (output) {
+      type = CAMERA3_STREAM_OUTPUT;
+    } else if (input) {
+      type = CAMERA3_STREAM_INPUT;
+    }
+    return {static_cast<int>(type),
+            static_cast<uint32_t>(width),
+            static_cast<uint32_t>(height),
+            static_cast<int>(format)};
+  }
+
+  void ExpectConfigurationSupported(std::vector<camera3_stream_t>& streams,
+                                    bool expected) {
+    std::vector<camera3_stream_t*> stream_addresses;
+    for (size_t i = 0; i < streams.size(); ++i) {
+      stream_addresses.push_back(&streams[i]);
+    }
+    camera3_stream_configuration_t config = {
+        stream_addresses.size(),
+        stream_addresses.data(),
+        CAMERA3_STREAM_CONFIGURATION_NORMAL_MODE};
+    PrepareDefaultDUT();
+    EXPECT_EQ(dut_->StreamConfigurationSupported(&config), expected);
+  }
+
+  std::unique_ptr<StaticProperties> dut_;
+  std::unique_ptr<MetadataReaderMock> mock_reader_;
+
+  // Some helper values used for stream testing.
+  static constexpr int32_t kWidth = 320;
+  static constexpr int32_t kHeight = 240;
+  static constexpr int32_t kAlternateWidth = 640;
+  static constexpr int32_t kAlternateHeight = 480;
+
+  const int test_facing_ = CAMERA_FACING_FRONT;
+  const int test_orientation_ = 90;
+  const int32_t test_max_inputs_ = 3;
+  const int32_t test_max_raw_outputs_ = 1;
+  const int32_t test_max_non_stalling_outputs_ = 2;
+  const int32_t test_max_stalling_outputs_ = 3;
+  const std::set<uint8_t> test_request_capabilities_ = {
+      ANDROID_REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE,
+      ANDROID_REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR,
+      ANDROID_REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING};
+
+  // Some formats for various purposes (in various combinations,
+  // these types should be capable of testing all failure conditions).
+  const int32_t output_multisize_non_stalling_ = 1;
+  const int32_t bidirectional_self_supporting_stalling_ = 2;
+  const int32_t bidirectional_raw_ = HAL_PIXEL_FORMAT_RAW10;
+  const int32_t input_ = 3;
+  const int32_t other = input_;
+
+  const std::vector<StreamConfiguration> test_configs_ = {
+      {{{output_multisize_non_stalling_,
+         kWidth,
+         kHeight,
+         ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT}}},
+      {{{output_multisize_non_stalling_,
+         kAlternateWidth,
+         kAlternateHeight,
+         ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT}}},
+      {{{bidirectional_self_supporting_stalling_,
+         kWidth,
+         kHeight,
+         ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT}}},
+      {{{bidirectional_self_supporting_stalling_,
+         kWidth,
+         kHeight,
+         ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT}}},
+      {{{bidirectional_raw_,
+         kWidth,
+         kHeight,
+         ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT}}},
+      {{{bidirectional_raw_,
+         kWidth,
+         kHeight,
+         ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT}}},
+      {{{input_,
+         kWidth,
+         kHeight,
+         ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT}}}};
+  // Raw having a stall duration shouldn't matter,
+  // it should still be counted as the raw type.
+  const std::vector<StreamStallDuration> test_stalls_ = {
+      {{{output_multisize_non_stalling_, kWidth, kHeight, 0}}},
+      {{{output_multisize_non_stalling_,
+         kAlternateWidth,
+         kAlternateHeight,
+         0}}},
+      {{{bidirectional_self_supporting_stalling_, kWidth, kHeight, 10}}},
+      {{{bidirectional_raw_, kWidth, kHeight, 15}}}};
+  // Format 2 can go to itself or 1. 3 and RAW can only go to 1.
+  const ReprocessFormatMap test_reprocess_map_ = {
+      {bidirectional_self_supporting_stalling_,
+       {output_multisize_non_stalling_,
+        bidirectional_self_supporting_stalling_}},
+      {bidirectional_raw_, {output_multisize_non_stalling_}},
+      {input_, {output_multisize_non_stalling_}}};
+  // Codify the above information about format capabilities in some helpful
+  // vectors.
+  int32_t multi_size_format_ = 1;
+  const std::vector<int32_t> input_formats_ = {2, 3, HAL_PIXEL_FORMAT_RAW10};
+  const std::vector<int32_t> output_formats_ = {1, 2, HAL_PIXEL_FORMAT_RAW10};
+};
+
+TEST_F(StaticPropertiesTest, FactorySuccess) {
+  PrepareDefaultDUT();
+  EXPECT_EQ(dut_->facing(), test_facing_);
+  EXPECT_EQ(dut_->orientation(), test_orientation_);
+
+  // Stream configurations tested seperately.
+}
+
+TEST_F(StaticPropertiesTest, FactoryFailedFacing) {
+  SetDefaultExpectations();
+  // Override with a failure expectation.
+  EXPECT_CALL(*mock_reader_, Facing(_)).WillOnce(Return(99));
+  PrepareDUT();
+  EXPECT_EQ(dut_, nullptr);
+}
+
+TEST_F(StaticPropertiesTest, FactoryFailedOrientation) {
+  SetDefaultExpectations();
+  // Override with a failure expectation.
+  EXPECT_CALL(*mock_reader_, Orientation(_)).WillOnce(Return(99));
+  PrepareDUT();
+  EXPECT_EQ(dut_, nullptr);
+}
+
+TEST_F(StaticPropertiesTest, FactoryFailedMaxInputs) {
+  SetDefaultExpectations();
+  // Override with a failure expectation.
+  EXPECT_CALL(*mock_reader_, MaxInputStreams(_)).WillOnce(Return(99));
+  PrepareDUT();
+  EXPECT_EQ(dut_, nullptr);
+}
+
+TEST_F(StaticPropertiesTest, FactoryFailedMaxOutputs) {
+  SetDefaultExpectations();
+  // Override with a failure expectation.
+  EXPECT_CALL(*mock_reader_, MaxOutputStreams(_, _, _)).WillOnce(Return(99));
+  PrepareDUT();
+  EXPECT_EQ(dut_, nullptr);
+}
+
+TEST_F(StaticPropertiesTest, FactoryFailedRequestCapabilities) {
+  SetDefaultExpectations();
+  // Override with a failure expectation.
+  EXPECT_CALL(*mock_reader_, RequestCapabilities(_)).WillOnce(Return(99));
+  PrepareDUT();
+  EXPECT_EQ(dut_, nullptr);
+}
+
+TEST_F(StaticPropertiesTest, FactoryFailedStreamConfigs) {
+  SetDefaultExpectations();
+  // Override with a failure expectation.
+  EXPECT_CALL(*mock_reader_, StreamConfigurations(_)).WillOnce(Return(99));
+  PrepareDUT();
+  EXPECT_EQ(dut_, nullptr);
+}
+
+TEST_F(StaticPropertiesTest, FactoryFailedStallDurations) {
+  SetDefaultExpectations();
+  // Override with a failure expectation.
+  EXPECT_CALL(*mock_reader_, StreamStallDurations(_)).WillOnce(Return(99));
+  PrepareDUT();
+  EXPECT_EQ(dut_, nullptr);
+}
+
+TEST_F(StaticPropertiesTest, FactoryFailedReprocessFormats) {
+  SetDefaultExpectations();
+  // Override with a failure expectation.
+  EXPECT_CALL(*mock_reader_, ReprocessFormats(_)).WillOnce(Return(99));
+  PrepareDUT();
+  EXPECT_EQ(dut_, nullptr);
+}
+
+TEST_F(StaticPropertiesTest, FactoryNoReprocessFormats) {
+  // If there are no inputs allowed, the reprocess formats shouldn't matter.
+  SetDefaultExpectations();
+  // Override max inputs.
+  EXPECT_CALL(*mock_reader_, MaxInputStreams(_))
+      .WillOnce(DoAll(SetArgPointee<0>(0), Return(0)));
+  // Override reprocess formats with a failure expectation.
+  EXPECT_CALL(*mock_reader_, ReprocessFormats(_))
+      .Times(AtMost(1))
+      .WillOnce(Return(99));
+  PrepareDUT();
+  // Should be ok.
+  EXPECT_NE(dut_, nullptr);
+}
+
+TEST_F(StaticPropertiesTest, FactoryInvalidCapabilities) {
+  SetDefaultExpectations();
+  // Override configs with an extra output format.
+  std::vector<StreamConfiguration> configs = test_configs_;
+  configs.push_back(
+      {{{5,
+         kWidth,
+         kHeight,
+         ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT}}});
+  EXPECT_CALL(*mock_reader_, StreamConfigurations(_))
+      .WillOnce(DoAll(SetArgPointee<0>(configs), Return(0)));
+  PrepareDUT();
+  // Should fail because not every output has a stall.
+  EXPECT_EQ(dut_, nullptr);
+}
+
+TEST_F(StaticPropertiesTest, InvalidReprocessNoInputs) {
+  SetDefaultExpectations();
+  // Override configs by removing all inputs.
+  std::vector<StreamConfiguration> configs = test_configs_;
+  for (auto it = configs.begin(); it != configs.end();) {
+    if ((*it).direction ==
+        ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT) {
+      it = configs.erase(it);
+    } else {
+      ++it;
+    }
+  }
+  EXPECT_CALL(*mock_reader_, StreamConfigurations(_))
+      .WillOnce(DoAll(SetArgPointee<0>(configs), Return(0)));
+  PrepareDUT();
+  // Should fail because inputs are supported but there are no input formats.
+  EXPECT_EQ(dut_, nullptr);
+}
+
+TEST_F(StaticPropertiesTest, InvalidReprocessExtraInput) {
+  SetDefaultExpectations();
+  // Override configs with an extra input format.
+  std::vector<StreamConfiguration> configs = test_configs_;
+  configs.push_back({{{5,
+                       kWidth,
+                       kHeight,
+                       ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT}}});
+  EXPECT_CALL(*mock_reader_, StreamConfigurations(_))
+      .WillOnce(DoAll(SetArgPointee<0>(configs), Return(0)));
+  PrepareDUT();
+  // Should fail because no reprocess outputs are listed for the extra input.
+  EXPECT_EQ(dut_, nullptr);
+}
+
+TEST_F(StaticPropertiesTest, InvalidReprocessExtraMapEntry) {
+  SetDefaultExpectations();
+  // Override the reprocess map with an extra entry.
+  ReprocessFormatMap reprocess_map = test_reprocess_map_;
+  reprocess_map[5] = {1};
+  EXPECT_CALL(*mock_reader_, ReprocessFormats(_))
+      .WillOnce(DoAll(SetArgPointee<0>(reprocess_map), Return(0)));
+  PrepareDUT();
+  // Should fail because the extra map entry doesn't correspond to an input.
+  EXPECT_EQ(dut_, nullptr);
+}
+
+TEST_F(StaticPropertiesTest, InvalidReprocessWrongMapEntries) {
+  SetDefaultExpectations();
+  // Override the reprocess map replacing the entry for the
+  // input-only format with the output-only format.
+  ReprocessFormatMap reprocess_map = test_reprocess_map_;
+  reprocess_map.erase(input_);
+  reprocess_map[output_multisize_non_stalling_] = {1};
+  EXPECT_CALL(*mock_reader_, ReprocessFormats(_))
+      .WillOnce(DoAll(SetArgPointee<0>(reprocess_map), Return(0)));
+  PrepareDUT();
+  // Should fail because not all input formats are present/
+  // one of the map "input" formats is output only.
+  EXPECT_EQ(dut_, nullptr);
+}
+
+TEST_F(StaticPropertiesTest, InvalidReprocessNotAnOutput) {
+  SetDefaultExpectations();
+  // Override the reprocess map with a non-output output entry.
+  ReprocessFormatMap reprocess_map = test_reprocess_map_;
+  reprocess_map[input_].insert(input_);
+  EXPECT_CALL(*mock_reader_, ReprocessFormats(_))
+      .WillOnce(DoAll(SetArgPointee<0>(reprocess_map), Return(0)));
+  PrepareDUT();
+  // Should fail because a specified output format doesn't support output.
+  EXPECT_EQ(dut_, nullptr);
+}
+
+TEST_F(StaticPropertiesTest, TemplatesValid) {
+  PrepareDefaultDUT();
+  for (int i = 1; i < CAMERA3_TEMPLATE_COUNT; ++i) {
+    EXPECT_TRUE(dut_->TemplateSupported(i));
+  }
+}
+
+TEST_F(StaticPropertiesTest, ConfigureSingleOutput) {
+  std::vector<camera3_stream_t> streams;
+  streams.push_back(MakeStream(output_multisize_non_stalling_));
+  ExpectConfigurationSupported(streams, true);
+}
+
+TEST_F(StaticPropertiesTest, ConfigureMultipleOutputs) {
+  std::vector<camera3_stream_t> streams;
+  // 2 outputs, of different sizes.
+  streams.push_back(MakeStream(bidirectional_raw_));
+  // Use the alternate size.
+  streams.push_back(MakeStream(output_multisize_non_stalling_,
+                               true,
+                               false,
+                               kAlternateWidth,
+                               kAlternateHeight));
+  ExpectConfigurationSupported(streams, true);
+}
+
+TEST_F(StaticPropertiesTest, ConfigureInput) {
+  std::vector<camera3_stream_t> streams;
+  // Single input -> different output.
+  streams.push_back(MakeStream(input_, false, true));
+  // Use the alternate size, it should be ok.
+  streams.push_back(MakeStream(output_multisize_non_stalling_,
+                               true,
+                               false,
+                               kAlternateWidth,
+                               kAlternateHeight));
+  ExpectConfigurationSupported(streams, true);
+}
+
+TEST_F(StaticPropertiesTest, ConfigureBidirectional) {
+  std::vector<camera3_stream_t> streams;
+  // Single input -> same output.
+  streams.push_back(
+      MakeStream(bidirectional_self_supporting_stalling_, true, true));
+  ExpectConfigurationSupported(streams, true);
+}
+
+TEST_F(StaticPropertiesTest, ConfigureMultipleReprocess) {
+  std::vector<camera3_stream_t> streams;
+  // Single input -> multiple outputs.
+  streams.push_back(
+      MakeStream(bidirectional_self_supporting_stalling_, true, true));
+  streams.push_back(MakeStream(output_multisize_non_stalling_));
+  ExpectConfigurationSupported(streams, true);
+}
+
+TEST_F(StaticPropertiesTest, ConfigureNull) {
+  PrepareDefaultDUT();
+  EXPECT_FALSE(dut_->StreamConfigurationSupported(nullptr));
+}
+
+TEST_F(StaticPropertiesTest, ConfigureEmptyStreams) {
+  std::vector<camera3_stream_t*> streams(1);
+  camera3_stream_configuration_t config = {
+      0, streams.data(), CAMERA3_STREAM_CONFIGURATION_NORMAL_MODE};
+  PrepareDefaultDUT();
+  EXPECT_FALSE(dut_->StreamConfigurationSupported(&config));
+}
+
+TEST_F(StaticPropertiesTest, ConfigureNullStreams) {
+  std::vector<camera3_stream_t*> streams(2, nullptr);
+  camera3_stream_configuration_t config = {
+      streams.size(), streams.data(), CAMERA3_STREAM_CONFIGURATION_NORMAL_MODE};
+  PrepareDefaultDUT();
+  EXPECT_FALSE(dut_->StreamConfigurationSupported(&config));
+}
+
+TEST_F(StaticPropertiesTest, ConfigureNullStreamVector) {
+  // Even if the camera claims to have multiple streams, check for null.
+  camera3_stream_configuration_t config = {
+      3, nullptr, CAMERA3_STREAM_CONFIGURATION_NORMAL_MODE};
+  PrepareDefaultDUT();
+  EXPECT_FALSE(dut_->StreamConfigurationSupported(&config));
+}
+
+TEST_F(StaticPropertiesTest, ConfigureNoOutput) {
+  std::vector<camera3_stream_t> streams;
+  // Only an input stream, no output.
+  streams.push_back(MakeStream(input_, false, true));
+  ExpectConfigurationSupported(streams, false);
+}
+
+TEST_F(StaticPropertiesTest, ConfigureInvalidType) {
+  std::vector<camera3_stream_t> streams;
+  // Not input, output, or bidirectional.
+  streams.push_back(MakeStream(output_multisize_non_stalling_, false, false));
+  ExpectConfigurationSupported(streams, false);
+}
+
+TEST_F(StaticPropertiesTest, ConfigureSpecFormatDoesNotExist) {
+  std::vector<camera3_stream_t> streams;
+  // Format 99 is not supported in any form.
+  streams.push_back(MakeStream(99));
+  ExpectConfigurationSupported(streams, false);
+}
+
+TEST_F(StaticPropertiesTest, ConfigureSpecSizeDoesNotExist) {
+  std::vector<camera3_stream_t> streams;
+  // Size 99 x 99 not supported for the output format.
+  streams.push_back(
+      MakeStream(output_multisize_non_stalling_, true, false, 99, 99));
+  ExpectConfigurationSupported(streams, false);
+}
+
+TEST_F(StaticPropertiesTest, ConfigureNotAnInput) {
+  std::vector<camera3_stream_t> streams;
+  streams.push_back(MakeStream(output_multisize_non_stalling_));
+  // Can't use output-only format as an input.
+  streams.push_back(MakeStream(output_multisize_non_stalling_, false, true));
+  ExpectConfigurationSupported(streams, false);
+}
+
+TEST_F(StaticPropertiesTest, ConfigureNotAnOutput) {
+  std::vector<camera3_stream_t> streams;
+  // Can't use input-only format as an output.
+  streams.push_back(MakeStream(input_));
+  ExpectConfigurationSupported(streams, false);
+}
+
+TEST_F(StaticPropertiesTest, ConfigureTooManyInputs) {
+  std::vector<camera3_stream_t> streams;
+  // At the threshold is ok.
+  for (int32_t i = 0; i < test_max_inputs_; ++i) {
+    streams.push_back(MakeStream(input_, false, true));
+  }
+  // Have a valid output still.
+  streams.push_back(MakeStream(output_multisize_non_stalling_));
+  ExpectConfigurationSupported(streams, false);
+
+  // Reset.
+  mock_reader_ = std::make_unique<MetadataReaderMock>();
+  streams.clear();
+
+  // Try again with too many.
+  for (int32_t i = 0; i <= test_max_inputs_; ++i) {
+    streams.push_back(MakeStream(input_, false, true));
+  }
+  // Have a valid output still.
+  streams.push_back(MakeStream(output_multisize_non_stalling_));
+  ExpectConfigurationSupported(streams, false);
+}
+
+TEST_F(StaticPropertiesTest, ConfigureTooManyRaw) {
+  std::vector<camera3_stream_t> streams;
+  // At the threshold is ok.
+  for (int32_t i = 0; i < test_max_raw_outputs_; ++i) {
+    streams.push_back(MakeStream(bidirectional_raw_));
+  }
+  ExpectConfigurationSupported(streams, true);
+
+  // Reset.
+  mock_reader_ = std::make_unique<MetadataReaderMock>();
+  streams.clear();
+
+  // Try again with too many.
+  for (int32_t i = 0; i <= test_max_raw_outputs_; ++i) {
+    streams.push_back(MakeStream(bidirectional_raw_));
+  }
+  ExpectConfigurationSupported(streams, false);
+}
+
+TEST_F(StaticPropertiesTest, ConfigureTooManyStalling) {
+  std::vector<camera3_stream_t> streams;
+  // At the threshold is ok.
+  for (int32_t i = 0; i < test_max_stalling_outputs_; ++i) {
+    streams.push_back(MakeStream(bidirectional_self_supporting_stalling_));
+  }
+  ExpectConfigurationSupported(streams, true);
+
+  // Reset.
+  mock_reader_ = std::make_unique<MetadataReaderMock>();
+  streams.clear();
+
+  // Try again with too many.
+  for (int32_t i = 0; i <= test_max_stalling_outputs_; ++i) {
+    streams.push_back(MakeStream(bidirectional_self_supporting_stalling_));
+  }
+  ExpectConfigurationSupported(streams, false);
+}
+
+TEST_F(StaticPropertiesTest, ConfigureTooManyNonStalling) {
+  std::vector<camera3_stream_t> streams;
+  // At the threshold is ok.
+  for (int32_t i = 0; i < test_max_non_stalling_outputs_; ++i) {
+    streams.push_back(MakeStream(output_multisize_non_stalling_));
+  }
+  ExpectConfigurationSupported(streams, true);
+
+  // Reset.
+  mock_reader_ = std::make_unique<MetadataReaderMock>();
+  streams.clear();
+
+  // Try again with too many.
+  for (int32_t i = 0; i <= test_max_non_stalling_outputs_; ++i) {
+    streams.push_back(MakeStream(output_multisize_non_stalling_));
+  }
+  ExpectConfigurationSupported(streams, false);
+}
+
+TEST_F(StaticPropertiesTest, ConfigureUnuspportedInput) {
+  std::vector<camera3_stream_t> streams;
+  streams.push_back(MakeStream(input_, false, true));
+  streams.push_back(MakeStream(bidirectional_raw_));
+  // No matching output format for input.
+  ExpectConfigurationSupported(streams, false);
+}
+
+TEST_F(StaticPropertiesTest, ConfigureUnsupportedOutput) {
+  std::vector<camera3_stream_t> streams;
+  streams.push_back(MakeStream(input_, false, true));
+  // The universal output does match input.
+  streams.push_back(MakeStream(output_multisize_non_stalling_));
+  // Raw does not match input.
+  streams.push_back(MakeStream(bidirectional_raw_));
+  // Input is matched; it's ok that raw doesn't match (only the actual
+  // requests care).
+  ExpectConfigurationSupported(streams, true);
+}
+
+TEST_F(StaticPropertiesTest, ConfigureUnsupportedBidirectional) {
+  std::vector<camera3_stream_t> streams;
+  // The test raw format, while supporting both input and output,
+  // does not actually support itself.
+  streams.push_back(MakeStream(bidirectional_raw_, true, true));
+  ExpectConfigurationSupported(streams, false);
+}
+
+TEST_F(StaticPropertiesTest, ConfigureBadOperationMode) {
+  // A valid stream set.
+  camera3_stream_t stream = MakeStream(output_multisize_non_stalling_);
+  camera3_stream_t* stream_address = &stream;
+  // But not a valid config.
+  camera3_stream_configuration_t config = {
+      1,
+      &stream_address,
+      99  // Not a valid operation mode.
+  };
+  PrepareDefaultDUT();
+  EXPECT_FALSE(dut_->StreamConfigurationSupported(&config));
+}
+
+TEST_F(StaticPropertiesTest, ReprocessingSingleOutput) {
+  camera3_stream_t input_stream = MakeStream(input_);
+  camera3_stream_t output_stream = MakeStream(output_multisize_non_stalling_);
+  PrepareDefaultDUT();
+  EXPECT_TRUE(dut_->ReprocessingSupported(&input_stream, {&output_stream}));
+}
+
+TEST_F(StaticPropertiesTest, ReprocessingMultipleOutputs) {
+  camera3_stream_t input_stream =
+      MakeStream(bidirectional_self_supporting_stalling_, false, true);
+  // Bi-directional self-supporting supports the universal output and itself.
+  camera3_stream_t output_stream1 = MakeStream(output_multisize_non_stalling_);
+  camera3_stream_t output_stream2 =
+      MakeStream(bidirectional_self_supporting_stalling_);
+  PrepareDefaultDUT();
+  EXPECT_TRUE(dut_->ReprocessingSupported(&input_stream,
+                                          {&output_stream1, &output_stream2}));
+}
+
+TEST_F(StaticPropertiesTest, ReprocessingNoInput) {
+  camera3_stream_t output_stream = MakeStream(output_multisize_non_stalling_);
+  PrepareDefaultDUT();
+  EXPECT_FALSE(dut_->ReprocessingSupported(nullptr, {&output_stream}));
+}
+
+TEST_F(StaticPropertiesTest, ReprocessingNoOutput) {
+  camera3_stream_t input_stream = MakeStream(input_);
+  PrepareDefaultDUT();
+  EXPECT_FALSE(dut_->ReprocessingSupported(&input_stream, {}));
+}
+
+TEST_F(StaticPropertiesTest, ReprocessingInvalidOutput) {
+  camera3_stream_t input_stream = MakeStream(input_, false, true);
+  // The universal output does match input.
+  camera3_stream_t output_stream1 = MakeStream(output_multisize_non_stalling_);
+  // Raw does not match input.
+  camera3_stream_t output_stream2 = MakeStream(bidirectional_raw_);
+  PrepareDefaultDUT();
+  EXPECT_FALSE(dut_->ReprocessingSupported(&input_stream,
+                                           {&output_stream1, &output_stream2}));
+}
+
+}  // namespace default_camera_hal
diff --git a/modules/camera/3_4/stream_format.cpp b/modules/camera/3_4/stream_format.cpp
new file mode 100644
index 0000000..c85c26b
--- /dev/null
+++ b/modules/camera/3_4/stream_format.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright 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 "stream_format.h"
+
+#include <linux/videodev2.h>
+
+#include <system/graphics.h>
+
+#include "common.h"
+
+namespace v4l2_camera_hal {
+
+StreamFormat::StreamFormat(int format, uint32_t width, uint32_t height)
+    // TODO(b/30000211): multiplanar support.
+    : type_(V4L2_BUF_TYPE_VIDEO_CAPTURE),
+      v4l2_pixel_format_(StreamFormat::HalToV4L2PixelFormat(format)),
+      width_(width),
+      height_(height),
+      bytes_per_line_(0),
+      min_buffer_size_(0) {}
+
+StreamFormat::StreamFormat(const v4l2_format& format)
+    : type_(format.type),
+      // TODO(b/30000211): multiplanar support.
+      v4l2_pixel_format_(format.fmt.pix.pixelformat),
+      width_(format.fmt.pix.width),
+      height_(format.fmt.pix.height),
+      bytes_per_line_(format.fmt.pix.bytesperline),
+      min_buffer_size_(format.fmt.pix.sizeimage) {}
+
+void StreamFormat::FillFormatRequest(v4l2_format* format) const {
+  memset(format, 0, sizeof(*format));
+  format->type = type_;
+  format->fmt.pix.pixelformat = v4l2_pixel_format_;
+  format->fmt.pix.width = width_;
+  format->fmt.pix.height = height_;
+  // Bytes per line and min buffer size are outputs set by the driver,
+  // not part of the request.
+}
+
+FormatCategory StreamFormat::Category() const {
+  switch (v4l2_pixel_format_) {
+    case V4L2_PIX_FMT_JPEG:
+      return kFormatCategoryStalling;
+    case V4L2_PIX_FMT_YUV420:  // Fall through.
+    case V4L2_PIX_FMT_BGR32:
+      return kFormatCategoryNonStalling;
+    default:
+      // Note: currently no supported RAW formats.
+      return kFormatCategoryUnknown;
+  }
+}
+
+bool StreamFormat::operator==(const StreamFormat& other) const {
+  // Used to check that a requested format was actually set, so
+  // don't compare bytes per line or min buffer size.
+  return (type_ == other.type_ &&
+          v4l2_pixel_format_ == other.v4l2_pixel_format_ &&
+          width_ == other.width_ && height_ == other.height_);
+}
+
+bool StreamFormat::operator!=(const StreamFormat& other) const {
+  return !(*this == other);
+}
+
+int StreamFormat::V4L2ToHalPixelFormat(uint32_t v4l2_pixel_format) {
+  // Translate V4L2 format to HAL format.
+  int hal_pixel_format = -1;
+  switch (v4l2_pixel_format) {
+    case V4L2_PIX_FMT_JPEG:
+      hal_pixel_format = HAL_PIXEL_FORMAT_BLOB;
+      break;
+    case V4L2_PIX_FMT_YUV420:
+      hal_pixel_format = HAL_PIXEL_FORMAT_YCbCr_420_888;
+      break;
+    case V4L2_PIX_FMT_BGR32:
+      hal_pixel_format = HAL_PIXEL_FORMAT_RGBA_8888;
+      break;
+    default:
+      // Unrecognized format.
+      HAL_LOGV("Unrecognized v4l2 pixel format %u", v4l2_pixel_format);
+      break;
+  }
+  return hal_pixel_format;
+}
+
+uint32_t StreamFormat::HalToV4L2PixelFormat(int hal_pixel_format) {
+  // Translate HAL format to V4L2 format.
+  uint32_t v4l2_pixel_format = 0;
+  switch (hal_pixel_format) {
+    case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:  // fall-through.
+    case HAL_PIXEL_FORMAT_RGBA_8888:
+      // Should be RGB32, but RPi doesn't support that.
+      // For now we accept that the colors will be off.
+      v4l2_pixel_format = V4L2_PIX_FMT_BGR32;
+      break;
+    case HAL_PIXEL_FORMAT_YCbCr_420_888:
+      v4l2_pixel_format = V4L2_PIX_FMT_YUV420;
+      break;
+    case HAL_PIXEL_FORMAT_BLOB:
+      v4l2_pixel_format = V4L2_PIX_FMT_JPEG;
+      break;
+    default:
+      // Unrecognized format.
+      HAL_LOGV("Unrecognized HAL pixel format %d", hal_pixel_format);
+      break;
+  }
+  return v4l2_pixel_format;
+}
+
+}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/stream_format.h b/modules/camera/3_4/stream_format.h
new file mode 100644
index 0000000..66c5965
--- /dev/null
+++ b/modules/camera/3_4/stream_format.h
@@ -0,0 +1,69 @@
+/*
+ * 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_STREAM_FORMAT_H_
+#define V4L2_CAMERA_HAL_STREAM_FORMAT_H_
+
+#include <string.h>
+
+#include <linux/videodev2.h>
+
+#include "common.h"
+
+namespace v4l2_camera_hal {
+
+enum FormatCategory {
+  kFormatCategoryRaw,
+  kFormatCategoryStalling,
+  kFormatCategoryNonStalling,
+  kFormatCategoryUnknown,
+};
+
+class StreamFormat {
+ public:
+  StreamFormat(int format, uint32_t width, uint32_t height);
+  StreamFormat(const v4l2_format& format);
+  virtual ~StreamFormat() = default;
+  // Only uint32_t members, use default generated copy and assign.
+
+  void FillFormatRequest(v4l2_format* format) const;
+  FormatCategory Category() const;
+
+  // Accessors.
+  inline uint32_t type() const { return type_; };
+  inline uint32_t bytes_per_line() const { return bytes_per_line_; };
+
+  bool operator==(const StreamFormat& other) const;
+  bool operator!=(const StreamFormat& other) const;
+
+  // HAL <-> V4L2 conversions
+  // Returns 0 for unrecognized.
+  static uint32_t HalToV4L2PixelFormat(int hal_pixel_format);
+  // Returns -1 for unrecognized.
+  static int V4L2ToHalPixelFormat(uint32_t v4l2_pixel_format);
+
+ private:
+  uint32_t type_;
+  uint32_t v4l2_pixel_format_;
+  uint32_t width_;
+  uint32_t height_;
+  uint32_t bytes_per_line_;
+  uint32_t min_buffer_size_;
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_STREAM_FORMAT_H_
diff --git a/modules/camera/3_4/v4l2_camera.cpp b/modules/camera/3_4/v4l2_camera.cpp
new file mode 100644
index 0000000..3e5b859
--- /dev/null
+++ b/modules/camera/3_4/v4l2_camera.cpp
@@ -0,0 +1,451 @@
+/*
+ * Copyright 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_camera.h"
+
+#include <fcntl.h>
+#include <linux/videodev2.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <cstdlib>
+
+#include <camera/CameraMetadata.h>
+#include <hardware/camera3.h>
+
+#include "common.h"
+#include "function_thread.h"
+#include "metadata/metadata_common.h"
+#include "stream_format.h"
+#include "v4l2_metadata_factory.h"
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
+
+namespace v4l2_camera_hal {
+
+// Helper function for managing metadata.
+static std::vector<int32_t> getMetadataKeys(const camera_metadata_t* metadata) {
+  std::vector<int32_t> keys;
+  size_t num_entries = get_camera_metadata_entry_count(metadata);
+  for (size_t i = 0; i < num_entries; ++i) {
+    camera_metadata_ro_entry_t entry;
+    get_camera_metadata_ro_entry(metadata, i, &entry);
+    keys.push_back(entry.tag);
+  }
+  return keys;
+}
+
+V4L2Camera* V4L2Camera::NewV4L2Camera(int id, const std::string path) {
+  HAL_LOG_ENTER();
+
+  std::shared_ptr<V4L2Wrapper> v4l2_wrapper(V4L2Wrapper::NewV4L2Wrapper(path));
+  if (!v4l2_wrapper) {
+    HAL_LOGE("Failed to initialize V4L2 wrapper.");
+    return nullptr;
+  }
+
+  std::unique_ptr<Metadata> metadata;
+  int res = GetV4L2Metadata(v4l2_wrapper, &metadata);
+  if (res) {
+    HAL_LOGE("Failed to initialize V4L2 metadata: %d", res);
+    return nullptr;
+  }
+
+  return new V4L2Camera(id, std::move(v4l2_wrapper), std::move(metadata));
+}
+
+V4L2Camera::V4L2Camera(int id,
+                       std::shared_ptr<V4L2Wrapper> v4l2_wrapper,
+                       std::unique_ptr<Metadata> metadata)
+    : default_camera_hal::Camera(id),
+      device_(std::move(v4l2_wrapper)),
+      metadata_(std::move(metadata)),
+      max_input_streams_(0),
+      max_output_streams_({{0, 0, 0}}),
+      buffer_enqueuer_(new FunctionThread(
+          std::bind(&V4L2Camera::enqueueRequestBuffers, this))),
+      buffer_dequeuer_(new FunctionThread(
+          std::bind(&V4L2Camera::dequeueRequestBuffers, this))) {
+  HAL_LOG_ENTER();
+}
+
+V4L2Camera::~V4L2Camera() {
+  HAL_LOG_ENTER();
+}
+
+int V4L2Camera::connect() {
+  HAL_LOG_ENTER();
+
+  if (connection_) {
+    HAL_LOGE("Already connected. Please disconnect and try again.");
+    return -EIO;
+  }
+
+  connection_.reset(new V4L2Wrapper::Connection(device_));
+  if (connection_->status()) {
+    HAL_LOGE("Failed to connect to device.");
+    return connection_->status();
+  }
+
+  // TODO(b/29185945): confirm this is a supported device.
+  // This is checked by the HAL, but the device at |device_|'s path may
+  // not be the same one that was there when the HAL was loaded.
+  // (Alternatively, better hotplugging support may make this unecessary
+  // by disabling cameras that get disconnected and checking newly connected
+  // cameras, so connect() is never called on an unsupported camera)
+
+  // TODO(b/29158098): Inform service of any flashes that are no longer
+  // available because this camera is in use.
+  return 0;
+}
+
+void V4L2Camera::disconnect() {
+  HAL_LOG_ENTER();
+
+  connection_.reset();
+
+  // TODO(b/29158098): Inform service of any flashes that are available again
+  // because this camera is no longer in use.
+}
+
+int V4L2Camera::flushBuffers() {
+  HAL_LOG_ENTER();
+
+  int res = device_->StreamOff();
+
+  // This is not strictly necessary, but prevents a buildup of aborted
+  // requests in the in_flight_ map. These should be cleared
+  // whenever the stream is turned off.
+  std::lock_guard<std::mutex> guard(in_flight_lock_);
+  in_flight_.clear();
+
+  return res;
+}
+
+int V4L2Camera::initStaticInfo(android::CameraMetadata* out) {
+  HAL_LOG_ENTER();
+
+  int res = metadata_->FillStaticMetadata(out);
+  if (res) {
+    HAL_LOGE("Failed to get static metadata.");
+    return res;
+  }
+
+  // Extract max streams for use in verifying stream configs.
+  res = SingleTagValue(
+      *out, ANDROID_REQUEST_MAX_NUM_INPUT_STREAMS, &max_input_streams_);
+  if (res) {
+    HAL_LOGE("Failed to get max num input streams from static metadata.");
+    return res;
+  }
+  res = SingleTagValue(
+      *out, ANDROID_REQUEST_MAX_NUM_OUTPUT_STREAMS, &max_output_streams_);
+  if (res) {
+    HAL_LOGE("Failed to get max num output streams from static metadata.");
+    return res;
+  }
+
+  return 0;
+}
+
+int V4L2Camera::initTemplate(int type, android::CameraMetadata* out) {
+  HAL_LOG_ENTER();
+
+  return metadata_->GetRequestTemplate(type, out);
+}
+
+void V4L2Camera::initDeviceInfo(camera_info_t* info) {
+  HAL_LOG_ENTER();
+
+  // TODO(b/31044975): move this into device interface.
+  // For now, just constants.
+  info->resource_cost = 100;
+  info->conflicting_devices = nullptr;
+  info->conflicting_devices_length = 0;
+}
+
+int V4L2Camera::initDevice() {
+  HAL_LOG_ENTER();
+
+  // Start the buffer enqueue/dequeue threads if they're not already running.
+  if (!buffer_enqueuer_->isRunning()) {
+    android::status_t res = buffer_enqueuer_->run("Enqueue buffers");
+    if (res != android::OK) {
+      HAL_LOGE("Failed to start buffer enqueue thread: %d", res);
+      return -ENODEV;
+    }
+  }
+  if (!buffer_dequeuer_->isRunning()) {
+    android::status_t res = buffer_dequeuer_->run("Dequeue buffers");
+    if (res != android::OK) {
+      HAL_LOGE("Failed to start buffer dequeue thread: %d", res);
+      return -ENODEV;
+    }
+  }
+
+  return 0;
+}
+
+int V4L2Camera::enqueueRequest(
+    std::shared_ptr<default_camera_hal::CaptureRequest> request) {
+  HAL_LOG_ENTER();
+
+  // Assume request validated before calling this function.
+  // (For now, always exactly 1 output buffer, no inputs).
+  {
+    std::lock_guard<std::mutex> guard(request_queue_lock_);
+    request_queue_.push(request);
+    requests_available_.notify_one();
+  }
+
+  return 0;
+}
+
+std::shared_ptr<default_camera_hal::CaptureRequest>
+V4L2Camera::dequeueRequest() {
+  std::unique_lock<std::mutex> lock(request_queue_lock_);
+  while (request_queue_.empty()) {
+    requests_available_.wait(lock);
+  }
+
+  std::shared_ptr<default_camera_hal::CaptureRequest> request =
+      request_queue_.front();
+  request_queue_.pop();
+  return request;
+}
+
+bool V4L2Camera::enqueueRequestBuffers() {
+  // Get a request from the queue (blocks this thread until one is available).
+  std::shared_ptr<default_camera_hal::CaptureRequest> request =
+      dequeueRequest();
+
+  // Assume request validated before being added to the queue
+  // (For now, always exactly 1 output buffer, no inputs).
+
+  // Setting and getting settings are best effort here,
+  // since there's no way to know through V4L2 exactly what
+  // settings are used for a buffer unless we were to enqueue them
+  // one at a time, which would be too slow.
+
+  // Set the requested settings
+  int res = metadata_->SetRequestSettings(request->settings);
+  if (res) {
+    HAL_LOGE("Failed to set settings.");
+    completeRequest(request, res);
+    return true;
+  }
+
+  // Replace the requested settings with a snapshot of
+  // the used settings/state immediately before enqueue.
+  res = metadata_->FillResultMetadata(&request->settings);
+  if (res) {
+    // Note: since request is a shared pointer, this may happen if another
+    // thread has already decided to complete the request (e.g. via flushing),
+    // since that locks the metadata (in that case, this failing is fine,
+    // and completeRequest will simply do nothing).
+    HAL_LOGE("Failed to fill result metadata.");
+    completeRequest(request, res);
+    return true;
+  }
+
+  // Actually enqueue the buffer for capture.
+  {
+    std::lock_guard<std::mutex> guard(in_flight_lock_);
+
+    uint32_t index;
+    res = device_->EnqueueBuffer(&request->output_buffers[0], &index);
+    if (res) {
+      HAL_LOGE("Device failed to enqueue buffer.");
+      completeRequest(request, res);
+      return true;
+    }
+
+    // Make sure the stream is on (no effect if already on).
+    res = device_->StreamOn();
+    if (res) {
+      HAL_LOGE("Device failed to turn on stream.");
+      // Don't really want to send an error for only the request here,
+      // since this is a full device error.
+      // TODO: Should trigger full flush.
+      return true;
+    }
+
+    // Note: the request should be dequeued/flushed from the device
+    // before removal from in_flight_.
+    in_flight_.emplace(index, request);
+    buffers_in_flight_.notify_one();
+  }
+
+  return true;
+}
+
+bool V4L2Camera::dequeueRequestBuffers() {
+  // Dequeue a buffer.
+  uint32_t result_index;
+  int res = device_->DequeueBuffer(&result_index);
+  if (res) {
+    if (res == -EAGAIN) {
+      // EAGAIN just means nothing to dequeue right now.
+      // Wait until something is available before looping again.
+      std::unique_lock<std::mutex> lock(in_flight_lock_);
+      while (in_flight_.empty()) {
+        buffers_in_flight_.wait(lock);
+      }
+    } else {
+      HAL_LOGW("Device failed to dequeue buffer: %d", res);
+    }
+    return true;
+  }
+
+  // Find the associated request and complete it.
+  std::lock_guard<std::mutex> guard(in_flight_lock_);
+  auto index_request = in_flight_.find(result_index);
+  if (index_request != in_flight_.end()) {
+    completeRequest(index_request->second, 0);
+    in_flight_.erase(index_request);
+  } else {
+    HAL_LOGW(
+        "Dequeued non in-flight buffer index %d. "
+        "This buffer may have been flushed from the HAL but not the device.",
+        index_request->first);
+  }
+  return true;
+}
+
+bool V4L2Camera::validateDataspacesAndRotations(
+    const camera3_stream_configuration_t* stream_config) {
+  HAL_LOG_ENTER();
+
+  for (uint32_t i = 0; i < stream_config->num_streams; ++i) {
+    if (stream_config->streams[i]->rotation != CAMERA3_STREAM_ROTATION_0) {
+      HAL_LOGV("Rotation %d for stream %d not supported",
+               stream_config->streams[i]->rotation,
+               i);
+      return false;
+    }
+    // Accept all dataspaces, as it will just be overwritten below anyways.
+  }
+  return true;
+}
+
+int V4L2Camera::setupStreams(camera3_stream_configuration_t* stream_config) {
+  HAL_LOG_ENTER();
+
+  std::lock_guard<std::mutex> guard(in_flight_lock_);
+  // The framework should be enforcing this, but doesn't hurt to be safe.
+  if (!in_flight_.empty()) {
+    HAL_LOGE("Can't set device format while frames are in flight.");
+    return -EINVAL;
+  }
+
+  // stream_config should have been validated; assume at least 1 stream.
+  camera3_stream_t* stream = stream_config->streams[0];
+  int format = stream->format;
+  uint32_t width = stream->width;
+  uint32_t height = stream->height;
+
+  if (stream_config->num_streams > 1) {
+    // TODO(b/29939583):  V4L2 doesn't actually support more than 1
+    // stream at a time. If not all streams are the same format
+    // and size, error. Note that this means the HAL is not spec-compliant.
+    // Technically, this error should be thrown during validation, but
+    // since it isn't a spec-valid error validation isn't set up to check it.
+    for (uint32_t i = 1; i < stream_config->num_streams; ++i) {
+      stream = stream_config->streams[i];
+      if (stream->format != format || stream->width != width ||
+          stream->height != height) {
+        HAL_LOGE(
+            "V4L2 only supports 1 stream configuration at a time "
+            "(stream 0 is format %d, width %u, height %u, "
+            "stream %d is format %d, width %u, height %u).",
+            format,
+            width,
+            height,
+            i,
+            stream->format,
+            stream->width,
+            stream->height);
+        return -EINVAL;
+      }
+    }
+  }
+
+  // Ensure the stream is off.
+  int res = device_->StreamOff();
+  if (res) {
+    HAL_LOGE("Device failed to turn off stream for reconfiguration: %d.", res);
+    return -ENODEV;
+  }
+
+  StreamFormat stream_format(format, width, height);
+  uint32_t max_buffers = 0;
+  res = device_->SetFormat(stream_format, &max_buffers);
+  if (res) {
+    HAL_LOGE("Failed to set device to correct format for stream: %d.", res);
+    return -ENODEV;
+  }
+
+  // Sanity check.
+  if (max_buffers < 1) {
+    HAL_LOGE("Setting format resulted in an invalid maximum of %u buffers.",
+             max_buffers);
+    return -ENODEV;
+  }
+
+  // Set all the streams dataspaces, usages, and max buffers.
+  for (uint32_t i = 0; i < stream_config->num_streams; ++i) {
+    stream = stream_config->streams[i];
+
+    // Max buffers as reported by the device.
+    stream->max_buffers = max_buffers;
+
+    // Usage: currently using sw graphics.
+    switch (stream->stream_type) {
+      case CAMERA3_STREAM_INPUT:
+        stream->usage = GRALLOC_USAGE_SW_READ_OFTEN;
+        break;
+      case CAMERA3_STREAM_OUTPUT:
+        stream->usage = GRALLOC_USAGE_SW_WRITE_OFTEN;
+        break;
+      case CAMERA3_STREAM_BIDIRECTIONAL:
+        stream->usage =
+            GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN;
+        break;
+      default:
+        // nothing to do.
+        break;
+    }
+
+    // Doesn't matter what was requested, we always use dataspace V0_JFIF.
+    // Note: according to camera3.h, this isn't allowed, but the camera
+    // framework team claims it's underdocumented; the implementation lets the
+    // HAL overwrite it. If this is changed, change the validation above.
+    stream->data_space = HAL_DATASPACE_V0_JFIF;
+  }
+
+  return 0;
+}
+
+bool V4L2Camera::isValidRequestSettings(
+    const android::CameraMetadata& settings) {
+  if (!metadata_->IsValidRequest(settings)) {
+    HAL_LOGE("Invalid request settings.");
+    return false;
+  }
+  return true;
+}
+
+}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/v4l2_camera.h b/modules/camera/3_4/v4l2_camera.h
new file mode 100644
index 0000000..1db8d40
--- /dev/null
+++ b/modules/camera/3_4/v4l2_camera.h
@@ -0,0 +1,119 @@
+/*
+ * 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.
+ */
+
+// Loosely based on hardware/libhardware/modules/camera/ExampleCamera.h
+
+#ifndef V4L2_CAMERA_HAL_V4L2_CAMERA_H_
+#define V4L2_CAMERA_HAL_V4L2_CAMERA_H_
+
+#include <array>
+#include <condition_variable>
+#include <map>
+#include <queue>
+#include <string>
+
+#include <camera/CameraMetadata.h>
+#include <utils/StrongPointer.h>
+#include <utils/Thread.h>
+
+#include "camera.h"
+#include "common.h"
+#include "metadata/metadata.h"
+#include "v4l2_wrapper.h"
+
+namespace v4l2_camera_hal {
+// V4L2Camera is a specific V4L2-supported camera device. The Camera object
+// contains all logic common between all cameras (e.g. front and back cameras),
+// while a specific camera device (e.g. V4L2Camera) holds all specific
+// metadata and logic about that device.
+class V4L2Camera : public default_camera_hal::Camera {
+ public:
+  // Use this method to create V4L2Camera objects. Functionally equivalent
+  // to "new V4L2Camera", except that it may return nullptr in case of failure.
+  static V4L2Camera* NewV4L2Camera(int id, const std::string path);
+  ~V4L2Camera();
+
+ private:
+  // Constructor private to allow failing on bad input.
+  // Use NewV4L2Camera instead.
+  V4L2Camera(int id,
+             std::shared_ptr<V4L2Wrapper> v4l2_wrapper,
+             std::unique_ptr<Metadata> metadata);
+
+  // default_camera_hal::Camera virtual methods.
+  // Connect to the device: open dev nodes, etc.
+  int connect() override;
+  // Disconnect from the device: close dev nodes, etc.
+  void disconnect() override;
+  // Initialize static camera characteristics for individual device.
+  int initStaticInfo(android::CameraMetadata* out) override;
+  // Initialize a template of the given type.
+  int initTemplate(int type, android::CameraMetadata* out) override;
+  // Initialize device info: resource cost and conflicting devices
+  // (/conflicting devices length).
+  void initDeviceInfo(camera_info_t* info) override;
+  // Extra initialization of device when opened.
+  int initDevice() override;
+  // Verify stream configuration dataspaces and rotation values
+  bool validateDataspacesAndRotations(
+      const camera3_stream_configuration_t* stream_config) override;
+  // Set up the streams, including seting usage & max_buffers
+  int setupStreams(camera3_stream_configuration_t* stream_config) override;
+  // Verify settings are valid for a capture or reprocessing.
+  bool isValidRequestSettings(const android::CameraMetadata& settings) override;
+  // Enqueue a request to receive data from the camera.
+  int enqueueRequest(
+      std::shared_ptr<default_camera_hal::CaptureRequest> request) override;
+  // Flush in flight buffers.
+  int flushBuffers() override;
+
+  // Async request processing helpers.
+  // Dequeue a request from the waiting queue.
+  // Blocks until a request is available.
+  std::shared_ptr<default_camera_hal::CaptureRequest> dequeueRequest();
+
+  // Thread functions. Return true to loop, false to exit.
+  // Pass buffers for enqueued requests to the device.
+  bool enqueueRequestBuffers();
+  // Retreive buffers from the device.
+  bool dequeueRequestBuffers();
+
+  // V4L2 helper.
+  std::shared_ptr<V4L2Wrapper> device_;
+  std::unique_ptr<V4L2Wrapper::Connection> connection_;
+  std::unique_ptr<Metadata> metadata_;
+  std::mutex request_queue_lock_;
+  std::queue<std::shared_ptr<default_camera_hal::CaptureRequest>>
+      request_queue_;
+  std::mutex in_flight_lock_;
+  // Maps buffer index : request.
+  std::map<uint32_t, std::shared_ptr<default_camera_hal::CaptureRequest>>
+      in_flight_;
+  // Threads require holding an Android strong pointer.
+  android::sp<android::Thread> buffer_enqueuer_;
+  android::sp<android::Thread> buffer_dequeuer_;
+  std::condition_variable requests_available_;
+  std::condition_variable buffers_in_flight_;
+
+  int32_t max_input_streams_;
+  std::array<int, 3> max_output_streams_;  // {raw, non-stalling, stalling}.
+
+  DISALLOW_COPY_AND_ASSIGN(V4L2Camera);
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_V4L2_CAMERA_H_
diff --git a/modules/camera/3_4/v4l2_camera_hal.cpp b/modules/camera/3_4/v4l2_camera_hal.cpp
new file mode 100644
index 0000000..bfc8712
--- /dev/null
+++ b/modules/camera/3_4/v4l2_camera_hal.cpp
@@ -0,0 +1,238 @@
+/*
+ * Copyright 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.
+ */
+
+// Modified from hardware/libhardware/modules/camera/CameraHAL.cpp
+
+#include "v4l2_camera_hal.h"
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <linux/videodev2.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <cstdlib>
+#include <unordered_set>
+
+#include <android-base/parseint.h>
+
+#include "common.h"
+#include "v4l2_camera.h"
+
+/*
+ * This file serves as the entry point to the HAL. It is modified from the
+ * example default HAL available in hardware/libhardware/modules/camera.
+ * It contains the module structure and functions used by the framework
+ * to load and interface to this HAL, as well as the handles to the individual
+ * camera devices.
+ */
+
+namespace v4l2_camera_hal {
+
+// Default global camera hal.
+static V4L2CameraHAL gCameraHAL;
+
+V4L2CameraHAL::V4L2CameraHAL() : mCameras(), mCallbacks(NULL) {
+  HAL_LOG_ENTER();
+  // Adds all available V4L2 devices.
+  // List /dev nodes.
+  DIR* dir = opendir("/dev");
+  if (dir == NULL) {
+    HAL_LOGE("Failed to open /dev");
+    return;
+  }
+  // Find /dev/video* nodes.
+  dirent* ent;
+  std::vector<std::string> nodes;
+  while ((ent = readdir(dir))) {
+    std::string desired = "video";
+    size_t len = desired.size();
+    if (strncmp(desired.c_str(), ent->d_name, len) == 0) {
+      if (strlen(ent->d_name) > len && isdigit(ent->d_name[len])) {
+        // ent is a numbered video node.
+        nodes.push_back(std::string("/dev/") + ent->d_name);
+        HAL_LOGV("Found video node %s.", nodes.back().c_str());
+      }
+    }
+  }
+  // Test each for V4L2 support and uniqueness.
+  std::unordered_set<std::string> buses;
+  std::string bus;
+  v4l2_capability cap;
+  int fd;
+  int id = 0;
+  for (const auto& node : nodes) {
+    // Open the node.
+    fd = TEMP_FAILURE_RETRY(open(node.c_str(), O_RDWR));
+    if (fd < 0) {
+      HAL_LOGE("failed to open %s (%s).", node.c_str(), strerror(errno));
+      continue;
+    }
+    // Read V4L2 capabilities.
+    if (TEMP_FAILURE_RETRY(ioctl(fd, VIDIOC_QUERYCAP, &cap)) != 0) {
+      HAL_LOGE(
+          "VIDIOC_QUERYCAP on %s fail: %s.", node.c_str(), strerror(errno));
+    } else if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
+      HAL_LOGE("%s is not a V4L2 video capture device.", node.c_str());
+    } else {
+      // If the node is unique, add a camera for it.
+      bus = reinterpret_cast<char*>(cap.bus_info);
+      if (buses.insert(bus).second) {
+        HAL_LOGV("Found unique bus at %s.", node.c_str());
+        std::unique_ptr<V4L2Camera> cam(V4L2Camera::NewV4L2Camera(id++, node));
+        if (cam) {
+          mCameras.push_back(std::move(cam));
+        } else {
+          HAL_LOGE("Failed to initialize camera at %s.", node.c_str());
+        }
+      }
+    }
+    TEMP_FAILURE_RETRY(close(fd));
+  }
+}
+
+V4L2CameraHAL::~V4L2CameraHAL() {
+  HAL_LOG_ENTER();
+}
+
+int V4L2CameraHAL::getNumberOfCameras() {
+  HAL_LOGV("returns %d", mCameras.size());
+  return mCameras.size();
+}
+
+int V4L2CameraHAL::getCameraInfo(int id, camera_info_t* info) {
+  HAL_LOG_ENTER();
+  if (id < 0 || id >= mCameras.size()) {
+    return -EINVAL;
+  }
+  // TODO(b/29185945): Hotplugging: return -EINVAL if unplugged.
+  return mCameras[id]->getInfo(info);
+}
+
+int V4L2CameraHAL::setCallbacks(const camera_module_callbacks_t* callbacks) {
+  HAL_LOG_ENTER();
+  mCallbacks = callbacks;
+  return 0;
+}
+
+void V4L2CameraHAL::getVendorTagOps(vendor_tag_ops_t* ops) {
+  HAL_LOG_ENTER();
+  // No vendor ops for this HAL. From <hardware/camera_common.h>:
+  // "leave ops unchanged if no vendor tags are defined."
+}
+
+int V4L2CameraHAL::openLegacy(const hw_module_t* module,
+                              const char* id,
+                              uint32_t halVersion,
+                              hw_device_t** device) {
+  HAL_LOG_ENTER();
+  // Not supported.
+  return -ENOSYS;
+}
+
+int V4L2CameraHAL::setTorchMode(const char* camera_id, bool enabled) {
+  HAL_LOG_ENTER();
+  // TODO(b/29158098): HAL is required to respond appropriately if
+  // the desired camera actually does support flash.
+  return -ENOSYS;
+}
+
+int V4L2CameraHAL::openDevice(const hw_module_t* module,
+                              const char* name,
+                              hw_device_t** device) {
+  HAL_LOG_ENTER();
+
+  if (module != &HAL_MODULE_INFO_SYM.common) {
+    HAL_LOGE(
+        "Invalid module %p expected %p", module, &HAL_MODULE_INFO_SYM.common);
+    return -EINVAL;
+  }
+
+  int id;
+  if (!android::base::ParseInt(name, &id, 0, getNumberOfCameras() - 1)) {
+    return -EINVAL;
+  }
+  // TODO(b/29185945): Hotplugging: return -EINVAL if unplugged.
+  return mCameras[id]->openDevice(module, device);
+}
+
+/*
+ * The framework calls the following wrappers, which in turn
+ * call the corresponding methods of the global HAL object.
+ */
+
+static int get_number_of_cameras() {
+  return gCameraHAL.getNumberOfCameras();
+}
+
+static int get_camera_info(int id, struct camera_info* info) {
+  return gCameraHAL.getCameraInfo(id, info);
+}
+
+static int set_callbacks(const camera_module_callbacks_t* callbacks) {
+  return gCameraHAL.setCallbacks(callbacks);
+}
+
+static void get_vendor_tag_ops(vendor_tag_ops_t* ops) {
+  return gCameraHAL.getVendorTagOps(ops);
+}
+
+static int open_legacy(const hw_module_t* module,
+                       const char* id,
+                       uint32_t halVersion,
+                       hw_device_t** device) {
+  return gCameraHAL.openLegacy(module, id, halVersion, device);
+}
+
+static int set_torch_mode(const char* camera_id, bool enabled) {
+  return gCameraHAL.setTorchMode(camera_id, enabled);
+}
+
+static int open_dev(const hw_module_t* module,
+                    const char* name,
+                    hw_device_t** device) {
+  return gCameraHAL.openDevice(module, name, device);
+}
+
+}  // namespace v4l2_camera_hal
+
+static hw_module_methods_t v4l2_module_methods = {
+    .open = v4l2_camera_hal::open_dev};
+
+camera_module_t HAL_MODULE_INFO_SYM __attribute__((visibility("default"))) = {
+    .common =
+        {
+            .tag = HARDWARE_MODULE_TAG,
+            .module_api_version = CAMERA_MODULE_API_VERSION_2_4,
+            .hal_api_version = HARDWARE_HAL_API_VERSION,
+            .id = CAMERA_HARDWARE_MODULE_ID,
+            .name = "V4L2 Camera HAL v3",
+            .author = "The Android Open Source Project",
+            .methods = &v4l2_module_methods,
+            .dso = nullptr,
+            .reserved = {0},
+        },
+    .get_number_of_cameras = v4l2_camera_hal::get_number_of_cameras,
+    .get_camera_info = v4l2_camera_hal::get_camera_info,
+    .set_callbacks = v4l2_camera_hal::set_callbacks,
+    .get_vendor_tag_ops = v4l2_camera_hal::get_vendor_tag_ops,
+    .open_legacy = v4l2_camera_hal::open_legacy,
+    .set_torch_mode = v4l2_camera_hal::set_torch_mode,
+    .init = nullptr,
+    .reserved = {nullptr, nullptr, nullptr, nullptr, nullptr}};
diff --git a/modules/camera/3_4/v4l2_camera_hal.h b/modules/camera/3_4/v4l2_camera_hal.h
new file mode 100644
index 0000000..1b4ef0f
--- /dev/null
+++ b/modules/camera/3_4/v4l2_camera_hal.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright 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.
+ */
+
+// Modified from hardware/libhardware/modules/camera/CameraHAL.h
+
+#ifndef V4L2_CAMERA_HAL_V4L2_CAMERA_HAL_H_
+#define V4L2_CAMERA_HAL_V4L2_CAMERA_HAL_H_
+
+#include <vector>
+
+#include <hardware/camera_common.h>
+#include <hardware/hardware.h>
+
+#include "camera.h"
+#include "common.h"
+
+namespace v4l2_camera_hal {
+/*
+ * V4L2CameraHAL contains all module state that isn't specific to an
+ * individual camera device. This class is based off of the sample
+ * default CameraHAL from /hardware/libhardware/modules/camera.
+ */
+class V4L2CameraHAL {
+ public:
+  V4L2CameraHAL();
+  ~V4L2CameraHAL();
+
+  // Camera Module Interface (see <hardware/camera_common.h>).
+  int getNumberOfCameras();
+  int getCameraInfo(int camera_id, camera_info_t* info);
+  int setCallbacks(const camera_module_callbacks_t* callbacks);
+  void getVendorTagOps(vendor_tag_ops_t* ops);
+  int openLegacy(const hw_module_t* module,
+                 const char* id,
+                 uint32_t halVersion,
+                 hw_device_t** device);
+  int setTorchMode(const char* camera_id, bool enabled);
+
+  // Hardware Module Interface (see <hardware/hardware.h>).
+  int openDevice(const hw_module_t* mod, const char* name, hw_device_t** dev);
+
+ private:
+  // Vector of cameras.
+  std::vector<std::unique_ptr<default_camera_hal::Camera>> mCameras;
+  // Callback handle.
+  const camera_module_callbacks_t* mCallbacks;
+
+  DISALLOW_COPY_AND_ASSIGN(V4L2CameraHAL);
+};
+
+}  // namespace v4l2_camera_hal
+
+extern camera_module_t HAL_MODULE_INFO_SYM;
+
+#endif  // V4L2_CAMERA_HAL_V4L2_CAMERA_HAL_H_
diff --git a/modules/camera/3_4/v4l2_gralloc.cpp b/modules/camera/3_4/v4l2_gralloc.cpp
new file mode 100644
index 0000000..7da3c4e
--- /dev/null
+++ b/modules/camera/3_4/v4l2_gralloc.cpp
@@ -0,0 +1,334 @@
+/*
+ * 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_gralloc.h"
+
+#include <linux/videodev2.h>
+
+#include <cstdlib>
+
+#include <hardware/camera3.h>
+#include <hardware/gralloc.h>
+#include <system/graphics.h>
+
+#include "common.h"
+#include "stream_format.h"
+
+namespace v4l2_camera_hal {
+
+// Copy |height| lines from |src| to |dest|,
+// where |src| and |dest| may have different line lengths.
+void copyWithPadding(uint8_t* dest,
+                     const uint8_t* src,
+                     size_t dest_stride,
+                     size_t src_stride,
+                     size_t height) {
+  size_t copy_stride = dest_stride;
+  if (copy_stride > src_stride) {
+    // Adding padding, not reducing. 0 out the extra memory.
+    memset(dest, 0, src_stride * height);
+    copy_stride = src_stride;
+  }
+  uint8_t* dest_line_start = dest;
+  const uint8_t* src_line_start = src;
+  for (size_t row = 0; row < height;
+       ++row, dest_line_start += dest_stride, src_line_start += src_stride) {
+    memcpy(dest_line_start, src_line_start, copy_stride);
+  }
+}
+
+V4L2Gralloc* V4L2Gralloc::NewV4L2Gralloc() {
+  // Initialize and check the gralloc module.
+  const hw_module_t* module = nullptr;
+  int res = hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module);
+  if (res || !module) {
+    HAL_LOGE("Couldn't get gralloc module.");
+    return nullptr;
+  }
+  const gralloc_module_t* gralloc =
+      reinterpret_cast<const gralloc_module_t*>(module);
+
+  // This class only supports Gralloc v0, not Gralloc V1.
+  if (gralloc->common.module_api_version > GRALLOC_MODULE_API_VERSION_0_3) {
+    HAL_LOGE(
+        "Invalid gralloc version %x. Only 0.3 (%x) "
+        "and below are supported by this HAL.",
+        gralloc->common.module_api_version,
+        GRALLOC_MODULE_API_VERSION_0_3);
+    return nullptr;
+  }
+
+  return new V4L2Gralloc(gralloc);
+}
+
+// Private. As checked by above factory, module will be non-null
+// and a supported version.
+V4L2Gralloc::V4L2Gralloc(const gralloc_module_t* module) : mModule(module) {}
+
+V4L2Gralloc::~V4L2Gralloc() {
+  // Unlock buffers that are still locked.
+  unlockAllBuffers();
+}
+
+int V4L2Gralloc::lock(const camera3_stream_buffer_t* camera_buffer,
+                      uint32_t bytes_per_line,
+                      v4l2_buffer* device_buffer) {
+  // Lock the camera buffer (varies depending on if the buffer is YUV or not).
+  std::unique_ptr<BufferData> buffer_data(
+      new BufferData{camera_buffer, nullptr, bytes_per_line});
+  buffer_handle_t buffer = *camera_buffer->buffer;
+  void* data;
+  camera3_stream_t* stream = camera_buffer->stream;
+  int ret = 0;
+  switch (StreamFormat::HalToV4L2PixelFormat(stream->format)) {
+    // TODO(b/30119452): support more YCbCr formats.
+    case V4L2_PIX_FMT_YUV420:
+      android_ycbcr yuv_data;
+      ret = mModule->lock_ycbcr(mModule,
+                                buffer,
+                                stream->usage,
+                                0,
+                                0,
+                                stream->width,
+                                stream->height,
+                                &yuv_data);
+      if (ret) {
+        HAL_LOGE("Failed to lock ycbcr buffer: %d", ret);
+        return ret;
+      }
+
+      // Check if gralloc format matches v4l2 format
+      // (same padding, not interleaved, contiguous).
+      if (yuv_data.ystride == bytes_per_line &&
+          yuv_data.cstride == bytes_per_line / 2 && yuv_data.chroma_step == 1 &&
+          (reinterpret_cast<uint8_t*>(yuv_data.cb) ==
+           reinterpret_cast<uint8_t*>(yuv_data.y) +
+               (stream->height * yuv_data.ystride)) &&
+          (reinterpret_cast<uint8_t*>(yuv_data.cr) ==
+           reinterpret_cast<uint8_t*>(yuv_data.cb) +
+               (stream->height / 2 * yuv_data.cstride))) {
+        // If so, great, point to the beginning.
+        data = yuv_data.y;
+      } else {
+        // If not, allocate a contiguous buffer of appropriate size
+        // (to be transformed back upon unlock).
+        data = new uint8_t[device_buffer->length];
+        // Make a dynamically-allocated copy of yuv_data,
+        // since it will be needed at transform time.
+        buffer_data->transform_dest.reset(new android_ycbcr(yuv_data));
+      }
+      break;
+    case V4L2_PIX_FMT_JPEG:
+      // Jpeg buffers are just contiguous blobs; lock length * 1.
+      ret = mModule->lock(mModule,
+                          buffer,
+                          stream->usage,
+                          0,
+                          0,
+                          device_buffer->length,
+                          1,
+                          &data);
+      if (ret) {
+        HAL_LOGE("Failed to lock jpeg buffer: %d", ret);
+        return ret;
+      }
+      break;
+    case V4L2_PIX_FMT_BGR32:  // Fall-through.
+    case V4L2_PIX_FMT_RGB32:
+      // RGB formats have nice agreed upon representation. Unless using android
+      // flex formats.
+      ret = mModule->lock(mModule,
+                          buffer,
+                          stream->usage,
+                          0,
+                          0,
+                          stream->width,
+                          stream->height,
+                          &data);
+      if (ret) {
+        HAL_LOGE("Failed to lock RGB buffer: %d", ret);
+        return ret;
+      }
+      break;
+    default:
+      return -EINVAL;
+  }
+
+  if (!data) {
+    ALOGE("Gralloc lock returned null ptr");
+    return -ENODEV;
+  }
+
+  // Set up the device buffer.
+  static_assert(sizeof(unsigned long) >= sizeof(void*),
+                "void* must be able to fit in the v4l2_buffer m.userptr "
+                "field (unsigned long) for this code to work");
+  device_buffer->m.userptr = reinterpret_cast<unsigned long>(data);
+
+  // Note the mapping of data:buffer info for when unlock is called.
+  mBufferMap.emplace(data, buffer_data.release());
+
+  return 0;
+}
+
+int V4L2Gralloc::unlock(const v4l2_buffer* device_buffer) {
+  // TODO(b/30000211): support multi-planar data (video_capture_mplane).
+  if (device_buffer->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+    return -EINVAL;
+  }
+
+  void* data = reinterpret_cast<void*>(device_buffer->m.userptr);
+
+  // Find and pop the matching entry in the map.
+  auto map_entry = mBufferMap.find(data);
+  if (map_entry == mBufferMap.end()) {
+    HAL_LOGE("No matching buffer for data at %p", data);
+    return -EINVAL;
+  }
+  std::unique_ptr<const BufferData> buffer_data(map_entry->second);
+  mBufferMap.erase(map_entry);
+
+  const camera3_stream_buffer_t* camera_buffer = buffer_data->camera_buffer;
+  const buffer_handle_t buffer = *camera_buffer->buffer;
+
+  // Check for transform.
+  if (buffer_data->transform_dest) {
+    HAL_LOGV("Transforming V4L2 YUV to gralloc YUV.");
+    // In this case data was allocated by this class, put it in a unique_ptr
+    // to ensure it gets cleaned up no matter which way this function exits.
+    std::unique_ptr<uint8_t[]> data_cleanup(reinterpret_cast<uint8_t*>(data));
+
+    uint32_t bytes_per_line = buffer_data->v4l2_bytes_per_line;
+    android_ycbcr* yuv_data = buffer_data->transform_dest.get();
+
+    // Should only occur in error situations.
+    if (device_buffer->bytesused == 0) {
+      return -EINVAL;
+    }
+
+    // Transform V4L2 to Gralloc, copying each plane to the correct place,
+    // adjusting padding, and interleaving if necessary.
+    uint32_t height = camera_buffer->stream->height;
+    // Y data first.
+    size_t y_len = bytes_per_line * height;
+    if (yuv_data->ystride == bytes_per_line) {
+      // Data should match exactly.
+      memcpy(yuv_data->y, data, y_len);
+    } else {
+      HAL_LOGV("Changing padding on Y plane from %u to %u.",
+               bytes_per_line,
+               yuv_data->ystride);
+      // Wrong padding from V4L2.
+      copyWithPadding(reinterpret_cast<uint8_t*>(yuv_data->y),
+                      reinterpret_cast<uint8_t*>(data),
+                      yuv_data->ystride,
+                      bytes_per_line,
+                      height);
+    }
+    // C data.
+    // TODO(b/30119452): These calculations assume YCbCr_420_888.
+    size_t c_len = y_len / 4;
+    uint32_t c_bytes_per_line = bytes_per_line / 2;
+    // V4L2 is packed, meaning the data is stored as contiguous {y, cb, cr}.
+    uint8_t* cb_device = reinterpret_cast<uint8_t*>(data) + y_len;
+    uint8_t* cr_device = cb_device + c_len;
+    size_t step = yuv_data->chroma_step;
+    if (step == 1) {
+      // Still planar.
+      if (yuv_data->cstride == c_bytes_per_line) {
+        // Data should match exactly.
+        memcpy(yuv_data->cb, cb_device, c_len);
+        memcpy(yuv_data->cr, cr_device, c_len);
+      } else {
+        HAL_LOGV("Changing padding on C plane from %u to %u.",
+                 c_bytes_per_line,
+                 yuv_data->cstride);
+        // Wrong padding from V4L2.
+        copyWithPadding(reinterpret_cast<uint8_t*>(yuv_data->cb),
+                        cb_device,
+                        yuv_data->cstride,
+                        c_bytes_per_line,
+                        height / 2);
+        copyWithPadding(reinterpret_cast<uint8_t*>(yuv_data->cr),
+                        cr_device,
+                        yuv_data->cstride,
+                        c_bytes_per_line,
+                        height / 2);
+      }
+    } else {
+      // Desire semiplanar (cb and cr interleaved).
+      HAL_LOGV("Interleaving cb and cr. Padding going from %u to %u.",
+               c_bytes_per_line,
+               yuv_data->cstride);
+      uint32_t c_height = height / 2;
+      uint32_t c_width = camera_buffer->stream->width / 2;
+      // Zero out destination
+      uint8_t* cb_gralloc = reinterpret_cast<uint8_t*>(yuv_data->cb);
+      uint8_t* cr_gralloc = reinterpret_cast<uint8_t*>(yuv_data->cr);
+      memset(cb_gralloc, 0, c_width * c_height * step);
+
+      // Interleaving means we need to copy the cb and cr bytes one by one.
+      for (size_t line = 0; line < c_height; ++line,
+                  cb_gralloc += yuv_data->cstride,
+                  cr_gralloc += yuv_data->cstride,
+                  cb_device += c_bytes_per_line,
+                  cr_device += c_bytes_per_line) {
+        for (size_t i = 0; i < c_width; ++i) {
+          *(cb_gralloc + (i * step)) = *(cb_device + i);
+          *(cr_gralloc + (i * step)) = *(cr_device + i);
+        }
+      }
+    }
+  }
+
+  // Unlock.
+  int res = mModule->unlock(mModule, buffer);
+  if (res) {
+    HAL_LOGE("Failed to unlock buffer at %p", buffer);
+    return -ENODEV;
+  }
+
+  return 0;
+}
+
+int V4L2Gralloc::unlockAllBuffers() {
+  HAL_LOG_ENTER();
+
+  bool failed = false;
+  for (auto const& entry : mBufferMap) {
+    int res = mModule->unlock(mModule, *entry.second->camera_buffer->buffer);
+    if (res) {
+      failed = true;
+    }
+    // When there is a transform to be made, the buffer returned by lock()
+    // is dynamically allocated (to hold the pre-transform data).
+    if (entry.second->transform_dest) {
+      delete[] reinterpret_cast<uint8_t*>(entry.first);
+    }
+    // The BufferData entry is always dynamically allocated in lock().
+    delete entry.second;
+  }
+  mBufferMap.clear();
+
+  // If any unlock failed, return error.
+  if (failed) {
+    return -ENODEV;
+  }
+
+  return 0;
+}
+
+}  // namespace default_camera_hal
diff --git a/modules/camera/3_4/v4l2_gralloc.h b/modules/camera/3_4/v4l2_gralloc.h
new file mode 100644
index 0000000..82129e3
--- /dev/null
+++ b/modules/camera/3_4/v4l2_gralloc.h
@@ -0,0 +1,71 @@
+/*
+ * 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_V4L2_GRALLOC_H_
+#define V4L2_CAMERA_HAL_V4L2_GRALLOC_H_
+
+#include <linux/videodev2.h>
+
+#include <unordered_map>
+
+#include <hardware/camera3.h>
+#include <hardware/gralloc.h>
+#include <system/graphics.h>
+
+namespace v4l2_camera_hal {
+
+// Generously allow up to 6MB (the largest JPEG on the RPi camera is about 5MB).
+static constexpr size_t V4L2_MAX_JPEG_SIZE = 6000000;
+
+// V4L2Gralloc is a wrapper around relevant parts of a gralloc module,
+// with some assistive transformations.
+class V4L2Gralloc {
+ public:
+  // Use this method to create V4L2Gralloc objects. Functionally equivalent
+  // to "new V4L2Gralloc", except that it may return nullptr in case of failure.
+  static V4L2Gralloc* NewV4L2Gralloc();
+  virtual ~V4L2Gralloc();
+
+  // Lock a camera buffer. Uses device buffer length, sets user pointer.
+  int lock(const camera3_stream_buffer_t* camera_buffer,
+           uint32_t bytes_per_line,
+           v4l2_buffer* device_buffer);
+  // Unlock a buffer that was locked by this helper (equality determined
+  // based on buffer user pointer, not the specific object).
+  int unlock(const v4l2_buffer* device_buffer);
+  // Release all held locks.
+  int unlockAllBuffers();
+
+ private:
+  // Constructor is private to allow failing on bad input.
+  // Use NewV4L2Gralloc instead.
+  V4L2Gralloc(const gralloc_module_t* module);
+
+  const gralloc_module_t* mModule;
+
+  struct BufferData {
+    const camera3_stream_buffer_t* camera_buffer;
+    // Below fields only used when a ycbcr format transform is necessary.
+    std::unique_ptr<android_ycbcr> transform_dest;  // nullptr if no transform.
+    uint32_t v4l2_bytes_per_line;
+  };
+  // Map data pointer : BufferData about that buffer.
+  std::unordered_map<void*, const BufferData*> mBufferMap;
+};
+
+}  // namespace default_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_V4L2_GRALLOC_H_
diff --git a/modules/camera/3_4/v4l2_metadata_factory.cpp b/modules/camera/3_4/v4l2_metadata_factory.cpp
new file mode 100644
index 0000000..67556d9
--- /dev/null
+++ b/modules/camera/3_4/v4l2_metadata_factory.cpp
@@ -0,0 +1,571 @@
+/*
+ * Copyright 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_metadata_factory.h"
+
+#include <camera/CameraMetadata.h>
+
+#include "common.h"
+#include "format_metadata_factory.h"
+#include "metadata/boottime_state_delegate.h"
+#include "metadata/control.h"
+#include "metadata/enum_converter.h"
+#include "metadata/metadata_common.h"
+#include "metadata/partial_metadata_factory.h"
+#include "metadata/property.h"
+#include "metadata/scaling_converter.h"
+#include "v4l2_gralloc.h"
+
+namespace v4l2_camera_hal {
+
+// According to spec, each unit of V4L2_CID_AUTO_EXPOSURE_BIAS is 0.001 EV.
+const camera_metadata_rational_t kAeCompensationUnit = {1, 1000};
+// According to spec, each unit of V4L2_CID_EXPOSURE_ABSOLUTE is 100 us.
+const int64_t kV4L2ExposureTimeStepNs = 100000;
+// According to spec, each unit of V4L2_CID_ISO_SENSITIVITY is ISO/1000.
+const int32_t kV4L2SensitivityDenominator = 1000;
+
+int GetV4L2Metadata(std::shared_ptr<V4L2Wrapper> device,
+                    std::unique_ptr<Metadata>* result) {
+  HAL_LOG_ENTER();
+
+  // Open a temporary connection to the device for all the V4L2 querying
+  // that will be happening (this could be done for each component individually,
+  // but doing it here prevents connecting and disconnecting for each one).
+  V4L2Wrapper::Connection temp_connection = V4L2Wrapper::Connection(device);
+  if (temp_connection.status()) {
+    HAL_LOGE("Failed to connect to device: %d.", temp_connection.status());
+    return temp_connection.status();
+  }
+
+  // TODO(b/30035628): Add states.
+
+  PartialMetadataSet components;
+
+  components.insert(NoEffectMenuControl<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},
+      {{CAMERA3_TEMPLATE_STILL_CAPTURE,
+        ANDROID_COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY},
+       {OTHER_TEMPLATES, ANDROID_COLOR_CORRECTION_ABERRATION_MODE_FAST}}));
+
+  // 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.
+  components.insert(std::unique_ptr<PartialMetadataInterface>(
+      new Property<std::array<int32_t, 3>>(ANDROID_CONTROL_MAX_REGIONS,
+                                           {{/*AE*/ 0, /*AWB*/ 0, /*AF*/ 0}})));
+  // TODO(b/30921166): V4L2_CID_AUTO_EXPOSURE_BIAS is an int menu, so
+  // this will be falling back to NoEffect until int menu support is added.
+  components.insert(V4L2ControlOrDefault<int32_t>(
+      ControlType::kSlider,
+      ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION,
+      ANDROID_CONTROL_AE_COMPENSATION_RANGE,
+      device,
+      V4L2_CID_AUTO_EXPOSURE_BIAS,
+      // No scaling necessary, AE_COMPENSATION_STEP handles this.
+      std::make_shared<ScalingConverter<int32_t, int32_t>>(1, 1),
+      0,
+      {{OTHER_TEMPLATES, 0}}));
+  components.insert(std::unique_ptr<PartialMetadataInterface>(
+      new Property<camera_metadata_rational_t>(
+          ANDROID_CONTROL_AE_COMPENSATION_STEP, kAeCompensationUnit)));
+  // TODO(b/31021522): Autofocus subcomponent.
+  components.insert(
+      NoEffectMenuControl<uint8_t>(ANDROID_CONTROL_AF_MODE,
+                                   ANDROID_CONTROL_AF_AVAILABLE_MODES,
+                                   {ANDROID_CONTROL_AF_MODE_OFF}));
+  // TODO(b/31021522): Should read autofocus state from
+  // V4L2_CID_AUTO_FOCUS_STATUS bitmask. The framework gets a little more
+  // complex than that does; there's a whole state-machine table in
+  // the docs (system/media/camera/docs/docs.html).
+  components.insert(FixedState<uint8_t>(ANDROID_CONTROL_AF_STATE,
+                                        ANDROID_CONTROL_AF_STATE_INACTIVE));
+  // TODO(b/31022735): Correctly implement AE & AF triggers that
+  // actually do something. These no effect triggers are even worse than most
+  // of the useless controls in this class, since technically they should
+  // revert back to IDLE eventually after START/CANCEL, but for now they won't
+  // unless IDLE is requested.
+  components.insert(
+      NoEffectMenuControl<uint8_t>(ANDROID_CONTROL_AF_TRIGGER,
+                                   DO_NOT_REPORT_OPTIONS,
+                                   {ANDROID_CONTROL_AF_TRIGGER_IDLE,
+                                    ANDROID_CONTROL_AF_TRIGGER_START,
+                                    ANDROID_CONTROL_AF_TRIGGER_CANCEL}));
+  components.insert(NoEffectMenuControl<uint8_t>(
+      ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER,
+      DO_NOT_REPORT_OPTIONS,
+      {ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER_IDLE,
+       ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER_START,
+       ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL}));
+  components.insert(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,
+      {{CAMERA3_TEMPLATE_MANUAL, ANDROID_CONTROL_AE_ANTIBANDING_MODE_OFF},
+       {OTHER_TEMPLATES, ANDROID_CONTROL_AE_ANTIBANDING_MODE_AUTO}}));
+  std::unique_ptr<PartialMetadataInterface> exposure_time =
+      V4L2Control<int64_t>(ControlType::kSlider,
+                           ANDROID_SENSOR_EXPOSURE_TIME,
+                           ANDROID_SENSOR_INFO_EXPOSURE_TIME_RANGE,
+                           device,
+                           V4L2_CID_EXPOSURE_ABSOLUTE,
+                           std::make_shared<ScalingConverter<int64_t, int32_t>>(
+                               kV4L2ExposureTimeStepNs, 1));
+  // TODO(b/31037072): Sensitivity has additional V4L2 controls
+  // (V4L2_CID_ISO_SENSITIVITY_AUTO), so this control currently has
+  // undefined behavior.
+  // TODO(b/30921166): V4L2_CID_ISO_SENSITIVITY is an int menu, so
+  // this will return nullptr until that is added.
+  std::unique_ptr<PartialMetadataInterface> sensitivity =
+      V4L2Control<int32_t>(ControlType::kSlider,
+                           ANDROID_SENSOR_SENSITIVITY,
+                           ANDROID_SENSOR_INFO_SENSITIVITY_RANGE,
+                           device,
+                           V4L2_CID_ISO_SENSITIVITY,
+                           std::make_shared<ScalingConverter<int32_t, int32_t>>(
+                               1, kV4L2SensitivityDenominator));
+  std::multimap<int32_t, uint8_t> ae_mode_mapping = {
+      {V4L2_EXPOSURE_AUTO, ANDROID_CONTROL_AE_MODE_ON}};
+  if (exposure_time && sensitivity) {
+    // TODO(b/30510395): as part of coordinated 3A component,
+    // if these aren't available don't advertise AE mode OFF, only AUTO.
+    components.insert(std::move(exposure_time));
+    components.insert(std::move(sensitivity));
+    ae_mode_mapping.emplace(V4L2_EXPOSURE_MANUAL, ANDROID_CONTROL_AE_MODE_OFF);
+  }
+  components.insert(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(ae_mode_mapping)),
+      ANDROID_CONTROL_AE_MODE_ON,
+      {{CAMERA3_TEMPLATE_MANUAL, ANDROID_CONTROL_AE_MODE_OFF},
+       {OTHER_TEMPLATES, ANDROID_CONTROL_AE_MODE_ON}}));
+  // Can't get AE status from V4L2.
+  // TODO(b/30510395): If AE mode is OFF, this should switch to INACTIVE.
+  components.insert(FixedState<uint8_t>(ANDROID_CONTROL_AE_STATE,
+                                        ANDROID_CONTROL_AE_STATE_CONVERGED));
+  // 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.
+  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,
+            ANDROID_CONTROL_AWB_MODE_INCANDESCENT},
+           {V4L2_WHITE_BALANCE_FLUORESCENT,
+            ANDROID_CONTROL_AWB_MODE_FLUORESCENT},
+           {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}})),
+      {{CAMERA3_TEMPLATE_MANUAL, ANDROID_CONTROL_AWB_MODE_OFF},
+       {OTHER_TEMPLATES, ANDROID_CONTROL_AWB_MODE_AUTO}}));
+  if (awb) {
+    components.insert(std::move(awb));
+  } else {
+    // Fall back to simpler AWB or even just an ignored control.
+    components.insert(V4L2ControlOrDefault<uint8_t>(
+        ControlType::kMenu,
+        ANDROID_CONTROL_AWB_MODE,
+        ANDROID_CONTROL_AWB_AVAILABLE_MODES,
+        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,
+        {{CAMERA3_TEMPLATE_MANUAL, ANDROID_CONTROL_AWB_MODE_OFF},
+         {OTHER_TEMPLATES, ANDROID_CONTROL_AWB_MODE_AUTO}}));
+  }
+  // TODO(b/31041577): Handle AWB state machine correctly.
+  components.insert(FixedState<uint8_t>(ANDROID_CONTROL_AWB_STATE,
+                                        ANDROID_CONTROL_AWB_STATE_CONVERGED));
+  // TODO(b/31022153): 3A locks.
+  components.insert(std::unique_ptr<PartialMetadataInterface>(
+      new Property<uint8_t>(ANDROID_CONTROL_AE_LOCK_AVAILABLE,
+                            ANDROID_CONTROL_AE_LOCK_AVAILABLE_FALSE)));
+  components.insert(
+      NoEffectMenuControl<uint8_t>(ANDROID_CONTROL_AE_LOCK,
+                                   DO_NOT_REPORT_OPTIONS,
+                                   {ANDROID_CONTROL_AE_LOCK_OFF}));
+  components.insert(std::unique_ptr<PartialMetadataInterface>(
+      new Property<uint8_t>(ANDROID_CONTROL_AWB_LOCK_AVAILABLE,
+                            ANDROID_CONTROL_AWB_LOCK_AVAILABLE_FALSE)));
+  components.insert(
+      NoEffectMenuControl<uint8_t>(ANDROID_CONTROL_AWB_LOCK,
+                                   DO_NOT_REPORT_OPTIONS,
+                                   {ANDROID_CONTROL_AWB_LOCK_OFF}));
+  // 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.
+  // V4L2: BACKLIGHT, DAWN_DUSK, FALL_COLORS, TEXT.
+  components.insert(V4L2ControlOrDefault<uint8_t>(
+      ControlType::kMenu,
+      ANDROID_CONTROL_SCENE_MODE,
+      ANDROID_CONTROL_AVAILABLE_SCENE_MODES,
+      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));
+  // TODO(b/31022612): Scene mode overrides.
+  // 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.
+  components.insert(V4L2ControlOrDefault<uint8_t>(
+      ControlType::kMenu,
+      ANDROID_CONTROL_EFFECT_MODE,
+      ANDROID_CONTROL_AVAILABLE_EFFECTS,
+      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));
+  // TODO(b/31021654): This should behave as a top level switch, not no effect.
+  // Should enforce being set to USE_SCENE_MODE when a scene mode is requested.
+  components.insert(NoEffectMenuControl<uint8_t>(
+      ANDROID_CONTROL_MODE,
+      ANDROID_CONTROL_AVAILABLE_MODES,
+      {ANDROID_CONTROL_MODE_AUTO, ANDROID_CONTROL_MODE_USE_SCENE_MODE}));
+
+  // 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. And if FAST is included, HIGH_QUALITY
+  // is supposed to be included as well.
+  components.insert(NoEffectMenuControl<uint8_t>(
+      ANDROID_EDGE_MODE,
+      ANDROID_EDGE_AVAILABLE_EDGE_MODES,
+      {ANDROID_EDGE_MODE_FAST, ANDROID_EDGE_MODE_HIGH_QUALITY},
+      {{CAMERA3_TEMPLATE_STILL_CAPTURE, ANDROID_EDGE_MODE_HIGH_QUALITY},
+       {OTHER_TEMPLATES, ANDROID_EDGE_MODE_FAST}}));
+
+  // TODO(b/31023454): subcomponents of flash.
+  components.insert(
+      std::unique_ptr<PartialMetadataInterface>(new Property<uint8_t>(
+          ANDROID_FLASH_INFO_AVAILABLE, ANDROID_FLASH_INFO_AVAILABLE_FALSE)));
+  components.insert(FixedState<uint8_t>(ANDROID_FLASH_STATE,
+                                        ANDROID_FLASH_STATE_UNAVAILABLE));
+  components.insert(NoEffectMenuControl<uint8_t>(
+      ANDROID_FLASH_MODE, DO_NOT_REPORT_OPTIONS, {ANDROID_FLASH_MODE_OFF}));
+
+  // TODO(30510395): subcomponents of hotpixel.
+  // No known V4L2 hot pixel correction. But it might be happening,
+  // so we report FAST/HIGH_QUALITY.
+  components.insert(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.
+  components.insert(NoEffectMenuControl<uint8_t>(
+      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.
+  components.insert(
+      NoEffectMenuControl<float>(ANDROID_LENS_APERTURE,
+                                 ANDROID_LENS_INFO_AVAILABLE_APERTURES,
+                                 {2.0}));  // RPi camera v2 is f/2.0.
+  // Always assume external-facing.
+  components.insert(
+      std::unique_ptr<PartialMetadataInterface>(new Property<uint8_t>(
+          ANDROID_LENS_FACING, ANDROID_LENS_FACING_EXTERNAL)));
+  components.insert(
+      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.
+  components.insert(
+      NoEffectMenuControl<float>(ANDROID_LENS_FILTER_DENSITY,
+                                 ANDROID_LENS_INFO_AVAILABLE_FILTER_DENSITIES,
+                                 {0.0}));
+  // V4L2 focal units do not correspond to a particular physical unit.
+  components.insert(
+      std::unique_ptr<PartialMetadataInterface>(new Property<uint8_t>(
+          ANDROID_LENS_INFO_FOCUS_DISTANCE_CALIBRATION,
+          ANDROID_LENS_INFO_FOCUS_DISTANCE_CALIBRATION_UNCALIBRATED)));
+  // TODO(b/31022711): Focus distance component.
+  // Using a NoEffectMenuControl for now because for
+  // fixed-focus it meets expectations. Framework may allow
+  // setting any value and expect it to be clamped to 0, in which
+  // case this will have unexpected behavior (failing on non-0 settings).
+  components.insert(
+      NoEffectMenuControl<float>(ANDROID_LENS_FOCUS_DISTANCE,
+                                 ANDROID_LENS_INFO_MINIMUM_FOCUS_DISTANCE,
+                                 {0}));
+  // Hypefocal distance doesn't mean much for a fixed-focus uncalibrated device.
+  components.insert(std::make_unique<Property<float>>(
+      ANDROID_LENS_INFO_HYPERFOCAL_DISTANCE, 0));
+
+  // No way to know when the lens is moving or not in V4L2.
+  components.insert(
+      FixedState<uint8_t>(ANDROID_LENS_STATE, ANDROID_LENS_STATE_STATIONARY));
+  // No known V4L2 lens shading. But it might be happening,
+  // so report FAST/HIGH_QUALITY.
+  components.insert(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.
+  components.insert(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).
+  components.insert(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));
+  components.insert(NoEffectMenuControl<uint8_t>(
+      ANDROID_LENS_OPTICAL_STABILIZATION_MODE,
+      ANDROID_LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION,
+      {ANDROID_LENS_OPTICAL_STABILIZATION_MODE_OFF}));
+  // TODO(b/31017806): This should definitely have a different default depending
+  // on template.
+  components.insert(NoEffectMenuControl<uint8_t>(
+      ANDROID_CONTROL_CAPTURE_INTENT,
+      DO_NOT_REPORT_OPTIONS,
+      {ANDROID_CONTROL_CAPTURE_INTENT_CUSTOM,
+       ANDROID_CONTROL_CAPTURE_INTENT_PREVIEW,
+       ANDROID_CONTROL_CAPTURE_INTENT_STILL_CAPTURE,
+       ANDROID_CONTROL_CAPTURE_INTENT_VIDEO_RECORD,
+       ANDROID_CONTROL_CAPTURE_INTENT_VIDEO_SNAPSHOT,
+       ANDROID_CONTROL_CAPTURE_INTENT_ZERO_SHUTTER_LAG,
+       ANDROID_CONTROL_CAPTURE_INTENT_MANUAL},
+      {{CAMERA3_TEMPLATE_PREVIEW, ANDROID_CONTROL_CAPTURE_INTENT_PREVIEW},
+       {CAMERA3_TEMPLATE_STILL_CAPTURE,
+        ANDROID_CONTROL_CAPTURE_INTENT_STILL_CAPTURE},
+       {CAMERA3_TEMPLATE_VIDEO_RECORD,
+        ANDROID_CONTROL_CAPTURE_INTENT_VIDEO_RECORD},
+       {CAMERA3_TEMPLATE_VIDEO_SNAPSHOT,
+        ANDROID_CONTROL_CAPTURE_INTENT_VIDEO_SNAPSHOT},
+       {CAMERA3_TEMPLATE_ZERO_SHUTTER_LAG,
+        ANDROID_CONTROL_CAPTURE_INTENT_ZERO_SHUTTER_LAG},
+       {CAMERA3_TEMPLATE_MANUAL, ANDROID_CONTROL_CAPTURE_INTENT_MANUAL},
+       {OTHER_TEMPLATES, ANDROID_CONTROL_CAPTURE_INTENT_CUSTOM}}));
+
+  // Unable to control noise reduction in V4L2 devices,
+  // but FAST is allowed to be the same as OFF,
+  // and HIGH_QUALITY can be the same as FAST.
+  components.insert(NoEffectMenuControl<uint8_t>(
+      ANDROID_NOISE_REDUCTION_MODE,
+      ANDROID_NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES,
+      {ANDROID_NOISE_REDUCTION_MODE_FAST,
+       ANDROID_NOISE_REDUCTION_MODE_HIGH_QUALITY},
+      {{CAMERA3_TEMPLATE_STILL_CAPTURE,
+        ANDROID_NOISE_REDUCTION_MODE_HIGH_QUALITY},
+       {OTHER_TEMPLATES, 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.
+  components.insert(NoEffectMenuControl<std::array<int32_t, 2>>(
+      ANDROID_JPEG_THUMBNAIL_SIZE,
+      ANDROID_JPEG_AVAILABLE_THUMBNAIL_SIZES,
+      {{{0, 0}}}));
+  // TODO(b/31022752): Get this from the device,
+  // not constant (from V4L2Gralloc.h).
+  components.insert(std::unique_ptr<PartialMetadataInterface>(
+      new Property<int32_t>(ANDROID_JPEG_MAX_SIZE, V4L2_MAX_JPEG_SIZE)));
+  // TODO(b/31021672): Other JPEG controls (GPS, quality, orientation).
+  // TODO(b/29939583): V4L2 can only support 1 stream at a time.
+  // For now, just reporting minimum allowable for LIMITED devices.
+  components.insert(std::unique_ptr<PartialMetadataInterface>(
+      new Property<std::array<int32_t, 3>>(
+          ANDROID_REQUEST_MAX_NUM_OUTPUT_STREAMS,
+          {{/* Raw */ 0, /* Non-stalling */ 2, /* Stalling */ 1}})));
+  // Reprocessing not supported.
+  components.insert(std::unique_ptr<PartialMetadataInterface>(
+      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.
+  components.insert(std::unique_ptr<PartialMetadataInterface>(
+      new Property<uint8_t>(ANDROID_REQUEST_PIPELINE_MAX_DEPTH, 4)));
+  components.insert(FixedState<uint8_t>(ANDROID_REQUEST_PIPELINE_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.
+  components.insert(
+      std::unique_ptr<PartialMetadataInterface>(new Property<int32_t>(
+          ANDROID_SYNC_MAX_LATENCY, ANDROID_SYNC_MAX_LATENCY_UNKNOWN)));
+  // Never know when controls are synced.
+  components.insert(FixedState<int64_t>(ANDROID_SYNC_FRAME_NUMBER,
+                                        ANDROID_SYNC_FRAME_NUMBER_UNKNOWN));
+
+  // TODO(b/31022480): subcomponents of cropping/sensors.
+  // Need ANDROID_SCALER_CROP_REGION control support.
+  // 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.
+  components.insert(std::unique_ptr<PartialMetadataInterface>(
+      new Property<float>(ANDROID_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM, 1)));
+  components.insert(std::unique_ptr<PartialMetadataInterface>(
+      new Property<uint8_t>(ANDROID_SCALER_CROPPING_TYPE,
+                            ANDROID_SCALER_CROPPING_TYPE_FREEFORM)));
+  // Spoof pixel array size for now, eventually get from CROPCAP.
+  std::array<int32_t, 2> pixel_array_size = {{640, 480}};
+  components.insert(std::unique_ptr<PartialMetadataInterface>(
+      new Property<std::array<int32_t, 2>>(ANDROID_SENSOR_INFO_PIXEL_ARRAY_SIZE,
+                                           pixel_array_size)));
+  // Active array size is {x-offset, y-offset, width, height}, relative to
+  // the pixel array size, with {0, 0} being the top left. Since there's no way
+  // to get this in V4L2, assume the full pixel array is the active array.
+  std::array<int32_t, 4> active_array_size = {
+      {0, 0, pixel_array_size[0], pixel_array_size[1]}};
+  components.insert(std::unique_ptr<PartialMetadataInterface>(
+      new Property<std::array<int32_t, 4>>(
+          ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE, active_array_size)));
+  // This is really more freeform than a menu control, but since we're
+  // restricting it to not being used anyways this works for now.
+  components.insert(NoEffectMenuControl<std::array<int32_t, 4>>(
+      ANDROID_SCALER_CROP_REGION, DO_NOT_REPORT_OPTIONS, {active_array_size}));
+  // 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.
+  components.insert(std::unique_ptr<PartialMetadataInterface>(
+      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.
+  components.insert(std::unique_ptr<PartialMetadataInterface>(
+      new Property<uint8_t>(ANDROID_SENSOR_INFO_TIMESTAMP_SOURCE,
+                            ANDROID_SENSOR_INFO_TIMESTAMP_SOURCE_UNKNOWN)));
+  components.insert(std::make_unique<State<int64_t>>(
+      ANDROID_SENSOR_TIMESTAMP, std::make_unique<BoottimeStateDelegate>()));
+  // No way to actually get shutter skew from V4L2.
+  components.insert(
+      FixedState<int64_t>(ANDROID_SENSOR_ROLLING_SHUTTER_SKEW, 0));
+  // No way to actually get orientation from V4L2.
+  components.insert(std::unique_ptr<PartialMetadataInterface>(
+      new Property<int32_t>(ANDROID_SENSOR_ORIENTATION, 0)));
+  // TODO(b/31023611): Sensor frame duration. Range should
+  // be dependent on the stream configuration being used.
+  // No test patterns supported.
+  components.insert(
+      NoEffectMenuControl<int32_t>(ANDROID_SENSOR_TEST_PATTERN_MODE,
+                                   ANDROID_SENSOR_AVAILABLE_TEST_PATTERN_MODES,
+                                   {ANDROID_SENSOR_TEST_PATTERN_MODE_OFF}));
+
+  // TODO(b/30510395): subcomponents of face detection.
+  // Face detection not supported.
+  components.insert(NoEffectMenuControl<uint8_t>(
+      ANDROID_STATISTICS_FACE_DETECT_MODE,
+      ANDROID_STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES,
+      {ANDROID_STATISTICS_FACE_DETECT_MODE_OFF}));
+  components.insert(std::unique_ptr<PartialMetadataInterface>(
+      new Property<int32_t>(ANDROID_STATISTICS_INFO_MAX_FACE_COUNT, 0)));
+
+  // No way to get detected scene flicker from V4L2.
+  components.insert(FixedState<uint8_t>(ANDROID_STATISTICS_SCENE_FLICKER,
+                                        ANDROID_STATISTICS_SCENE_FLICKER_NONE));
+
+  // TOOD(b/31023265): V4L2_CID_FLASH_INDICATOR_INTENSITY could be queried
+  // to see if there's a transmit LED. Would need to translate HAL off/on
+  // enum to slider min/max value. For now, no LEDs available.
+  components.insert(std::unique_ptr<PartialMetadataInterface>(
+      new Property<uint8_t>(ANDROID_LED_AVAILABLE_LEDS, {})));
+
+  /* Capabilities. */
+  // The V4L2Metadata pretends to at least meet the
+  // "LIMITED" and "BACKWARD_COMPATIBLE" functionality requirements.
+  components.insert(std::unique_ptr<PartialMetadataInterface>(
+      new Property<uint8_t>(ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL,
+                            ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED)));
+  components.insert(std::unique_ptr<PartialMetadataInterface>(
+      new Property<std::vector<uint8_t>>(
+          ANDROID_REQUEST_AVAILABLE_CAPABILITIES,
+          {ANDROID_REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE})));
+
+  // Request is unused, and can be any value,
+  // but that value needs to be propagated.
+  components.insert(NoEffectOptionlessControl<int32_t>(ANDROID_REQUEST_ID, 0));
+
+  // Metadata is returned in a single result; not multiple pieces.
+  components.insert(std::make_unique<Property<int32_t>>(
+      ANDROID_REQUEST_PARTIAL_RESULT_COUNT, 1));
+
+  int res =
+      AddFormatComponents(device, std::inserter(components, components.end()));
+  if (res) {
+    HAL_LOGE("Failed to initialize format components.");
+    return res;
+  }
+
+  *result = std::make_unique<Metadata>(std::move(components));
+  return 0;
+}
+
+}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/v4l2_metadata_factory.h b/modules/camera/3_4/v4l2_metadata_factory.h
new file mode 100644
index 0000000..f25a370
--- /dev/null
+++ b/modules/camera/3_4/v4l2_metadata_factory.h
@@ -0,0 +1,34 @@
+/*
+ * 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_V4L2_METADATA_FACTORY_H_
+#define V4L2_CAMERA_HAL_V4L2_METADATA_FACTORY_H_
+
+#include <memory>
+
+#include "metadata/metadata.h"
+#include "v4l2_wrapper.h"
+
+namespace v4l2_camera_hal {
+
+// A static function to get a Metadata object populated with V4L2 or other
+// controls as appropriate.
+int GetV4L2Metadata(std::shared_ptr<V4L2Wrapper> device,
+                    std::unique_ptr<Metadata>* result);
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_V4L2_METADATA_FACTORY_H_
diff --git a/modules/camera/3_4/v4l2_wrapper.cpp b/modules/camera/3_4/v4l2_wrapper.cpp
new file mode 100644
index 0000000..3fafffd
--- /dev/null
+++ b/modules/camera/3_4/v4l2_wrapper.cpp
@@ -0,0 +1,629 @@
+/*
+ * Copyright 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_wrapper.h"
+
+#include <algorithm>
+#include <array>
+#include <limits>
+#include <mutex>
+#include <vector>
+
+#include <fcntl.h>
+#include <linux/videodev2.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <android-base/unique_fd.h>
+
+#include "common.h"
+#include "stream_format.h"
+#include "v4l2_gralloc.h"
+
+namespace v4l2_camera_hal {
+
+const int32_t kStandardSizes[][2] = {{640, 480}, {320, 240}};
+
+V4L2Wrapper* V4L2Wrapper::NewV4L2Wrapper(const std::string device_path) {
+  std::unique_ptr<V4L2Gralloc> gralloc(V4L2Gralloc::NewV4L2Gralloc());
+  if (!gralloc) {
+    HAL_LOGE("Failed to initialize gralloc helper.");
+    return nullptr;
+  }
+
+  return new V4L2Wrapper(device_path, std::move(gralloc));
+}
+
+V4L2Wrapper::V4L2Wrapper(const std::string device_path,
+                         std::unique_ptr<V4L2Gralloc> gralloc)
+    : device_path_(std::move(device_path)),
+      gralloc_(std::move(gralloc)),
+      connection_count_(0) {}
+
+V4L2Wrapper::~V4L2Wrapper() {}
+
+int V4L2Wrapper::Connect() {
+  HAL_LOG_ENTER();
+  std::lock_guard<std::mutex> lock(connection_lock_);
+
+  if (connected()) {
+    HAL_LOGV("Camera device %s is already connected.", device_path_.c_str());
+    ++connection_count_;
+    return 0;
+  }
+
+  // Open in nonblocking mode (DQBUF may return EAGAIN).
+  int fd = TEMP_FAILURE_RETRY(open(device_path_.c_str(), O_RDWR | O_NONBLOCK));
+  if (fd < 0) {
+    HAL_LOGE("failed to open %s (%s)", device_path_.c_str(), strerror(errno));
+    return -ENODEV;
+  }
+  device_fd_.reset(fd);
+  ++connection_count_;
+
+  // Check if this connection has the extended control query capability.
+  v4l2_query_ext_ctrl query;
+  query.id = V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND;
+  extended_query_supported_ = (IoctlLocked(VIDIOC_QUERY_EXT_CTRL, &query) == 0);
+
+  // TODO(b/29185945): confirm this is a supported device.
+  // This is checked by the HAL, but the device at device_path_ may
+  // not be the same one that was there when the HAL was loaded.
+  // (Alternatively, better hotplugging support may make this unecessary
+  // by disabling cameras that get disconnected and checking newly connected
+  // cameras, so Connect() is never called on an unsupported camera)
+  return 0;
+}
+
+void V4L2Wrapper::Disconnect() {
+  HAL_LOG_ENTER();
+  std::lock_guard<std::mutex> lock(connection_lock_);
+
+  if (connection_count_ == 0) {
+    // Not connected.
+    HAL_LOGE("Camera device %s is not connected, cannot disconnect.",
+             device_path_.c_str());
+    return;
+  }
+
+  --connection_count_;
+  if (connection_count_ > 0) {
+    HAL_LOGV("Disconnected from camera device %s. %d connections remain.",
+             device_path_.c_str());
+    return;
+  }
+
+  device_fd_.reset(-1);  // Includes close().
+  format_.reset();
+  buffers_.clear();
+  // Closing the device releases all queued buffers back to the user.
+  gralloc_->unlockAllBuffers();
+}
+
+// Helper function. Should be used instead of ioctl throughout this class.
+template <typename T>
+int V4L2Wrapper::IoctlLocked(int request, T data) {
+  // Potentially called so many times logging entry is a bad idea.
+  std::lock_guard<std::mutex> lock(device_lock_);
+
+  if (!connected()) {
+    HAL_LOGE("Device %s not connected.", device_path_.c_str());
+    return -ENODEV;
+  }
+  return TEMP_FAILURE_RETRY(ioctl(device_fd_.get(), request, data));
+}
+
+int V4L2Wrapper::StreamOn() {
+  if (!format_) {
+    HAL_LOGE("Stream format must be set before turning on stream.");
+    return -EINVAL;
+  }
+
+  int32_t type = format_->type();
+  if (IoctlLocked(VIDIOC_STREAMON, &type) < 0) {
+    HAL_LOGE("STREAMON fails: %s", strerror(errno));
+    return -ENODEV;
+  }
+
+  HAL_LOGV("Stream turned on.");
+  return 0;
+}
+
+int V4L2Wrapper::StreamOff() {
+  if (!format_) {
+    // Can't have turned on the stream without format being set,
+    // so nothing to turn off here.
+    return 0;
+  }
+
+  int32_t type = format_->type();
+  int res = IoctlLocked(VIDIOC_STREAMOFF, &type);
+  // Calling STREAMOFF releases all queued buffers back to the user.
+  int gralloc_res = gralloc_->unlockAllBuffers();
+  // No buffers in flight.
+  for (size_t i = 0; i < buffers_.size(); ++i) {
+    buffers_[i] = false;
+  }
+  if (res < 0) {
+    HAL_LOGE("STREAMOFF fails: %s", strerror(errno));
+    return -ENODEV;
+  }
+  if (gralloc_res < 0) {
+    HAL_LOGE("Failed to unlock all buffers after turning stream off.");
+    return gralloc_res;
+  }
+
+  HAL_LOGV("Stream turned off.");
+  return 0;
+}
+
+int V4L2Wrapper::QueryControl(uint32_t control_id,
+                              v4l2_query_ext_ctrl* result) {
+  int res;
+
+  memset(result, 0, sizeof(*result));
+
+  if (extended_query_supported_) {
+    result->id = control_id;
+    res = IoctlLocked(VIDIOC_QUERY_EXT_CTRL, result);
+    // Assuming the operation was supported (not ENOTTY), no more to do.
+    if (errno != ENOTTY) {
+      if (res) {
+        HAL_LOGE("QUERY_EXT_CTRL fails: %s", strerror(errno));
+        return -ENODEV;
+      }
+      return 0;
+    }
+  }
+
+  // Extended control querying not supported, fall back to basic control query.
+  v4l2_queryctrl query;
+  query.id = control_id;
+  if (IoctlLocked(VIDIOC_QUERYCTRL, &query)) {
+    HAL_LOGE("QUERYCTRL fails: %s", strerror(errno));
+    return -ENODEV;
+  }
+
+  // Convert the basic result to the extended result.
+  result->id = query.id;
+  result->type = query.type;
+  memcpy(result->name, query.name, sizeof(query.name));
+  result->minimum = query.minimum;
+  if (query.type == V4L2_CTRL_TYPE_BITMASK) {
+    // According to the V4L2 documentation, when type is BITMASK,
+    // max and default should be interpreted as __u32. Practically,
+    // this means the conversion from 32 bit to 64 will pad with 0s not 1s.
+    result->maximum = static_cast<uint32_t>(query.maximum);
+    result->default_value = static_cast<uint32_t>(query.default_value);
+  } else {
+    result->maximum = query.maximum;
+    result->default_value = query.default_value;
+  }
+  result->step = static_cast<uint32_t>(query.step);
+  result->flags = query.flags;
+  result->elems = 1;
+  switch (result->type) {
+    case V4L2_CTRL_TYPE_INTEGER64:
+      result->elem_size = sizeof(int64_t);
+      break;
+    case V4L2_CTRL_TYPE_STRING:
+      result->elem_size = result->maximum + 1;
+      break;
+    default:
+      result->elem_size = sizeof(int32_t);
+      break;
+  }
+
+  return 0;
+}
+
+int V4L2Wrapper::GetControl(uint32_t control_id, int32_t* value) {
+  // For extended controls (any control class other than "user"),
+  // G_EXT_CTRL must be used instead of G_CTRL.
+  if (V4L2_CTRL_ID2CLASS(control_id) != V4L2_CTRL_CLASS_USER) {
+    v4l2_ext_control control;
+    v4l2_ext_controls controls;
+    memset(&control, 0, sizeof(control));
+    memset(&controls, 0, sizeof(controls));
+
+    control.id = control_id;
+    controls.ctrl_class = V4L2_CTRL_ID2CLASS(control_id);
+    controls.count = 1;
+    controls.controls = &control;
+
+    if (IoctlLocked(VIDIOC_G_EXT_CTRLS, &controls) < 0) {
+      HAL_LOGE("G_EXT_CTRLS fails: %s", strerror(errno));
+      return -ENODEV;
+    }
+    *value = control.value;
+  } else {
+    v4l2_control control{control_id, 0};
+    if (IoctlLocked(VIDIOC_G_CTRL, &control) < 0) {
+      HAL_LOGE("G_CTRL fails: %s", strerror(errno));
+      return -ENODEV;
+    }
+    *value = control.value;
+  }
+  return 0;
+}
+
+int V4L2Wrapper::SetControl(uint32_t control_id,
+                            int32_t desired,
+                            int32_t* result) {
+  int32_t result_value = 0;
+
+  // TODO(b/29334616): When async, this may need to check if the stream
+  // is on, and if so, lock it off while setting format. Need to look
+  // into if V4L2 supports adjusting controls while the stream is on.
+
+  // For extended controls (any control class other than "user"),
+  // S_EXT_CTRL must be used instead of S_CTRL.
+  if (V4L2_CTRL_ID2CLASS(control_id) != V4L2_CTRL_CLASS_USER) {
+    v4l2_ext_control control;
+    v4l2_ext_controls controls;
+    memset(&control, 0, sizeof(control));
+    memset(&controls, 0, sizeof(controls));
+
+    control.id = control_id;
+    control.value = desired;
+    controls.ctrl_class = V4L2_CTRL_ID2CLASS(control_id);
+    controls.count = 1;
+    controls.controls = &control;
+
+    if (IoctlLocked(VIDIOC_S_EXT_CTRLS, &controls) < 0) {
+      HAL_LOGE("S_EXT_CTRLS fails: %s", strerror(errno));
+      return -ENODEV;
+    }
+    result_value = control.value;
+  } else {
+    v4l2_control control{control_id, desired};
+    if (IoctlLocked(VIDIOC_S_CTRL, &control) < 0) {
+      HAL_LOGE("S_CTRL fails: %s", strerror(errno));
+      return -ENODEV;
+    }
+    result_value = control.value;
+  }
+
+  // If the caller wants to know the result, pass it back.
+  if (result != nullptr) {
+    *result = result_value;
+  }
+  return 0;
+}
+
+int V4L2Wrapper::GetFormats(std::set<uint32_t>* v4l2_formats) {
+  HAL_LOG_ENTER();
+
+  v4l2_fmtdesc format_query;
+  memset(&format_query, 0, sizeof(format_query));
+  // TODO(b/30000211): multiplanar support.
+  format_query.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+  while (IoctlLocked(VIDIOC_ENUM_FMT, &format_query) >= 0) {
+    v4l2_formats->insert(format_query.pixelformat);
+    ++format_query.index;
+  }
+
+  if (errno != EINVAL) {
+    HAL_LOGE(
+        "ENUM_FMT fails at index %d: %s", format_query.index, strerror(errno));
+    return -ENODEV;
+  }
+  return 0;
+}
+
+int V4L2Wrapper::GetFormatFrameSizes(uint32_t v4l2_format,
+                                     std::set<std::array<int32_t, 2>>* sizes) {
+  v4l2_frmsizeenum size_query;
+  memset(&size_query, 0, sizeof(size_query));
+  size_query.pixel_format = v4l2_format;
+  if (IoctlLocked(VIDIOC_ENUM_FRAMESIZES, &size_query) < 0) {
+    HAL_LOGE("ENUM_FRAMESIZES failed: %s", strerror(errno));
+    return -ENODEV;
+  }
+  if (size_query.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
+    // Discrete: enumerate all sizes using VIDIOC_ENUM_FRAMESIZES.
+    // Assuming that a driver with discrete frame sizes has a reasonable number
+    // of them.
+    do {
+      sizes->insert({{{static_cast<int32_t>(size_query.discrete.width),
+                       static_cast<int32_t>(size_query.discrete.height)}}});
+      ++size_query.index;
+    } while (IoctlLocked(VIDIOC_ENUM_FRAMESIZES, &size_query) >= 0);
+    if (errno != EINVAL) {
+      HAL_LOGE("ENUM_FRAMESIZES fails at index %d: %s",
+               size_query.index,
+               strerror(errno));
+      return -ENODEV;
+    }
+  } else {
+    // Continuous/Step-wise: based on the stepwise struct returned by the query.
+    // Fully listing all possible sizes, with large enough range/small enough
+    // step size, may produce far too many potential sizes. Instead, find the
+    // closest to a set of standard sizes.
+    for (const auto size : kStandardSizes) {
+      // Find the closest size, rounding up.
+      uint32_t desired_width = size[0];
+      uint32_t desired_height = size[1];
+      if (desired_width < size_query.stepwise.min_width ||
+          desired_height < size_query.stepwise.min_height) {
+        HAL_LOGV("Standard size %u x %u is too small for format %d",
+                 desired_width,
+                 desired_height,
+                 v4l2_format);
+        continue;
+      } else if (desired_width > size_query.stepwise.max_width &&
+                 desired_height > size_query.stepwise.max_height) {
+        HAL_LOGV("Standard size %u x %u is too big for format %d",
+                 desired_width,
+                 desired_height,
+                 v4l2_format);
+        continue;
+      }
+
+      // Round up.
+      uint32_t width_steps = (desired_width - size_query.stepwise.min_width +
+                              size_query.stepwise.step_width - 1) /
+                             size_query.stepwise.step_width;
+      uint32_t height_steps = (desired_height - size_query.stepwise.min_height +
+                               size_query.stepwise.step_height - 1) /
+                              size_query.stepwise.step_height;
+      sizes->insert(
+          {{{static_cast<int32_t>(size_query.stepwise.min_width +
+                                  width_steps * size_query.stepwise.step_width),
+             static_cast<int32_t>(size_query.stepwise.min_height +
+                                  height_steps *
+                                      size_query.stepwise.step_height)}}});
+    }
+  }
+  return 0;
+}
+
+// Converts a v4l2_fract with units of seconds to an int64_t with units of ns.
+inline int64_t FractToNs(const v4l2_fract& fract) {
+  return (1000000000LL * fract.numerator) / fract.denominator;
+}
+
+int V4L2Wrapper::GetFormatFrameDurationRange(
+    uint32_t v4l2_format,
+    const std::array<int32_t, 2>& size,
+    std::array<int64_t, 2>* duration_range) {
+  // Potentially called so many times logging entry is a bad idea.
+
+  v4l2_frmivalenum duration_query;
+  memset(&duration_query, 0, sizeof(duration_query));
+  duration_query.pixel_format = v4l2_format;
+  duration_query.width = size[0];
+  duration_query.height = size[1];
+  if (IoctlLocked(VIDIOC_ENUM_FRAMEINTERVALS, &duration_query) < 0) {
+    HAL_LOGE("ENUM_FRAMEINTERVALS failed: %s", strerror(errno));
+    return -ENODEV;
+  }
+
+  int64_t min = std::numeric_limits<int64_t>::max();
+  int64_t max = std::numeric_limits<int64_t>::min();
+  if (duration_query.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
+    // Discrete: enumerate all durations using VIDIOC_ENUM_FRAMEINTERVALS.
+    do {
+      min = std::min(min, FractToNs(duration_query.discrete));
+      max = std::max(max, FractToNs(duration_query.discrete));
+      ++duration_query.index;
+    } while (IoctlLocked(VIDIOC_ENUM_FRAMEINTERVALS, &duration_query) >= 0);
+    if (errno != EINVAL) {
+      HAL_LOGE("ENUM_FRAMEINTERVALS fails at index %d: %s",
+               duration_query.index,
+               strerror(errno));
+      return -ENODEV;
+    }
+  } else {
+    // Continuous/Step-wise: simply convert the given min and max.
+    min = FractToNs(duration_query.stepwise.min);
+    max = FractToNs(duration_query.stepwise.max);
+  }
+  (*duration_range)[0] = min;
+  (*duration_range)[1] = max;
+  return 0;
+}
+
+int V4L2Wrapper::SetFormat(const StreamFormat& desired_format,
+                           uint32_t* result_max_buffers) {
+  HAL_LOG_ENTER();
+
+  if (format_ && desired_format == *format_) {
+    HAL_LOGV("Already in correct format, skipping format setting.");
+    *result_max_buffers = buffers_.size();
+    return 0;
+  }
+
+  // Not in the correct format, set the new one.
+
+  if (format_) {
+    // If we had an old format, first request 0 buffers to inform the device
+    // we're no longer using any previously "allocated" buffers from the old
+    // format. This seems like it shouldn't be necessary for USERPTR memory,
+    // and/or should happen from turning the stream off, but the driver
+    // complained. May be a driver issue, or may be intended behavior.
+    int res = RequestBuffers(0);
+    if (res) {
+      return res;
+    }
+  }
+
+  // Set the camera to the new format.
+  v4l2_format new_format;
+  desired_format.FillFormatRequest(&new_format);
+  // TODO(b/29334616): When async, this will need to check if the stream
+  // is on, and if so, lock it off while setting format.
+
+  if (IoctlLocked(VIDIOC_S_FMT, &new_format) < 0) {
+    HAL_LOGE("S_FMT failed: %s", strerror(errno));
+    return -ENODEV;
+  }
+
+  // Check that the driver actually set to the requested values.
+  if (desired_format != new_format) {
+    HAL_LOGE("Device doesn't support desired stream configuration.");
+    return -EINVAL;
+  }
+
+  // Keep track of our new format.
+  format_.reset(new StreamFormat(new_format));
+
+  // Format changed, request new buffers.
+  int res = RequestBuffers(1);
+  if (res) {
+    HAL_LOGE("Requesting buffers for new format failed.");
+    return res;
+  }
+  *result_max_buffers = buffers_.size();
+  return 0;
+}
+
+int V4L2Wrapper::RequestBuffers(uint32_t num_requested) {
+  v4l2_requestbuffers req_buffers;
+  memset(&req_buffers, 0, sizeof(req_buffers));
+  req_buffers.type = format_->type();
+  req_buffers.memory = V4L2_MEMORY_USERPTR;
+  req_buffers.count = num_requested;
+
+  int res = IoctlLocked(VIDIOC_REQBUFS, &req_buffers);
+  // Calling REQBUFS releases all queued buffers back to the user.
+  int gralloc_res = gralloc_->unlockAllBuffers();
+  if (res < 0) {
+    HAL_LOGE("REQBUFS failed: %s", strerror(errno));
+    return -ENODEV;
+  }
+  if (gralloc_res < 0) {
+    HAL_LOGE("Failed to unlock all buffers when setting up new buffers.");
+    return gralloc_res;
+  }
+
+  // V4L2 will set req_buffers.count to a number of buffers it can handle.
+  if (num_requested > 0 && req_buffers.count < 1) {
+    HAL_LOGE("REQBUFS claims it can't handle any buffers.");
+    return -ENODEV;
+  }
+  buffers_.resize(req_buffers.count, false);
+
+  return 0;
+}
+
+int V4L2Wrapper::EnqueueBuffer(const camera3_stream_buffer_t* camera_buffer,
+                               uint32_t* enqueued_index) {
+  if (!format_) {
+    HAL_LOGE("Stream format must be set before enqueuing buffers.");
+    return -ENODEV;
+  }
+
+  // Find a free buffer index. Could use some sort of persistent hinting
+  // here to improve expected efficiency, but buffers_.size() is expected
+  // to be low enough (<10 experimentally) that it's not worth it.
+  int index = -1;
+  {
+    std::lock_guard<std::mutex> guard(buffer_queue_lock_);
+    for (int i = 0; i < buffers_.size(); ++i) {
+      if (!buffers_[i]) {
+        index = i;
+        break;
+      }
+    }
+  }
+  if (index < 0) {
+    // Note: The HAL should be tracking the number of buffers in flight
+    // for each stream, and should never overflow the device.
+    HAL_LOGE("Cannot enqueue buffer: stream is already full.");
+    return -ENODEV;
+  }
+
+  // Set up a v4l2 buffer struct.
+  v4l2_buffer device_buffer;
+  memset(&device_buffer, 0, sizeof(device_buffer));
+  device_buffer.type = format_->type();
+  device_buffer.index = index;
+
+  // Use QUERYBUF to ensure our buffer/device is in good shape,
+  // and fill out remaining fields.
+  if (IoctlLocked(VIDIOC_QUERYBUF, &device_buffer) < 0) {
+    HAL_LOGE("QUERYBUF fails: %s", strerror(errno));
+    return -ENODEV;
+  }
+
+  // Lock the buffer for writing (fills in the user pointer field).
+  int res =
+      gralloc_->lock(camera_buffer, format_->bytes_per_line(), &device_buffer);
+  if (res) {
+    HAL_LOGE("Gralloc failed to lock buffer.");
+    return res;
+  }
+  if (IoctlLocked(VIDIOC_QBUF, &device_buffer) < 0) {
+    HAL_LOGE("QBUF fails: %s", strerror(errno));
+    gralloc_->unlock(&device_buffer);
+    return -ENODEV;
+  }
+
+  // Mark the buffer as in flight.
+  std::lock_guard<std::mutex> guard(buffer_queue_lock_);
+  buffers_[index] = true;
+
+  if (enqueued_index) {
+    *enqueued_index = index;
+  }
+  return 0;
+}
+
+int V4L2Wrapper::DequeueBuffer(uint32_t* dequeued_index) {
+  if (!format_) {
+    HAL_LOGV(
+        "Format not set, so stream can't be on, "
+        "so no buffers available for dequeueing");
+    return -EAGAIN;
+  }
+
+  v4l2_buffer buffer;
+  memset(&buffer, 0, sizeof(buffer));
+  buffer.type = format_->type();
+  buffer.memory = V4L2_MEMORY_USERPTR;
+  int res = IoctlLocked(VIDIOC_DQBUF, &buffer);
+  if (res) {
+    if (errno == EAGAIN) {
+      // Expected failure.
+      return -EAGAIN;
+    } else {
+      // Unexpected failure.
+      HAL_LOGE("DQBUF fails: %s", strerror(errno));
+      return -ENODEV;
+    }
+  }
+
+  // Mark the buffer as no longer in flight.
+  {
+    std::lock_guard<std::mutex> guard(buffer_queue_lock_);
+    buffers_[buffer.index] = false;
+  }
+
+  // Now that we're done painting the buffer, we can unlock it.
+  res = gralloc_->unlock(&buffer);
+  if (res) {
+    HAL_LOGE("Gralloc failed to unlock buffer after dequeueing.");
+    return res;
+  }
+
+  if (dequeued_index) {
+    *dequeued_index = buffer.index;
+  }
+  return 0;
+}
+
+}  // namespace v4l2_camera_hal
diff --git a/modules/camera/3_4/v4l2_wrapper.h b/modules/camera/3_4/v4l2_wrapper.h
new file mode 100644
index 0000000..c3ad272
--- /dev/null
+++ b/modules/camera/3_4/v4l2_wrapper.h
@@ -0,0 +1,133 @@
+/*
+ * 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_V4L2_WRAPPER_H_
+#define V4L2_CAMERA_HAL_V4L2_WRAPPER_H_
+
+#include <array>
+#include <memory>
+#include <mutex>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+
+#include "common.h"
+#include "stream_format.h"
+#include "v4l2_gralloc.h"
+
+namespace v4l2_camera_hal {
+class V4L2Wrapper {
+ public:
+  // Use this method to create V4L2Wrapper objects. Functionally equivalent
+  // to "new V4L2Wrapper", except that it may return nullptr in case of failure.
+  static V4L2Wrapper* NewV4L2Wrapper(const std::string device_path);
+  virtual ~V4L2Wrapper();
+
+  // Helper class to ensure all opened connections are closed.
+  class Connection {
+   public:
+    Connection(std::shared_ptr<V4L2Wrapper> device)
+        : device_(std::move(device)), connect_result_(device_->Connect()) {}
+    ~Connection() {
+      if (connect_result_ == 0) {
+        device_->Disconnect();
+      }
+    }
+    // Check whether the connection succeeded or not.
+    inline int status() const { return connect_result_; }
+
+   private:
+    std::shared_ptr<V4L2Wrapper> device_;
+    const int connect_result_;
+  };
+
+  // Turn the stream on or off.
+  virtual int StreamOn();
+  virtual int StreamOff();
+  // Manage controls.
+  virtual int QueryControl(uint32_t control_id, v4l2_query_ext_ctrl* result);
+  virtual int GetControl(uint32_t control_id, int32_t* value);
+  virtual int SetControl(uint32_t control_id,
+                         int32_t desired,
+                         int32_t* result = nullptr);
+  // Manage format.
+  virtual int GetFormats(std::set<uint32_t>* v4l2_formats);
+  virtual int GetFormatFrameSizes(uint32_t v4l2_format,
+                                  std::set<std::array<int32_t, 2>>* sizes);
+  // Durations are returned in ns.
+  virtual int GetFormatFrameDurationRange(
+      uint32_t v4l2_format,
+      const std::array<int32_t, 2>& size,
+      std::array<int64_t, 2>* duration_range);
+  virtual int SetFormat(const StreamFormat& desired_format,
+                        uint32_t* result_max_buffers);
+  // Manage buffers.
+  virtual int EnqueueBuffer(const camera3_stream_buffer_t* camera_buffer,
+                            uint32_t* enqueued_index = nullptr);
+  virtual int DequeueBuffer(uint32_t* dequeued_index = nullptr);
+
+ private:
+  // Constructor is private to allow failing on bad input.
+  // Use NewV4L2Wrapper instead.
+  V4L2Wrapper(const std::string device_path,
+              std::unique_ptr<V4L2Gralloc> gralloc);
+
+  // Connect or disconnect to the device. Access by creating/destroying
+  // a V4L2Wrapper::Connection object.
+  int Connect();
+  void Disconnect();
+  // Perform an ioctl call in a thread-safe fashion.
+  template <typename T>
+  int IoctlLocked(int request, T data);
+  // Request/release userspace buffer mode via VIDIOC_REQBUFS.
+  int RequestBuffers(uint32_t num_buffers);
+
+  inline bool connected() { return device_fd_.get() >= 0; }
+
+  // The camera device path. For example, /dev/video0.
+  const std::string device_path_;
+  // The opened device fd.
+  android::base::unique_fd device_fd_;
+  // The underlying gralloc module.
+  std::unique_ptr<V4L2Gralloc> gralloc_;
+  // Whether or not the device supports the extended control query.
+  bool extended_query_supported_;
+  // The format this device is set up for.
+  std::unique_ptr<StreamFormat> format_;
+  // Map indecies to buffer status. True if the index is in-flight.
+  // |buffers_.size()| will always be the maximum number of buffers this device
+  // can handle in its current format.
+  std::vector<bool> buffers_;
+  // Lock protecting use of the buffer tracker.
+  std::mutex buffer_queue_lock_;
+  // Lock protecting use of the device.
+  std::mutex device_lock_;
+  // Lock protecting connecting/disconnecting the device.
+  std::mutex connection_lock_;
+  // Reference count connections.
+  int connection_count_;
+
+  friend class Connection;
+  friend class V4L2WrapperMock;
+
+  DISALLOW_COPY_AND_ASSIGN(V4L2Wrapper);
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_V4L2_WRAPPER_H_
diff --git a/modules/camera/3_4/v4l2_wrapper_mock.h b/modules/camera/3_4/v4l2_wrapper_mock.h
new file mode 100644
index 0000000..d423cc5
--- /dev/null
+++ b/modules/camera/3_4/v4l2_wrapper_mock.h
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+
+// Mock for wrapper class used to communicate with V4L2 devices.
+
+#ifndef V4L2_CAMERA_HAL_V4L2_WRAPPER_MOCK_H_
+#define V4L2_CAMERA_HAL_V4L2_WRAPPER_MOCK_H_
+
+#include <gmock/gmock.h>
+
+#include "v4l2_wrapper.h"
+
+namespace v4l2_camera_hal {
+
+class V4L2WrapperMock : public V4L2Wrapper {
+ public:
+  V4L2WrapperMock() : V4L2Wrapper("", nullptr){};
+  MOCK_METHOD0(StreamOn, int());
+  MOCK_METHOD0(StreamOff, int());
+  MOCK_METHOD2(QueryControl,
+               int(uint32_t control_id, v4l2_query_ext_ctrl* result));
+  MOCK_METHOD2(GetControl, int(uint32_t control_id, int32_t* value));
+  MOCK_METHOD3(SetControl,
+               int(uint32_t control_id, int32_t desired, int32_t* result));
+  MOCK_METHOD1(GetFormats, int(std::set<uint32_t>*));
+  MOCK_METHOD2(GetFormatFrameSizes,
+               int(uint32_t, std::set<std::array<int32_t, 2>>*));
+  MOCK_METHOD3(GetFormatFrameDurationRange,
+               int(uint32_t,
+                   const std::array<int32_t, 2>&,
+                   std::array<int64_t, 2>*));
+  MOCK_METHOD4(SetFormat,
+               int(int format,
+                   uint32_t width,
+                   uint32_t height,
+                   uint32_t* result_max_buffers));
+  MOCK_METHOD2(EnqueueBuffer,
+               int(const camera3_stream_buffer_t* camera_buffer,
+                   uint32_t* enqueued_index));
+  MOCK_METHOD1(DequeueBuffer, int(uint32_t* dequeued_index));
+};
+
+}  // namespace v4l2_camera_hal
+
+#endif  // V4L2_CAMERA_HAL_V4L2_WRAPPER_MOCK_H_
diff --git a/modules/nfc-nci/Android.bp b/modules/nfc-nci/Android.bp
index 90d2a28..f4ef64e 100644
--- a/modules/nfc-nci/Android.bp
+++ b/modules/nfc-nci/Android.bp
@@ -16,7 +16,7 @@
     name: "nfc_nci.default",
     relative_install_path: "hw",
     proprietary: true,
-    srcs: ["nfc_nci_example.c"],
+    srcs: ["nfc_nci_example.cpp"],
     shared_libs: [
         "liblog",
         "libcutils",
diff --git a/modules/nfc-nci/nfc_nci_example.c b/modules/nfc-nci/nfc_nci_example.cpp
similarity index 68%
rename from modules/nfc-nci/nfc_nci_example.c
rename to modules/nfc-nci/nfc_nci_example.cpp
index f3f60f6..0471b6b 100644
--- a/modules/nfc-nci/nfc_nci_example.c
+++ b/modules/nfc-nci/nfc_nci_example.cpp
@@ -23,61 +23,45 @@
 #include <hardware/nfc.h>
 
 /*
- * We want to silence the "unused argument" that gcc and clang give.
- * Other compilers generating this warning will need to provide their
- * custom attribute to silence this.
- */
-#if defined(__GNUC__) || defined(__clang__)
-#define UNUSED_ARGUMENT __attribute((unused))
-#else
-#define UNUSED_ARGUMENT
-#endif
-
-/*
  * NCI HAL method implementations. These must be overriden
  */
-static int hal_open(const struct nfc_nci_device *dev UNUSED_ARGUMENT,
-        nfc_stack_callback_t *p_cback UNUSED_ARGUMENT,
-        nfc_stack_data_callback_t *p_data_cback UNUSED_ARGUMENT) {
+static int hal_open(const struct nfc_nci_device* /*dev*/,
+                    nfc_stack_callback_t* /*p_cback*/,
+                    nfc_stack_data_callback_t* /*p_data_cback*/) {
     ALOGE("NFC-NCI HAL: %s", __FUNCTION__);
     return 0;
 }
 
-static int hal_write(const struct nfc_nci_device *dev UNUSED_ARGUMENT,
-        uint16_t data_len UNUSED_ARGUMENT,
-        const uint8_t *p_data UNUSED_ARGUMENT) {
+static int hal_write(const struct nfc_nci_device* /*dev*/,
+                     uint16_t /*data_len*/, const uint8_t* /*p_data*/) {
     ALOGE("NFC-NCI HAL: %s", __FUNCTION__);
     return 0;
 }
 
-static int hal_core_initialized(
-        const struct nfc_nci_device *dev UNUSED_ARGUMENT,
-        uint8_t* p_core_init_rsp_params UNUSED_ARGUMENT) {
+static int hal_core_initialized(const struct nfc_nci_device* /*dev*/,
+                                uint8_t* /*p_core_init_rsp_params*/) {
     ALOGE("NFC-NCI HAL: %s", __FUNCTION__);
     return 0;
 }
 
-static int hal_pre_discover(
-        const struct nfc_nci_device *dev UNUSED_ARGUMENT) {
+static int hal_pre_discover(const struct nfc_nci_device* /*dev*/) {
     ALOGE("NFC-NCI HAL: %s", __FUNCTION__);
     return 0;
 }
 
-static int hal_close(const struct nfc_nci_device *dev UNUSED_ARGUMENT) {
+static int hal_close(const struct nfc_nci_device* /*dev*/) {
     ALOGE("NFC-NCI HAL: %s", __FUNCTION__);
     return 0;
 }
 
-static int hal_control_granted (
-        const struct nfc_nci_device *p_dev UNUSED_ARGUMENT)
+static int hal_control_granted (const struct nfc_nci_device* /*p_dev*/)
 {
     ALOGE("NFC-NCI HAL: %s", __FUNCTION__);
     return 0;
 }
 
 
-static int hal_power_cycle (
-        const struct nfc_nci_device *p_dev UNUSED_ARGUMENT)
+static int hal_power_cycle (const struct nfc_nci_device* /*p_dev*/)
 {
     ALOGE("NFC-NCI HAL: %s", __FUNCTION__);
     return 0;
@@ -93,9 +77,10 @@
 }
 
 static int nfc_open(const hw_module_t* module, const char* name,
-        hw_device_t** device) {
+                    hw_device_t** device) {
     if (strcmp(name, NFC_NCI_CONTROLLER) == 0) {
-        nfc_nci_device_t *dev = calloc(1, sizeof(nfc_nci_device_t));
+        nfc_nci_device_t *dev = static_cast<nfc_nci_device_t*>(
+                calloc(1, sizeof(nfc_nci_device_t)));
 
         dev->common.tag = HARDWARE_DEVICE_TAG;
         dev->common.version = 0x00010000; // [31:16] major, [15:0] minor
diff --git a/modules/radio/radio_hw.c b/modules/radio/radio_hw.c
index 4376bf0..93b8c88 100644
--- a/modules/radio/radio_hw.c
+++ b/modules/radio/radio_hw.c
@@ -334,6 +334,7 @@
 
                 case CMD_CONFIG: {
                     tuner->config = cmd->config;
+                    tuner->config.antenna_connected = true;
                     event.type = RADIO_EVENT_CONFIG;
                     event.config = tuner->config;
                     ALOGV("%s CMD_CONFIG type %d low %d up %d",
@@ -669,6 +670,7 @@
     pthread_mutex_lock(&rdev->lock);
 
     if (rdev->tuner != NULL) {
+        ALOGE("Can't open tuner twice");
         status = -ENOSYS;
         goto exit;
     }
diff --git a/tests/camera2/AndroidTest.xml b/tests/camera2/AndroidTest.xml
new file mode 100644
index 0000000..693beb2
--- /dev/null
+++ b/tests/camera2/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<configuration description="Config for camera2_test">
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push" value="camera2_test->/data/local/tmp/camera2_test" />
+    </target_preparer>
+    <option name="test-suite-tag" value="apct" />
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="camera2_test" />
+    </test>
+</configuration>
diff --git a/tests/hardware/struct-last.cpp b/tests/hardware/struct-last.cpp
index 44a7b2d..276d786 100644
--- a/tests/hardware/struct-last.cpp
+++ b/tests/hardware/struct-last.cpp
@@ -15,7 +15,6 @@
  */
 
 #include <cstddef>
-#include <system/window.h>
 #include <hardware/hardware.h>
 #include <hardware/sensors.h>
 #include <hardware/fb.h>
diff --git a/tests/hardware/struct-offset.cpp b/tests/hardware/struct-offset.cpp
index 7f7f2e0..6f86f03 100644
--- a/tests/hardware/struct-offset.cpp
+++ b/tests/hardware/struct-offset.cpp
@@ -15,7 +15,6 @@
  */
 
 #include <cstddef>
-#include <system/window.h>
 #include <hardware/hardware.h>
 #include <hardware/sensors.h>
 #include <hardware/fb.h>
diff --git a/tests/hardware/struct-size.cpp b/tests/hardware/struct-size.cpp
index acb9d2d..232b55d 100644
--- a/tests/hardware/struct-size.cpp
+++ b/tests/hardware/struct-size.cpp
@@ -15,7 +15,6 @@
  */
 
 
-#include <system/window.h>
 #include <hardware/hardware.h>
 #include <hardware/sensors.h>
 #include <hardware/fb.h>
diff --git a/tests/hwc/Android.mk b/tests/hwc/Android.mk
index 367f5f4..0416ff1 100644
--- a/tests/hwc/Android.mk
+++ b/tests/hwc/Android.mk
@@ -4,12 +4,13 @@
 LOCAL_MODULE := libcnativewindow 
 LOCAL_SRC_FILES := cnativewindow.c util.c
 LOCAL_CFLAGS := -Wno-unused-parameter
+LOCAL_SHARED_LIBRARIES := libEGL libGLESv2 libdl libhardware libnativewindow
 include $(BUILD_STATIC_LIBRARY)
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := hwc-test-arrows
 LOCAL_SRC_FILES := test-arrows.c
 LOCAL_STATIC_LIBRARIES := libcnativewindow
-LOCAL_SHARED_LIBRARIES := libEGL libGLESv2 libdl libhardware
+LOCAL_SHARED_LIBRARIES := libEGL libGLESv2 libdl libhardware libnativewindow
 LOCAL_CFLAGS := -DGL_GLEXT_PROTOTYPES
 include $(BUILD_EXECUTABLE)