Allow to specify list of supported input configurations.

... and populate corresponding metadata entries / perform
validation based on these.

Bug: 301023410
Test: atest virtual_camera_tests
Change-Id: I66f3cf2b013d5845b6fa7429294a1ed2157318f8
diff --git a/services/camera/virtualcamera/VirtualCameraDevice.cc b/services/camera/virtualcamera/VirtualCameraDevice.cc
index 6c8c0f7..e21afb7 100644
--- a/services/camera/virtualcamera/VirtualCameraDevice.cc
+++ b/services/camera/virtualcamera/VirtualCameraDevice.cc
@@ -18,13 +18,19 @@
 #define LOG_TAG "VirtualCameraDevice"
 #include "VirtualCameraDevice.h"
 
+#include <algorithm>
+#include <array>
 #include <chrono>
 #include <cstdint>
+#include <iterator>
+#include <optional>
 #include <string>
 
 #include "VirtualCameraSession.h"
+#include "aidl/android/companion/virtualcamera/SupportedStreamConfiguration.h"
 #include "aidl/android/hardware/camera/common/Status.h"
 #include "aidl/android/hardware/camera/device/CameraMetadata.h"
+#include "aidl/android/hardware/camera/device/StreamConfiguration.h"
 #include "android/binder_auto_utils.h"
 #include "android/binder_status.h"
 #include "log/log.h"
@@ -36,13 +42,16 @@
 namespace companion {
 namespace virtualcamera {
 
+using ::aidl::android::companion::virtualcamera::Format;
 using ::aidl::android::companion::virtualcamera::IVirtualCameraCallback;
+using ::aidl::android::companion::virtualcamera::SupportedStreamConfiguration;
 using ::aidl::android::hardware::camera::common::CameraResourceCost;
 using ::aidl::android::hardware::camera::common::Status;
 using ::aidl::android::hardware::camera::device::CameraMetadata;
 using ::aidl::android::hardware::camera::device::ICameraDeviceCallback;
 using ::aidl::android::hardware::camera::device::ICameraDeviceSession;
 using ::aidl::android::hardware::camera::device::ICameraInjectionSession;
+using ::aidl::android::hardware::camera::device::Stream;
 using ::aidl::android::hardware::camera::device::StreamConfiguration;
 using ::aidl::android::hardware::camera::device::StreamRotation;
 using ::aidl::android::hardware::camera::device::StreamType;
@@ -55,16 +64,68 @@
 // Prefix of camera name - "device@1.1/virtual/{numerical_id}"
 const char* kDevicePathPrefix = "device@1.1/virtual/";
 
-constexpr int32_t kVgaWidth = 640;
-constexpr int32_t kVgaHeight = 480;
 constexpr std::chrono::nanoseconds kMinFrameDuration30Fps = 1s / 30;
 constexpr int32_t kMaxJpegSize = 3 * 1024 * 1024 /*3MiB*/;
 
 constexpr MetadataBuilder::ControlRegion kDefaultEmptyControlRegion{};
 
+struct Resolution {
+  Resolution(const int w, const int h) : width(w), height(h) {
+  }
+
+  bool operator<(const Resolution& other) const {
+    return width * height < other.width * other.height;
+  }
+
+  bool operator==(const Resolution& other) const {
+    return width == other.width && height == other.height;
+  }
+
+  const int width;
+  const int height;
+};
+
+std::optional<Resolution> getMaxResolution(
+    const std::vector<SupportedStreamConfiguration>& configs) {
+  auto itMax = std::max_element(configs.begin(), configs.end(),
+                                [](const SupportedStreamConfiguration& a,
+                                   const SupportedStreamConfiguration& b) {
+                                  return a.width * b.height < a.width * b.height;
+                                });
+  if (itMax == configs.end()) {
+    ALOGE(
+        "%s: empty vector of supported configurations, cannot find largest "
+        "resolution.",
+        __func__);
+    return std::nullopt;
+  }
+
+  return Resolution(itMax->width, itMax->height);
+}
+
+std::set<Resolution> getUniqueResolutions(
+    const std::vector<SupportedStreamConfiguration>& configs) {
+  std::set<Resolution> uniqueResolutions;
+  std::transform(configs.begin(), configs.end(),
+                 std::inserter(uniqueResolutions, uniqueResolutions.begin()),
+                 [](const SupportedStreamConfiguration& config) {
+                   return Resolution(config.width, config.height);
+                 });
+  return uniqueResolutions;
+}
+
 // TODO(b/301023410) - Populate camera characteristics according to camera configuration.
-CameraMetadata initCameraCharacteristics() {
-  auto metadata =
+std::optional<CameraMetadata> initCameraCharacteristics(
+    const std::vector<SupportedStreamConfiguration>& supportedInputConfig) {
+  if (!std::all_of(supportedInputConfig.begin(), supportedInputConfig.end(),
+                   [](const SupportedStreamConfiguration& config) {
+                     return config.pixelFormat == Format::YUV_420_888;
+                   })) {
+    ALOGE("%s: input configuration contains unsupported pixel format", __func__);
+    return std::nullopt;
+  }
+
+  MetadataBuilder builder =
       MetadataBuilder()
           .setSupportedHardwareLevel(
               ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL)
@@ -73,29 +134,8 @@
           .setSensorOrientation(0)
           .setAvailableFaceDetectModes({ANDROID_STATISTICS_FACE_DETECT_MODE_OFF})
           .setControlAfAvailableModes({ANDROID_CONTROL_AF_MODE_OFF})
-          .setAvailableOutputStreamConfigurations(
-              {MetadataBuilder::StreamConfiguration{
-                   .width = kVgaWidth,
-                   .height = kVgaHeight,
-                   .format =
-                       ANDROID_SCALER_AVAILABLE_FORMATS_IMPLEMENTATION_DEFINED,
-                   .minFrameDuration = kMinFrameDuration30Fps,
-                   .minStallDuration = 0s},
-               MetadataBuilder::StreamConfiguration{
-                   .width = kVgaWidth,
-                   .height = kVgaHeight,
-                   .format = ANDROID_SCALER_AVAILABLE_FORMATS_YCbCr_420_888,
-                   .minFrameDuration = kMinFrameDuration30Fps,
-                   .minStallDuration = 0s},
-               {MetadataBuilder::StreamConfiguration{
-                   .width = kVgaWidth,
-                   .height = kVgaHeight,
-                   .format = ANDROID_SCALER_AVAILABLE_FORMATS_BLOB,
-                   .minFrameDuration = kMinFrameDuration30Fps,
-                   .minStallDuration = 0s}}})
           .setControlAeAvailableFpsRange(10, 30)
           .setControlMaxRegions(0, 0, 0)
-          .setSensorActiveArraySize(0, 0, kVgaWidth, kVgaHeight)
           .setControlAfRegions({kDefaultEmptyControlRegion})
           .setControlAeRegions({kDefaultEmptyControlRegion})
           .setControlAwbRegions({kDefaultEmptyControlRegion})
@@ -106,9 +146,66 @@
           .setAvailableResultKeys({ANDROID_CONTROL_AF_MODE})
           .setAvailableCapabilities(
               {ANDROID_REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE})
-          .setAvailableCharacteristicKeys()
-          .build();
+          .setAvailableCharacteristicKeys();
 
+  // Active array size must correspond to largest supported input resolution.
+  std::optional<Resolution> maxResolution =
+      getMaxResolution(supportedInputConfig);
+  if (!maxResolution.has_value()) {
+    return std::nullopt;
+  }
+  builder.setSensorActiveArraySize(0, 0, maxResolution->width,
+                                   maxResolution->height);
+
+  std::vector<MetadataBuilder::StreamConfiguration> outputConfigurations;
+
+  // TODO(b/301023410) Add also all "standard" resolutions we can rescale the
+  // streams to (all standard resolutions with same aspect ratio).
+
+  // Add IMPLEMENTATION_DEFINED format for all supported input resolutions.
+  std::set<Resolution> uniqueResolutions =
+      getUniqueResolutions(supportedInputConfig);
+  std::transform(
+      uniqueResolutions.begin(), uniqueResolutions.end(),
+      std::back_inserter(outputConfigurations),
+      [](const Resolution& resolution) {
+        return MetadataBuilder::StreamConfiguration{
+            .width = resolution.width,
+            .height = resolution.height,
+            .format = ANDROID_SCALER_AVAILABLE_FORMATS_IMPLEMENTATION_DEFINED,
+            .minFrameDuration = kMinFrameDuration30Fps,
+            .minStallDuration = 0s};
+      });
+
+  // Add all supported configuration with explicit pixel format.
+  std::transform(supportedInputConfig.begin(), supportedInputConfig.end(),
+                 std::back_inserter(outputConfigurations),
+                 [](const SupportedStreamConfiguration& config) {
+                   return MetadataBuilder::StreamConfiguration{
+                       .width = config.width,
+                       .height = config.height,
+                       .format = static_cast<int>(config.pixelFormat),
+                       .minFrameDuration = kMinFrameDuration30Fps,
+                       .minStallDuration = 0s};
+                 });
+
+  // TODO(b/301023410) We currently don't support rescaling for still capture,
+  // so only announce BLOB support for formats exactly matching the input.
+  std::transform(uniqueResolutions.begin(), uniqueResolutions.end(),
+                 std::back_inserter(outputConfigurations),
+                 [](const Resolution& resolution) {
+                   return MetadataBuilder::StreamConfiguration{
+                       .width = resolution.width,
+                       .height = resolution.height,
+                       .format = ANDROID_SCALER_AVAILABLE_FORMATS_BLOB,
+                       .minFrameDuration = kMinFrameDuration30Fps,
+                       .minStallDuration = 0s};
+                 });
+
+  ALOGV("Adding %zu output configurations", outputConfigurations.size());
+  builder.setAvailableOutputStreamConfigurations(outputConfigurations);
+
+  auto metadata = builder.build();
   if (metadata == nullptr) {
     ALOGE("Failed to build metadata!");
     return CameraMetadata();
@@ -121,10 +218,21 @@
 
 VirtualCameraDevice::VirtualCameraDevice(
     const uint32_t cameraId,
+    const std::vector<SupportedStreamConfiguration>& supportedInputConfig,
     std::shared_ptr<IVirtualCameraCallback> virtualCameraClientCallback)
     : mCameraId(cameraId),
-      mVirtualCameraClientCallback(virtualCameraClientCallback) {
-  mCameraCharacteristics = initCameraCharacteristics();
+      mVirtualCameraClientCallback(virtualCameraClientCallback),
+      mSupportedInputConfigurations(supportedInputConfig) {
+  std::optional<CameraMetadata> metadata =
+      initCameraCharacteristics(mSupportedInputConfigurations);
+  if (metadata.has_value()) {
+    mCameraCharacteristics = *metadata;
+  } else {
+    ALOGE(
+        "%s: Failed to initialize camera characteristic based on provided "
+        "configuration.",
+        __func__);
+  }
 }
 
 ndk::ScopedAStatus VirtualCameraDevice::getCameraCharacteristics(
@@ -168,29 +276,42 @@
     return cameraStatus(Status::ILLEGAL_ARGUMENT);
   }
 
-  for (const auto& stream : in_streams.streams) {
+  *_aidl_return = isStreamCombinationSupported(in_streams);
+  return ndk::ScopedAStatus::ok();
+};
+
+bool VirtualCameraDevice::isStreamCombinationSupported(
+    const StreamConfiguration& streamConfiguration) const {
+  for (const Stream& stream : streamConfiguration.streams) {
     ALOGV("%s: Configuration queried: %s", __func__, stream.toString().c_str());
 
     if (stream.streamType == StreamType::INPUT) {
       ALOGW("%s: Input stream type is not supported", __func__);
-      *_aidl_return = false;
-      return ndk::ScopedAStatus::ok();
+      return false;
     }
 
     // TODO(b/301023410) remove hardcoded format checks, verify against configuration.
-    if (stream.width != 640 || stream.height != 480 ||
-        stream.rotation != StreamRotation::ROTATION_0 ||
+    if (stream.rotation != StreamRotation::ROTATION_0 ||
         (stream.format != PixelFormat::IMPLEMENTATION_DEFINED &&
          stream.format != PixelFormat::YCBCR_420_888 &&
          stream.format != PixelFormat::BLOB)) {
-      *_aidl_return = false;
-      return ndk::ScopedAStatus::ok();
+      ALOGV("Unsupported output stream type");
+      return false;
+    }
+
+    auto matchesSupportedInputConfig =
+        [&stream](const SupportedStreamConfiguration& config) {
+          return stream.width == config.width && stream.height == config.height;
+        };
+    if (std::none_of(mSupportedInputConfigurations.begin(),
+                     mSupportedInputConfigurations.end(),
+                     matchesSupportedInputConfig)) {
+      ALOGV("Requested config doesn't match any supported input config");
+      return false;
     }
   }
-
-  *_aidl_return = true;
-  return ndk::ScopedAStatus::ok();
-};
+  return true;
+}
 
 ndk::ScopedAStatus VirtualCameraDevice::open(
     const std::shared_ptr<ICameraDeviceCallback>& in_callback,
@@ -198,7 +319,7 @@
   ALOGV("%s", __func__);
 
   *_aidl_return = ndk::SharedRefBase::make<VirtualCameraSession>(
-      std::to_string(mCameraId), in_callback, mVirtualCameraClientCallback);
+      *this, in_callback, mVirtualCameraClientCallback);
 
   return ndk::ScopedAStatus::ok();
 };
diff --git a/services/camera/virtualcamera/VirtualCameraDevice.h b/services/camera/virtualcamera/VirtualCameraDevice.h
index 0c95b7a..4c3cfc2 100644
--- a/services/camera/virtualcamera/VirtualCameraDevice.h
+++ b/services/camera/virtualcamera/VirtualCameraDevice.h
@@ -21,6 +21,7 @@
 #include <memory>
 
 #include "aidl/android/companion/virtualcamera/IVirtualCameraCallback.h"
+#include "aidl/android/companion/virtualcamera/SupportedStreamConfiguration.h"
 #include "aidl/android/hardware/camera/device/BnCameraDevice.h"
 
 namespace android {
@@ -34,6 +35,9 @@
  public:
   explicit VirtualCameraDevice(
       uint32_t cameraId,
+      const std::vector<
+          aidl::android::companion::virtualcamera::SupportedStreamConfiguration>&
+          supportedInputConfig,
       std::shared_ptr<
           ::aidl::android::companion::virtualcamera::IVirtualCameraCallback>
           virtualCameraClientCallback = nullptr);
@@ -58,6 +62,10 @@
           in_streams,
       bool* _aidl_return) override;
 
+  bool isStreamCombinationSupported(
+      const ::aidl::android::hardware::camera::device::StreamConfiguration&
+          in_streams) const;
+
   ndk::ScopedAStatus open(
       const std::shared_ptr<
           ::aidl::android::hardware::camera::device::ICameraDeviceCallback>&
@@ -94,6 +102,10 @@
       mVirtualCameraClientCallback;
 
   ::aidl::android::hardware::camera::device::CameraMetadata mCameraCharacteristics;
+
+  const std::vector<
+      aidl::android::companion::virtualcamera::SupportedStreamConfiguration>
+      mSupportedInputConfigurations;
 };
 
 }  // namespace virtualcamera
diff --git a/services/camera/virtualcamera/VirtualCameraProvider.cc b/services/camera/virtualcamera/VirtualCameraProvider.cc
index b2bdd06..25a43d6 100644
--- a/services/camera/virtualcamera/VirtualCameraProvider.cc
+++ b/services/camera/virtualcamera/VirtualCameraProvider.cc
@@ -34,6 +34,7 @@
 namespace virtualcamera {
 
 using ::aidl::android::companion::virtualcamera::IVirtualCameraCallback;
+using ::aidl::android::companion::virtualcamera::SupportedStreamConfiguration;
 using ::aidl::android::hardware::camera::common::CameraDeviceStatus;
 using ::aidl::android::hardware::camera::common::Status;
 using ::aidl::android::hardware::camera::common::VendorTagSection;
@@ -154,9 +155,10 @@
 }
 
 std::shared_ptr<VirtualCameraDevice> VirtualCameraProvider::createCamera(
+    const std::vector<SupportedStreamConfiguration>& supportedInputConfig,
     std::shared_ptr<IVirtualCameraCallback> virtualCameraClientCallback) {
   auto camera = ndk::SharedRefBase::make<VirtualCameraDevice>(
-      sNextId++, virtualCameraClientCallback);
+      sNextId++, supportedInputConfig, virtualCameraClientCallback);
   std::shared_ptr<ICameraProviderCallback> callback;
   {
     const std::lock_guard<std::mutex> lock(mLock);
diff --git a/services/camera/virtualcamera/VirtualCameraProvider.h b/services/camera/virtualcamera/VirtualCameraProvider.h
index e0f72fa..d41a005 100644
--- a/services/camera/virtualcamera/VirtualCameraProvider.h
+++ b/services/camera/virtualcamera/VirtualCameraProvider.h
@@ -77,6 +77,9 @@
   //
   // TODO(b/301023410) - Add camera configuration.
   std::shared_ptr<VirtualCameraDevice> createCamera(
+      const std::vector<
+          aidl::android::companion::virtualcamera::SupportedStreamConfiguration>&
+          supportedInputConfig,
       std::shared_ptr<aidl::android::companion::virtualcamera::IVirtualCameraCallback>
           virtualCameraClientCallback = nullptr);
 
diff --git a/services/camera/virtualcamera/VirtualCameraService.cc b/services/camera/virtualcamera/VirtualCameraService.cc
index 62dc08b..8afd901 100644
--- a/services/camera/virtualcamera/VirtualCameraService.cc
+++ b/services/camera/virtualcamera/VirtualCameraService.cc
@@ -15,7 +15,6 @@
  */
 
 // #define LOG_NDEBUG 0
-#include "android/binder_status.h"
 #define LOG_TAG "VirtualCameraService"
 #include "VirtualCameraService.h"
 
@@ -27,9 +26,12 @@
 
 #include "VirtualCameraDevice.h"
 #include "VirtualCameraProvider.h"
+#include "aidl/android/companion/virtualcamera/Format.h"
+#include "aidl/android/companion/virtualcamera/VirtualCameraConfiguration.h"
 #include "android/binder_auto_utils.h"
 #include "android/binder_libbinder.h"
 #include "binder/Status.h"
+#include "util/Util.h"
 
 using ::android::binder::Status;
 
@@ -37,10 +39,14 @@
 namespace companion {
 namespace virtualcamera {
 
+using ::aidl::android::companion::virtualcamera::Format;
+using ::aidl::android::companion::virtualcamera::SupportedStreamConfiguration;
 using ::aidl::android::companion::virtualcamera::VirtualCameraConfiguration;
 
 namespace {
 
+constexpr int kVgaWidth = 640;
+constexpr int kVgaHeight = 480;
 constexpr char kEnableTestCameraCmd[] = "enable_test_camera";
 constexpr char kDisableTestCameraCmd[] = "disable_test_camera";
 constexpr char kShellCmdHelp[] = R"(
@@ -49,6 +55,27 @@
  * disable_test_camera
 )";
 
+ndk::ScopedAStatus validateConfiguration(
+    const VirtualCameraConfiguration& configuration) {
+  if (configuration.supportedStreamConfigs.empty()) {
+    ALOGE("%s: No supported input configuration specified", __func__);
+    return ndk::ScopedAStatus::fromServiceSpecificError(
+        Status::EX_ILLEGAL_ARGUMENT);
+  }
+
+  for (const SupportedStreamConfiguration& config :
+       configuration.supportedStreamConfigs) {
+    if (!isFormatSupportedForInput(config.width, config.height,
+                                   config.pixelFormat)) {
+      ALOGE("%s: Requested unsupported input format: %d x %d (%d)", __func__,
+            config.width, config.height, static_cast<int>(config.pixelFormat));
+      return ndk::ScopedAStatus::fromServiceSpecificError(
+          Status::EX_ILLEGAL_ARGUMENT);
+    }
+  }
+  return ndk::ScopedAStatus::ok();
+}
+
 }  // namespace
 
 VirtualCameraService::VirtualCameraService(
@@ -59,13 +86,18 @@
 ndk::ScopedAStatus VirtualCameraService::registerCamera(
     const ::ndk::SpAIBinder& token,
     const VirtualCameraConfiguration& configuration, bool* _aidl_return) {
-  (void)configuration;
   if (_aidl_return == nullptr) {
     return ndk::ScopedAStatus::fromServiceSpecificError(
         Status::EX_ILLEGAL_ARGUMENT);
   }
   *_aidl_return = true;
 
+  auto status = validateConfiguration(configuration);
+  if (!status.isOk()) {
+    *_aidl_return = false;
+    return status;
+  }
+
   std::lock_guard lock(mLock);
   if (mTokenToCameraName.find(token) != mTokenToCameraName.end()) {
     ALOGE(
@@ -74,11 +106,13 @@
         "0x%" PRIxPTR,
         reinterpret_cast<uintptr_t>(token.get()));
     *_aidl_return = false;
+    return ndk::ScopedAStatus::ok();
   }
 
   // TODO(b/301023410) Validate configuration and pass it to the camera.
   std::shared_ptr<VirtualCameraDevice> camera =
-      mVirtualCameraProvider->createCamera(configuration.virtualCameraCallback);
+      mVirtualCameraProvider->createCamera(configuration.supportedStreamConfigs,
+                                           configuration.virtualCameraCallback);
   if (camera == nullptr) {
     ALOGE("Failed to create camera for binder token 0x%" PRIxPTR,
           reinterpret_cast<uintptr_t>(token.get()));
@@ -159,7 +193,10 @@
   mTestCameraToken.set(AIBinder_fromPlatformBinder(token));
 
   bool ret;
-  registerCamera(mTestCameraToken, VirtualCameraConfiguration(), &ret);
+  VirtualCameraConfiguration configuration;
+  configuration.supportedStreamConfigs.push_back(
+      {.width = kVgaWidth, .height = kVgaHeight, Format::YUV_420_888});
+  registerCamera(mTestCameraToken, configuration, &ret);
   if (ret) {
     dprintf(out, "Successfully registered test camera %s",
             getCamera(mTestCameraToken)->getCameraName().c_str());
diff --git a/services/camera/virtualcamera/VirtualCameraSession.cc b/services/camera/virtualcamera/VirtualCameraSession.cc
index 55678b7..ff6235f 100644
--- a/services/camera/virtualcamera/VirtualCameraSession.cc
+++ b/services/camera/virtualcamera/VirtualCameraSession.cc
@@ -34,6 +34,7 @@
 
 #include "CameraMetadata.h"
 #include "EGL/egl.h"
+#include "VirtualCameraDevice.h"
 #include "VirtualCameraRenderThread.h"
 #include "VirtualCameraStream.h"
 #include "aidl/android/hardware/camera/common/Status.h"
@@ -152,10 +153,10 @@
 }  // namespace
 
 VirtualCameraSession::VirtualCameraSession(
-    const std::string& cameraId,
+    VirtualCameraDevice& cameraDevice,
     std::shared_ptr<ICameraDeviceCallback> cameraDeviceCallback,
     std::shared_ptr<IVirtualCameraCallback> virtualCameraClientCallback)
-    : mCameraId(cameraId),
+    : mCameraDevice(cameraDevice),
       mCameraDeviceCallback(cameraDeviceCallback),
       mVirtualCameraClientCallback(virtualCameraClientCallback) {
   mRequestMetadataQueue = std::make_unique<RequestMetadataQueue>(
@@ -204,18 +205,14 @@
   int inputWidth;
   int inputHeight;
 
+  if (!mCameraDevice.isStreamCombinationSupported(in_requestedConfiguration)) {
+    ALOGE("%s: Requested stream configuration is not supported", __func__);
+    return cameraStatus(Status::ILLEGAL_ARGUMENT);
+  }
+
   {
     std::lock_guard<std::mutex> lock(mLock);
     for (int i = 0; i < in_requestedConfiguration.streams.size(); ++i) {
-      // TODO(b/301023410) remove hardcoded format checks, verify against configuration.
-      if (streams[i].width != 640 || streams[i].height != 480 ||
-          streams[i].rotation != StreamRotation::ROTATION_0 ||
-          (streams[i].format != PixelFormat::IMPLEMENTATION_DEFINED &&
-           streams[i].format != PixelFormat::YCBCR_420_888 &&
-           streams[i].format != PixelFormat::BLOB)) {
-        halStreams.clear();
-        return cameraStatus(Status::ILLEGAL_ARGUMENT);
-      }
       halStreams[i] = getHalStream(streams[i]);
       if (mSessionContext.initializeStream(streams[i])) {
         ALOGV("Configured new stream: %s", streams[i].toString().c_str());
diff --git a/services/camera/virtualcamera/VirtualCameraSession.h b/services/camera/virtualcamera/VirtualCameraSession.h
index 440720e..6df5d58 100644
--- a/services/camera/virtualcamera/VirtualCameraSession.h
+++ b/services/camera/virtualcamera/VirtualCameraSession.h
@@ -34,6 +34,8 @@
 namespace companion {
 namespace virtualcamera {
 
+class VirtualCameraDevice;
+
 // Implementation of ICameraDeviceSession AIDL interface to allow camera
 // framework to read image data from open virtual camera device. This class
 // encapsulates possibly several image streams for the same session.
@@ -44,7 +46,7 @@
   // When virtualCameraClientCallback is null, the input surface will be filled
   // with test pattern.
   VirtualCameraSession(
-      const std::string& cameraId,
+      VirtualCameraDevice& mCameraDevice,
       std::shared_ptr<
           ::aidl::android::hardware::camera::device::ICameraDeviceCallback>
           cameraDeviceCallback,
@@ -114,7 +116,7 @@
       const ::aidl::android::hardware::camera::device::CaptureRequest& request)
       EXCLUDES(mLock);
 
-  const std::string mCameraId;
+  VirtualCameraDevice& mCameraDevice;
 
   mutable std::mutex mLock;
 
diff --git a/services/camera/virtualcamera/tests/Android.bp b/services/camera/virtualcamera/tests/Android.bp
index c30779c..bc46ba0 100644
--- a/services/camera/virtualcamera/tests/Android.bp
+++ b/services/camera/virtualcamera/tests/Android.bp
@@ -15,6 +15,7 @@
         "libgmock",
     ],
     srcs: ["EglUtilTest.cc",
+           "VirtualCameraDeviceTest.cc",
            "VirtualCameraProviderTest.cc",
            "VirtualCameraRenderThreadTest.cc",
            "VirtualCameraServiceTest.cc",
diff --git a/services/camera/virtualcamera/tests/VirtualCameraDeviceTest.cc b/services/camera/virtualcamera/tests/VirtualCameraDeviceTest.cc
new file mode 100644
index 0000000..140ae65
--- /dev/null
+++ b/services/camera/virtualcamera/tests/VirtualCameraDeviceTest.cc
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <memory>
+
+#include "VirtualCameraDevice.h"
+#include "aidl/android/companion/virtualcamera/Format.h"
+#include "aidl/android/companion/virtualcamera/SupportedStreamConfiguration.h"
+#include "aidl/android/hardware/camera/device/CameraMetadata.h"
+#include "aidl/android/hardware/camera/device/StreamConfiguration.h"
+#include "android/binder_interface_utils.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "log/log_main.h"
+#include "system/camera_metadata.h"
+#include "utils/Errors.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+namespace {
+
+using ::aidl::android::companion::virtualcamera::Format;
+using ::aidl::android::companion::virtualcamera::SupportedStreamConfiguration;
+using ::aidl::android::hardware::camera::device::CameraMetadata;
+using ::aidl::android::hardware::camera::device::Stream;
+using ::aidl::android::hardware::camera::device::StreamConfiguration;
+using ::aidl::android::hardware::camera::device::StreamType;
+using ::aidl::android::hardware::graphics::common::PixelFormat;
+using ::testing::UnorderedElementsAreArray;
+using metadata_stream_t =
+    camera_metadata_enum_android_scaler_available_stream_configurations_t;
+
+constexpr int kCameraId = 42;
+constexpr int kVgaWidth = 640;
+constexpr int kVgaHeight = 480;
+constexpr int kHdWidth = 1280;
+constexpr int kHdHeight = 720;
+
+struct AvailableStreamConfiguration {
+  const int width;
+  const int height;
+  const int pixelFormat;
+  const metadata_stream_t streamConfiguration;
+};
+
+bool operator==(const AvailableStreamConfiguration& a,
+                const AvailableStreamConfiguration& b) {
+  return a.width == b.width && a.height == b.height &&
+         a.pixelFormat == b.pixelFormat &&
+         a.streamConfiguration == b.streamConfiguration;
+}
+
+std::ostream& operator<<(std::ostream& os,
+                         const AvailableStreamConfiguration& config) {
+  os << config.width << "x" << config.height << " (pixfmt "
+     << config.pixelFormat << ", streamConfiguration "
+     << config.streamConfiguration << ")";
+  return os;
+}
+
+std::vector<AvailableStreamConfiguration> getAvailableStreamConfigurations(
+    const CameraMetadata& metadata) {
+  const camera_metadata_t* const raw =
+      reinterpret_cast<const camera_metadata_t*>(metadata.metadata.data());
+  camera_metadata_ro_entry_t entry;
+  if (find_camera_metadata_ro_entry(
+          raw, ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &entry) !=
+      NO_ERROR) {
+    return {};
+  }
+
+  std::vector<AvailableStreamConfiguration> res;
+  for (int i = 0; i < entry.count; i += 4) {
+    res.push_back(AvailableStreamConfiguration{
+        .width = entry.data.i32[i + 1],
+        .height = entry.data.i32[i + 2],
+        .pixelFormat = entry.data.i32[i],
+        .streamConfiguration =
+            static_cast<metadata_stream_t>(entry.data.i32[i + 3])});
+  }
+  return res;
+}
+
+struct VirtualCameraConfigTestParam {
+  std::vector<SupportedStreamConfiguration> inputConfig;
+  std::vector<AvailableStreamConfiguration> expectedAvailableStreamConfigs;
+};
+
+class VirtualCameraDeviceTest
+    : public testing::TestWithParam<VirtualCameraConfigTestParam> {};
+
+TEST_P(VirtualCameraDeviceTest, cameraCharacteristicsForInputFormat) {
+  const VirtualCameraConfigTestParam& param = GetParam();
+  std::shared_ptr<VirtualCameraDevice> camera =
+      ndk::SharedRefBase::make<VirtualCameraDevice>(
+          kCameraId, param.inputConfig, /*virtualCameraClientCallback=*/nullptr);
+
+  CameraMetadata metadata;
+  ASSERT_TRUE(camera->getCameraCharacteristics(&metadata).isOk());
+  EXPECT_THAT(getAvailableStreamConfigurations(metadata),
+              UnorderedElementsAreArray(param.expectedAvailableStreamConfigs));
+
+  // Configuration needs to succeed for every available stream configuration
+  for (const AvailableStreamConfiguration& config :
+       param.expectedAvailableStreamConfigs) {
+    StreamConfiguration configuration{
+        .streams = std::vector<Stream>{Stream{
+            .streamType = StreamType::OUTPUT,
+            .width = config.width,
+            .height = config.height,
+            .format = static_cast<PixelFormat>(config.pixelFormat),
+        }}};
+    bool aidl_ret;
+    ASSERT_TRUE(
+        camera->isStreamCombinationSupported(configuration, &aidl_ret).isOk());
+    EXPECT_TRUE(aidl_ret);
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    cameraCharacteristicsForInputFormat, VirtualCameraDeviceTest,
+    testing::Values(
+        VirtualCameraConfigTestParam{
+            .inputConfig = {SupportedStreamConfiguration{
+                .width = kVgaWidth,
+                .height = kVgaHeight,
+                .pixelFormat = Format::YUV_420_888}},
+            .expectedAvailableStreamConfigs =
+                {AvailableStreamConfiguration{
+                     .width = kVgaWidth,
+                     .height = kVgaHeight,
+                     .pixelFormat = ANDROID_SCALER_AVAILABLE_FORMATS_YCbCr_420_888,
+                     .streamConfiguration =
+                         ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT},
+                 AvailableStreamConfiguration{
+                     .width = kVgaWidth,
+                     .height = kVgaHeight,
+                     .pixelFormat =
+                         ANDROID_SCALER_AVAILABLE_FORMATS_IMPLEMENTATION_DEFINED,
+                     .streamConfiguration =
+                         ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT},
+                 AvailableStreamConfiguration{
+                     .width = kVgaWidth,
+                     .height = kVgaHeight,
+                     .pixelFormat = ANDROID_SCALER_AVAILABLE_FORMATS_BLOB,
+                     .streamConfiguration =
+                         ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT}}},
+        VirtualCameraConfigTestParam{
+            .inputConfig = {SupportedStreamConfiguration{
+                                .width = kVgaWidth,
+                                .height = kVgaHeight,
+                                .pixelFormat = Format::YUV_420_888},
+                            SupportedStreamConfiguration{
+                                .width = kHdWidth,
+                                .height = kHdHeight,
+                                .pixelFormat = Format::YUV_420_888}},
+            .expectedAvailableStreamConfigs = {
+                AvailableStreamConfiguration{
+                    .width = kVgaWidth,
+                    .height = kVgaHeight,
+                    .pixelFormat = ANDROID_SCALER_AVAILABLE_FORMATS_YCbCr_420_888,
+                    .streamConfiguration =
+                        ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT},
+                AvailableStreamConfiguration{
+                    .width = kVgaWidth,
+                    .height = kVgaHeight,
+                    .pixelFormat =
+                        ANDROID_SCALER_AVAILABLE_FORMATS_IMPLEMENTATION_DEFINED,
+                    .streamConfiguration =
+                        ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT},
+                AvailableStreamConfiguration{
+                    .width = kVgaWidth,
+                    .height = kVgaHeight,
+                    .pixelFormat = ANDROID_SCALER_AVAILABLE_FORMATS_BLOB,
+                    .streamConfiguration =
+                        ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT},
+                AvailableStreamConfiguration{
+                    .width = kHdWidth,
+                    .height = kHdHeight,
+                    .pixelFormat = ANDROID_SCALER_AVAILABLE_FORMATS_YCbCr_420_888,
+                    .streamConfiguration =
+                        ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT},
+                AvailableStreamConfiguration{
+                    .width = kHdWidth,
+                    .height = kHdHeight,
+                    .pixelFormat =
+                        ANDROID_SCALER_AVAILABLE_FORMATS_IMPLEMENTATION_DEFINED,
+                    .streamConfiguration =
+                        ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT},
+                AvailableStreamConfiguration{
+                    .width = kHdWidth,
+                    .height = kHdHeight,
+                    .pixelFormat = ANDROID_SCALER_AVAILABLE_FORMATS_BLOB,
+                    .streamConfiguration =
+                        ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT}}}));
+
+}  // namespace
+}  // namespace virtualcamera
+}  // namespace companion
+}  // namespace android
diff --git a/services/camera/virtualcamera/tests/VirtualCameraProviderTest.cc b/services/camera/virtualcamera/tests/VirtualCameraProviderTest.cc
index 03fc2c2..615a77c 100644
--- a/services/camera/virtualcamera/tests/VirtualCameraProviderTest.cc
+++ b/services/camera/virtualcamera/tests/VirtualCameraProviderTest.cc
@@ -32,6 +32,8 @@
 namespace virtualcamera {
 namespace {
 
+using ::aidl::android::companion::virtualcamera::Format;
+using ::aidl::android::companion::virtualcamera::SupportedStreamConfiguration;
 using ::aidl::android::hardware::camera::common::CameraDeviceStatus;
 using ::aidl::android::hardware::camera::common::Status;
 using ::aidl::android::hardware::camera::common::TorchModeStatus;
@@ -45,6 +47,8 @@
 using ::testing::Not;
 using ::testing::Return;
 
+constexpr int kVgaWidth = 640;
+constexpr int kVgaHeight = 480;
 constexpr char kVirtualCameraNameRegex[] =
     "device@[0-9]+\\.[0-9]+/virtual/[0-9]+";
 
@@ -75,6 +79,10 @@
   std::shared_ptr<VirtualCameraProvider> mCameraProvider;
   std::shared_ptr<MockCameraProviderCallback> mMockCameraProviderCallback =
       ndk::SharedRefBase::make<MockCameraProviderCallback>();
+  std::vector<SupportedStreamConfiguration> mInputConfigs = {
+      SupportedStreamConfiguration{.width = kVgaWidth,
+                                   .height = kVgaHeight,
+                                   .pixelFormat = Format::YUV_420_888}};
 };
 
 TEST_F(VirtualCameraProviderTest, SetNullCameraCallbackFails) {
@@ -100,7 +108,8 @@
       .WillOnce(Return(ndk::ScopedAStatus::ok()));
 
   ASSERT_TRUE(mCameraProvider->setCallback(mMockCameraProviderCallback).isOk());
-  std::shared_ptr<VirtualCameraDevice> camera = mCameraProvider->createCamera();
+  std::shared_ptr<VirtualCameraDevice> camera =
+      mCameraProvider->createCamera(mInputConfigs);
   EXPECT_THAT(camera, Not(IsNull()));
   EXPECT_THAT(camera->getCameraName(), MatchesRegex(kVirtualCameraNameRegex));
 
@@ -117,7 +126,8 @@
               cameraDeviceStatusChange(_, CameraDeviceStatus::PRESENT))
       .WillOnce(Return(ndk::ScopedAStatus::ok()));
 
-  std::shared_ptr<VirtualCameraDevice> camera = mCameraProvider->createCamera();
+  std::shared_ptr<VirtualCameraDevice> camera =
+      mCameraProvider->createCamera(mInputConfigs);
   ASSERT_TRUE(mCameraProvider->setCallback(mMockCameraProviderCallback).isOk());
 
   // Created camera should be in the list of cameras.
@@ -128,7 +138,8 @@
 
 TEST_F(VirtualCameraProviderTest, RemoveCamera) {
   ASSERT_TRUE(mCameraProvider->setCallback(mMockCameraProviderCallback).isOk());
-  std::shared_ptr<VirtualCameraDevice> camera = mCameraProvider->createCamera();
+  std::shared_ptr<VirtualCameraDevice> camera =
+      mCameraProvider->createCamera(mInputConfigs);
 
   EXPECT_CALL(*mMockCameraProviderCallback,
               cameraDeviceStatusChange(Eq(camera->getCameraName()),
@@ -144,7 +155,8 @@
 
 TEST_F(VirtualCameraProviderTest, RemoveNonExistingCamera) {
   ASSERT_TRUE(mCameraProvider->setCallback(mMockCameraProviderCallback).isOk());
-  std::shared_ptr<VirtualCameraDevice> camera = mCameraProvider->createCamera();
+  std::shared_ptr<VirtualCameraDevice> camera =
+      mCameraProvider->createCamera(mInputConfigs);
 
   // Removing non-existing camera should fail.
   const std::string cameraName = "DefinitelyNoTCamera";
diff --git a/services/camera/virtualcamera/tests/VirtualCameraServiceTest.cc b/services/camera/virtualcamera/tests/VirtualCameraServiceTest.cc
index 4fd0b3b..04349b1 100644
--- a/services/camera/virtualcamera/tests/VirtualCameraServiceTest.cc
+++ b/services/camera/virtualcamera/tests/VirtualCameraServiceTest.cc
@@ -37,6 +37,7 @@
 namespace {
 
 using ::aidl::android::companion::virtualcamera::BnVirtualCameraCallback;
+using ::aidl::android::companion::virtualcamera::Format;
 using ::aidl::android::companion::virtualcamera::VirtualCameraConfiguration;
 using ::aidl::android::hardware::camera::common::CameraDeviceStatus;
 using ::aidl::android::hardware::camera::common::TorchModeStatus;
@@ -51,8 +52,19 @@
 using ::testing::Not;
 using ::testing::SizeIs;
 
+constexpr int kVgaWidth = 640;
+constexpr int kVgaHeight = 480;
+
 const VirtualCameraConfiguration kEmptyVirtualCameraConfiguration;
 
+VirtualCameraConfiguration createConfiguration(const int width, const int height,
+                                               const Format format) {
+  VirtualCameraConfiguration configuration;
+  configuration.supportedStreamConfigs.push_back(
+      {.width = width, .height = height, .pixelFormat = format});
+  return configuration;
+}
+
 class MockCameraProviderCallback : public BnCameraProviderCallback {
  public:
   MOCK_METHOD(ndk::ScopedAStatus, cameraDeviceStatusChange,
@@ -89,7 +101,7 @@
 
     ASSERT_TRUE(mCameraService
                     ->registerCamera(mNdkOwnerToken,
-                                     kEmptyVirtualCameraConfiguration, &aidlRet)
+                                     mVgaYUV420OnlyConfiguration, &aidlRet)
                     .isOk());
     ASSERT_TRUE(aidlRet);
   }
@@ -106,6 +118,12 @@
         Eq(NO_ERROR));
   }
 
+  std::vector<std::string> getCameraIds() {
+    std::vector<std::string> cameraIds;
+    EXPECT_TRUE(mCameraProvider->getCameraIdList(&cameraIds).isOk());
+    return cameraIds;
+  }
+
  protected:
   std::shared_ptr<VirtualCameraService> mCameraService;
   std::shared_ptr<VirtualCameraProvider> mCameraProvider;
@@ -116,6 +134,9 @@
   ndk::SpAIBinder mNdkOwnerToken;
 
   int mDevNullFd;
+
+  VirtualCameraConfiguration mVgaYUV420OnlyConfiguration =
+      createConfiguration(kVgaWidth, kVgaHeight, Format::YUV_420_888);
 };
 
 TEST_F(VirtualCameraServiceTest, RegisterCameraSucceeds) {
@@ -125,10 +146,11 @@
 
   ASSERT_TRUE(
       mCameraService
-          ->registerCamera(ndkToken, kEmptyVirtualCameraConfiguration, &aidlRet)
+          ->registerCamera(ndkToken, mVgaYUV420OnlyConfiguration, &aidlRet)
           .isOk());
 
   EXPECT_TRUE(aidlRet);
+  EXPECT_THAT(getCameraIds(), SizeIs(1));
 }
 
 TEST_F(VirtualCameraServiceTest, RegisterCameraTwiceSecondReturnsFalse) {
@@ -136,10 +158,67 @@
   bool aidlRet;
 
   ASSERT_TRUE(mCameraService
-                  ->registerCamera(mNdkOwnerToken,
-                                   kEmptyVirtualCameraConfiguration, &aidlRet)
+                  ->registerCamera(mNdkOwnerToken, mVgaYUV420OnlyConfiguration,
+                                   &aidlRet)
                   .isOk());
   EXPECT_FALSE(aidlRet);
+  EXPECT_THAT(getCameraIds(), SizeIs(1));
+}
+
+TEST_F(VirtualCameraServiceTest, EmptyConfigurationFails) {
+  bool aidlRet;
+
+  ASSERT_FALSE(mCameraService
+                   ->registerCamera(mNdkOwnerToken,
+                                    kEmptyVirtualCameraConfiguration, &aidlRet)
+                   .isOk());
+  EXPECT_FALSE(aidlRet);
+  EXPECT_THAT(getCameraIds(), IsEmpty());
+}
+
+TEST_F(VirtualCameraServiceTest, ConfigurationWithUnsupportedPixelFormatFails) {
+  bool aidlRet;
+
+  VirtualCameraConfiguration config =
+      createConfiguration(kVgaWidth, kVgaHeight, Format::UNKNOWN);
+
+  ASSERT_FALSE(
+      mCameraService->registerCamera(mNdkOwnerToken, config, &aidlRet).isOk());
+  EXPECT_FALSE(aidlRet);
+  EXPECT_THAT(getCameraIds(), IsEmpty());
+}
+
+TEST_F(VirtualCameraServiceTest, ConfigurationWithTooHighResFails) {
+  bool aidlRet;
+  VirtualCameraConfiguration config =
+      createConfiguration(1000000, 1000000, Format::YUV_420_888);
+
+  ASSERT_FALSE(
+      mCameraService->registerCamera(mNdkOwnerToken, config, &aidlRet).isOk());
+  EXPECT_FALSE(aidlRet);
+  EXPECT_THAT(getCameraIds(), IsEmpty());
+}
+
+TEST_F(VirtualCameraServiceTest, ConfigurationWithUnalignedResolutionFails) {
+  bool aidlRet;
+  VirtualCameraConfiguration config =
+      createConfiguration(641, 481, Format::YUV_420_888);
+
+  ASSERT_FALSE(
+      mCameraService->registerCamera(mNdkOwnerToken, config, &aidlRet).isOk());
+  EXPECT_FALSE(aidlRet);
+  EXPECT_THAT(getCameraIds(), IsEmpty());
+}
+
+TEST_F(VirtualCameraServiceTest, ConfigurationWithNegativeResolutionFails) {
+  bool aidlRet;
+  VirtualCameraConfiguration config =
+      createConfiguration(-1, kVgaHeight, Format::YUV_420_888);
+
+  ASSERT_FALSE(
+      mCameraService->registerCamera(mNdkOwnerToken, config, &aidlRet).isOk());
+  EXPECT_FALSE(aidlRet);
+  EXPECT_THAT(getCameraIds(), IsEmpty());
 }
 
 TEST_F(VirtualCameraServiceTest, GetCamera) {
@@ -191,13 +270,13 @@
 TEST_F(VirtualCameraServiceTest, TestCameraShellCmd) {
   execute_shell_command("enable_test_camera");
 
-  std::vector<std::string> cameraIds;
-  EXPECT_TRUE(mCameraProvider->getCameraIdList(&cameraIds).isOk());
-  EXPECT_THAT(cameraIds, SizeIs(1));
+  std::vector<std::string> cameraIdsAfterEnable = getCameraIds();
+  EXPECT_THAT(cameraIdsAfterEnable, SizeIs(1));
 
   execute_shell_command("disable_test_camera");
-  EXPECT_TRUE(mCameraProvider->getCameraIdList(&cameraIds).isOk());
-  EXPECT_THAT(cameraIds, IsEmpty());
+
+  std::vector<std::string> cameraIdsAfterDisable = getCameraIds();
+  EXPECT_THAT(cameraIdsAfterDisable, IsEmpty());
 }
 
 }  // namespace
diff --git a/services/camera/virtualcamera/tests/VirtualCameraSessionTest.cc b/services/camera/virtualcamera/tests/VirtualCameraSessionTest.cc
index 5da080b..0bc5210 100644
--- a/services/camera/virtualcamera/tests/VirtualCameraSessionTest.cc
+++ b/services/camera/virtualcamera/tests/VirtualCameraSessionTest.cc
@@ -17,9 +17,11 @@
 #include <cstdint>
 #include <memory>
 
+#include "VirtualCameraDevice.h"
 #include "VirtualCameraSession.h"
 #include "aidl/android/companion/virtualcamera/BnVirtualCameraCallback.h"
 #include "aidl/android/companion/virtualcamera/IVirtualCameraCallback.h"
+#include "aidl/android/companion/virtualcamera/SupportedStreamConfiguration.h"
 #include "aidl/android/hardware/camera/device/BnCameraDeviceCallback.h"
 #include "aidl/android/hardware/camera/device/StreamConfiguration.h"
 #include "aidl/android/hardware/graphics/common/PixelFormat.h"
@@ -37,10 +39,11 @@
 constexpr int kWidth = 640;
 constexpr int kHeight = 480;
 constexpr int kStreamId = 0;
-const std::string kCameraName = "virtual_camera";
+constexpr int kCameraId = 42;
 
 using ::aidl::android::companion::virtualcamera::BnVirtualCameraCallback;
 using ::aidl::android::companion::virtualcamera::Format;
+using ::aidl::android::companion::virtualcamera::SupportedStreamConfiguration;
 using ::aidl::android::hardware::camera::device::BnCameraDeviceCallback;
 using ::aidl::android::hardware::camera::device::BufferRequest;
 using ::aidl::android::hardware::camera::device::BufferRequestStatus;
@@ -99,8 +102,15 @@
         ndk::SharedRefBase::make<MockCameraDeviceCallback>();
     mMockVirtualCameraClientCallback =
         ndk::SharedRefBase::make<MockVirtualCameraCallback>();
+    mVirtualCameraDevice = ndk::SharedRefBase::make<VirtualCameraDevice>(
+        kCameraId,
+        std::vector<SupportedStreamConfiguration>{
+            SupportedStreamConfiguration{.width = kWidth,
+                                         .height = kHeight,
+                                         .pixelFormat = Format::YUV_420_888}},
+        mMockVirtualCameraClientCallback);
     mVirtualCameraSession = ndk::SharedRefBase::make<VirtualCameraSession>(
-        kCameraName, mMockCameraDeviceCallback,
+        *mVirtualCameraDevice, mMockCameraDeviceCallback,
         mMockVirtualCameraClientCallback);
 
     ON_CALL(*mMockVirtualCameraClientCallback, onStreamConfigured)
@@ -112,6 +122,7 @@
  protected:
   std::shared_ptr<MockCameraDeviceCallback> mMockCameraDeviceCallback;
   std::shared_ptr<MockVirtualCameraCallback> mMockVirtualCameraClientCallback;
+  std::shared_ptr<VirtualCameraDevice> mVirtualCameraDevice;
   std::shared_ptr<VirtualCameraSession> mVirtualCameraSession;
 };
 
diff --git a/services/camera/virtualcamera/util/Util.cc b/services/camera/virtualcamera/util/Util.cc
index 78236a4..11dc3a6 100644
--- a/services/camera/virtualcamera/util/Util.cc
+++ b/services/camera/virtualcamera/util/Util.cc
@@ -18,10 +18,19 @@
 
 #include <unistd.h>
 
+#include "jpeglib.h"
+
 namespace android {
 namespace companion {
 namespace virtualcamera {
 
+// Lower bound for maximal supported texture size is at least 2048x2048
+// but on most platforms will be more.
+// TODO(b/301023410) - Query actual max texture size.
+constexpr int kMaxTextureSize = 2048;
+constexpr int kLibJpegDctSize = DCTSIZE;
+
+using ::aidl::android::companion::virtualcamera::Format;
 using ::aidl::android::hardware::common::NativeHandle;
 
 sp<Fence> importFence(const NativeHandle& aidlHandle) {
@@ -32,6 +41,29 @@
   return sp<Fence>::make(::dup(aidlHandle.fds[0].get()));
 }
 
+// Returns true if specified format is supported for virtual camera input.
+bool isFormatSupportedForInput(const int width, const int height,
+                               const Format format) {
+  if (format != Format::YUV_420_888) {
+    // For now only YUV_420_888 is supported for input.
+    return false;
+  }
+
+  if (width <= 0 || height <= 0 || width > kMaxTextureSize ||
+      height > kMaxTextureSize) {
+    return false;
+  }
+
+  if (width % kLibJpegDctSize != 0 || height % kLibJpegDctSize != 0) {
+    // Input dimension needs to be multiple of libjpeg DCT size.
+    // TODO(b/301023410) This restriction can be removed once we add support for
+    // unaligned jpeg compression.
+    return false;
+  }
+
+  return true;
+}
+
 }  // namespace virtualcamera
 }  // namespace companion
 }  // namespace android
diff --git a/services/camera/virtualcamera/util/Util.h b/services/camera/virtualcamera/util/Util.h
index 1a0a458..b778321 100644
--- a/services/camera/virtualcamera/util/Util.h
+++ b/services/camera/virtualcamera/util/Util.h
@@ -19,6 +19,7 @@
 
 #include <cstdint>
 
+#include "aidl/android/companion/virtualcamera/Format.h"
 #include "aidl/android/hardware/camera/common/Status.h"
 #include "aidl/android/hardware/camera/device/StreamBuffer.h"
 #include "android/binder_auto_utils.h"
@@ -42,6 +43,11 @@
 sp<Fence> importFence(
     const ::aidl::android::hardware::common::NativeHandle& handle);
 
+// Returns true if specified format is supported for virtual camera input.
+bool isFormatSupportedForInput(
+    int width, int height,
+    ::aidl::android::companion::virtualcamera::Format format);
+
 }  // namespace virtualcamera
 }  // namespace companion
 }  // namespace android