Add configs & validation to static properties

Static properties read and validate stream configurations,
stall durations, and reprocess formats.

Static properties can be used to validate a given
camera3_stream_configuration_t to check if the streams
all work together as defined by the metadata properties.

BUG: 31044638
TEST: unit tests pass
Change-Id: I2c8eecb680cd86fbe0340c07e4d73bd25429b4e9
diff --git a/modules/camera/3_4/metadata/metadata_reader_mock.h b/modules/camera/3_4/metadata/metadata_reader_mock.h
index 096cefe..fcd0704 100644
--- a/modules/camera/3_4/metadata/metadata_reader_mock.h
+++ b/modules/camera/3_4/metadata/metadata_reader_mock.h
@@ -31,6 +31,13 @@
   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(StreamConfigurations,
+                     int(std::vector<StreamConfiguration>*));
+  MOCK_CONST_METHOD1(StreamStallDurations,
+                     int(std::vector<StreamStallDuration>*));
+  MOCK_CONST_METHOD1(ReprocessFormats, int(ReprocessFormatMap*));
 };
 
 }  // namespace default_camera_hal
diff --git a/modules/camera/3_4/static_properties.cpp b/modules/camera/3_4/static_properties.cpp
index a55e9e0..0584290 100644
--- a/modules/camera/3_4/static_properties.cpp
+++ b/modules/camera/3_4/static_properties.cpp
@@ -25,25 +25,441 @@
 
 namespace default_camera_hal {
 
+// Build and capabilities from configs + stall durations.
+static bool ConstructCapabilities(
+    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 ValidateCapabilities(
+    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::vector<StreamConfiguration> configs;
+  std::vector<StreamStallDuration> stalls;
+  CapabilitiesMap 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->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->StreamConfigurations(&configs) ||
+      metadata_reader->StreamStallDurations(&stalls) ||
+      !ConstructCapabilities(configs, stalls, &capabilities) ||
+      // MetadataReader validates configs and stall seperately,
+      // but not that they match.
+      !ValidateCapabilities(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(capabilities, reprocess_map)))) {
     return nullptr;
   }
 
-  return new StaticProperties(std::move(metadata_reader), facing, orientation);
+  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(capabilities),
+                              std::move(reprocess_map));
 }
 
 StaticProperties::StaticProperties(
     std::unique_ptr<const MetadataReader> metadata_reader,
     int facing,
-    int orientation)
+    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,
+    CapabilitiesMap stream_capabilities,
+    ReprocessFormatMap supported_reprocess_outputs)
     : metadata_reader_(std::move(metadata_reader)),
       facing_(facing),
-      orientation_(orientation) {}
+      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),
+      stream_capabilities_(std::move(stream_capabilities)),
+      supported_reprocess_outputs_(std::move(supported_reprocess_outputs)) {}
+
+// 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;
+  int input_format;
+  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
index 77d5c92..a687a80 100644
--- a/modules/camera/3_4/static_properties.h
+++ b/modules/camera/3_4/static_properties.h
@@ -18,15 +18,34 @@
 #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).
@@ -34,6 +53,7 @@
       std::unique_ptr<const MetadataReader> metadata_reader);
   virtual ~StaticProperties(){};
 
+  // Simple accessors.
   int facing() const { return facing_; };
   int orientation() const { return orientation_; };
   // Carrying on the promise of the underlying reader,
@@ -42,16 +62,47 @@
     return metadata_reader_->raw_metadata();
   };
 
+  // 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);
+                   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,
+                   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 CapabilitiesMap stream_capabilities_;
+  const ReprocessFormatMap supported_reprocess_outputs_;
 
   DISALLOW_COPY_AND_ASSIGN(StaticProperties);
 };
diff --git a/modules/camera/3_4/static_properties_test.cpp b/modules/camera/3_4/static_properties_test.cpp
index 7c05ad3..4e41ada 100644
--- a/modules/camera/3_4/static_properties_test.cpp
+++ b/modules/camera/3_4/static_properties_test.cpp
@@ -18,6 +18,7 @@
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
+#include <hardware/camera3.h>
 #include <system/camera.h>
 
 #include "metadata/metadata_reader_mock.h"
@@ -33,7 +34,7 @@
 
 class StaticPropertiesTest : public Test {
  protected:
-  void SetUp() {
+  virtual void SetUp() {
     // Ensure tests will probably fail if PrepareDUT isn't called.
     dut_.reset();
     mock_reader_ = std::make_unique<MetadataReaderMock>();
@@ -43,6 +44,12 @@
     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))
@@ -50,21 +57,142 @@
     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_, 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;
+
+  // 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) {
-  SetDefaultExpectations();
-  PrepareDUT();
-  ASSERT_NE(dut_, nullptr);
+  PrepareDefaultDUT();
   EXPECT_EQ(dut_->facing(), test_facing_);
   EXPECT_EQ(dut_->orientation(), test_orientation_);
+
+  // Stream configurations tested seperately.
 }
 
 TEST_F(StaticPropertiesTest, FactoryFailedFacing) {
@@ -83,4 +211,434 @@
   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, 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, 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