[media][tests][e2e] Additional virtualaudio FIDL interfaces

This CL adds methods and events to fuchsia.virtualaudio.Configuration
and fuchsia.virtualaudio.Device, as needed to support the end-to-end
testing push. This includes notifications to the client upon SetGain,
SetFormat, GetBuffer, Start, Stop, and PositionNotify, as well as the
ability to retrieve current values for format, gain, buffer and
position.

This also adds a method to remove the added-by-default format range.

Test: build, CQ, new test cases,

Change-Id: I02df1e9d56eb5b7ba114a35701835950e4a4b4da
diff --git a/garnet/drivers/audio/virtual_audio/README.md b/garnet/drivers/audio/virtual_audio/README.md
index 797121e..2b55007 100644
--- a/garnet/drivers/audio/virtual_audio/README.md
+++ b/garnet/drivers/audio/virtual_audio/README.md
@@ -55,9 +55,10 @@
 ### virtualaudio.Input and virtualaudio.Output
 
 These FIDL interfaces are used to configure and add virtual audio input and
-output devices. virtualaudio.Config is a subset of the top-level input and
-output interfaces, and includes methods to statically configure these virtual
-devices before they are created, specifically to set the following properties:
+output devices. virtualaudio.Configuration is a subset of the top-level input
+and output interfaces, and includes methods to statically configure these
+virtual devices before they are created, specifically to set the following
+properties:
 * Device name
 * Manufacturer name
 * Product name
@@ -86,8 +87,9 @@
   - Plugged
   - Plug time
 Together, these properties comprise a virtual audio device _config_. Finally,
-the virtualaudio.Config interface contains a method to:
+the virtualaudio.Configuration interface contains methods to:
 * Reset the device configuration, returning it to a default state.
+* Clear the previously-added format ranges, retaining other configuration state.
 
 The virtualaudio.Device interface (another subset of Input and Output) contains
 methods to make the following dynamic changes:
diff --git a/garnet/drivers/audio/virtual_audio/virtual_audio_control_impl.cpp b/garnet/drivers/audio/virtual_audio/virtual_audio_control_impl.cpp
index a6cb674..43737aa 100644
--- a/garnet/drivers/audio/virtual_audio/virtual_audio_control_impl.cpp
+++ b/garnet/drivers/audio/virtual_audio/virtual_audio_control_impl.cpp
@@ -120,6 +120,9 @@
       fidl::InterfaceRequest<fuchsia::virtualaudio::Input>(
           std::move(input_request_channel)));
 
+  auto* binding = input_bindings_.bindings().back().get();
+  binding->impl()->SetBinding(binding);
+
   return ZX_OK;
 }
 
@@ -139,6 +142,9 @@
       fidl::InterfaceRequest<fuchsia::virtualaudio::Output>(
           std::move(output_request_channel)));
 
+  auto* binding = output_bindings_.bindings().back().get();
+  binding->impl()->SetBinding(binding);
+
   return ZX_OK;
 }
 
diff --git a/garnet/drivers/audio/virtual_audio/virtual_audio_device_impl.cpp b/garnet/drivers/audio/virtual_audio/virtual_audio_device_impl.cpp
index 7e8feb4..36d20e8 100644
--- a/garnet/drivers/audio/virtual_audio/virtual_audio_device_impl.cpp
+++ b/garnet/drivers/audio/virtual_audio/virtual_audio_device_impl.cpp
@@ -28,12 +28,38 @@
 }
 
 // If we have not already destroyed our child stream, do so now.
-VirtualAudioDeviceImpl::~VirtualAudioDeviceImpl() { RemoveStream(); }
+VirtualAudioDeviceImpl::~VirtualAudioDeviceImpl() {
+  RemoveStream();
+  input_binding_ = nullptr;
+  output_binding_ = nullptr;
+}
 
 void VirtualAudioDeviceImpl::PostToDispatcher(fit::closure task_to_post) {
   owner_->PostToDispatcher(std::move(task_to_post));
 }
 
+void VirtualAudioDeviceImpl::SetBinding(
+    fidl::Binding<fuchsia::virtualaudio::Input,
+                  fbl::unique_ptr<virtual_audio::VirtualAudioDeviceImpl>>*
+        binding) {
+  ZX_ASSERT(is_input_);
+  ZX_DEBUG_ASSERT(output_binding_ == nullptr);
+
+  input_binding_ = binding;
+  ZX_DEBUG_ASSERT(input_binding_->is_bound());
+}
+
+void VirtualAudioDeviceImpl::SetBinding(
+    fidl::Binding<fuchsia::virtualaudio::Output,
+                  fbl::unique_ptr<virtual_audio::VirtualAudioDeviceImpl>>*
+        binding) {
+  ZX_ASSERT(!is_input_);
+  ZX_DEBUG_ASSERT(input_binding_ == nullptr);
+
+  output_binding_ = binding;
+  ZX_DEBUG_ASSERT(output_binding_->is_bound());
+}
+
 bool VirtualAudioDeviceImpl::CreateStream(zx_device_t* devnode) {
   stream_ = VirtualAudioStream::CreateStream(this, devnode, is_input_);
   return (stream_ != nullptr);
@@ -66,13 +92,12 @@
 }
 
 void VirtualAudioDeviceImpl::Init() {
-  snprintf(device_name_, sizeof(device_name_), kDefaultDeviceName);
-  snprintf(mfr_name_, sizeof(mfr_name_), kDefaultManufacturerName);
-  snprintf(prod_name_, sizeof(prod_name_), kDefaultProductName);
+  device_name_ = kDefaultDeviceName;
+  mfr_name_ = kDefaultManufacturerName;
+  prod_name_ = kDefaultProductName;
   memcpy(unique_id_, kDefaultUniqueId, sizeof(unique_id_));
 
   // By default, we support one basic format range (stereo 16-bit 48kHz)
-  default_range_ = true;
   supported_formats_.clear();
   supported_formats_.push_back(kDefaultFormatRange);
 
@@ -96,15 +121,15 @@
 // virtualaudio::Configuration implementation
 //
 void VirtualAudioDeviceImpl::SetDeviceName(std::string device_name) {
-  strlcpy(device_name_, device_name.c_str(), sizeof(device_name_));
+  device_name_ = device_name;
 };
 
 void VirtualAudioDeviceImpl::SetManufacturer(std::string manufacturer_name) {
-  strlcpy(mfr_name_, manufacturer_name.c_str(), sizeof(mfr_name_));
+  mfr_name_ = manufacturer_name;
 };
 
 void VirtualAudioDeviceImpl::SetProduct(std::string product_name) {
-  strlcpy(prod_name_, product_name.c_str(), sizeof(prod_name_));
+  prod_name_ = product_name;
 };
 
 void VirtualAudioDeviceImpl::SetUniqueId(fidl::Array<uint8_t, 16> unique_id) {
@@ -116,11 +141,6 @@
 void VirtualAudioDeviceImpl::AddFormatRange(
     uint32_t format_flags, uint32_t min_rate, uint32_t max_rate,
     uint8_t min_chans, uint8_t max_chans, uint16_t rate_family_flags) {
-  if (default_range_) {
-    supported_formats_.clear();
-    default_range_ = false;
-  }
-
   audio_stream_format_range_t range = {.sample_formats = format_flags,
                                        .min_frames_per_second = min_rate,
                                        .max_frames_per_second = max_rate,
@@ -131,6 +151,8 @@
   supported_formats_.push_back(range);
 };
 
+void VirtualAudioDeviceImpl::ClearFormatRanges() { supported_formats_.clear(); }
+
 void VirtualAudioDeviceImpl::SetFifoDepth(uint32_t fifo_depth_bytes) {
   fifo_depth_ = fifo_depth_bytes;
 }
@@ -225,6 +247,136 @@
   RemoveStream();
 }
 
+void VirtualAudioDeviceImpl::GetFormat(
+    fuchsia::virtualaudio::Device::GetFormatCallback format_callback) {
+  if (stream_ == nullptr) {
+    zxlogf(TRACE, "%s: %p has no stream for this request\n",
+           __PRETTY_FUNCTION__, this);
+    return;
+  }
+
+  stream_->EnqueueFormatRequest(std::move(format_callback));
+}
+
+// Deliver SetFormat notification on binding's thread, if binding is valid.
+void VirtualAudioDeviceImpl::NotifySetFormat(uint32_t frames_per_second,
+                                             uint32_t sample_format,
+                                             uint32_t num_channels,
+                                             zx_duration_t external_delay) {
+  PostToDispatcher(
+      [this, frames_per_second, sample_format, num_channels, external_delay]() {
+        if (input_binding_ && input_binding_->is_bound()) {
+          input_binding_->events().OnSetFormat(frames_per_second, sample_format,
+                                               num_channels, external_delay);
+        } else if (output_binding_ && output_binding_->is_bound()) {
+          output_binding_->events().OnSetFormat(
+              frames_per_second, sample_format, num_channels, external_delay);
+        }
+      });
+}
+
+void VirtualAudioDeviceImpl::GetGain(
+    fuchsia::virtualaudio::Device::GetGainCallback gain_callback) {
+  if (stream_ == nullptr) {
+    zxlogf(TRACE, "%s: %p has no stream for this request\n",
+           __PRETTY_FUNCTION__, this);
+    return;
+  }
+
+  stream_->EnqueueGainRequest(std::move(gain_callback));
+}
+
+// Deliver SetGain notification on binding's thread, if binding is valid.
+void VirtualAudioDeviceImpl::NotifySetGain(bool current_mute, bool current_agc,
+                                           float current_gain_db) {
+  PostToDispatcher([this, current_mute, current_agc, current_gain_db]() {
+    if (input_binding_ && input_binding_->is_bound()) {
+      input_binding_->events().OnSetGain(current_mute, current_agc,
+                                         current_gain_db);
+    } else if (output_binding_ && output_binding_->is_bound()) {
+      output_binding_->events().OnSetGain(current_mute, current_agc,
+                                          current_gain_db);
+    }
+  });
+}
+
+void VirtualAudioDeviceImpl::GetBuffer(
+    fuchsia::virtualaudio::Device::GetBufferCallback buffer_callback) {
+  if (stream_ == nullptr) {
+    zxlogf(TRACE, "%s: %p has no stream for this request\n",
+           __PRETTY_FUNCTION__, this);
+    return;
+  }
+
+  stream_->EnqueueBufferRequest(std::move(buffer_callback));
+}
+
+// Deliver SetBuffer notification on binding's thread, if binding is valid.
+void VirtualAudioDeviceImpl::NotifyBufferCreated(
+    zx::vmo ring_buffer_vmo, uint32_t num_ring_buffer_frames,
+    uint32_t notifications_per_ring) {
+  PostToDispatcher([this, ring_buffer_vmo = std::move(ring_buffer_vmo),
+                    num_ring_buffer_frames, notifications_per_ring]() mutable {
+    if (input_binding_ && input_binding_->is_bound()) {
+      input_binding_->events().OnBufferCreated(std::move(ring_buffer_vmo),
+                                               num_ring_buffer_frames,
+                                               notifications_per_ring);
+    } else if (output_binding_ && output_binding_->is_bound()) {
+      output_binding_->events().OnBufferCreated(std::move(ring_buffer_vmo),
+                                                num_ring_buffer_frames,
+                                                notifications_per_ring);
+    }
+  });
+}
+
+// Deliver Start notification on binding's thread, if binding is valid.
+void VirtualAudioDeviceImpl::NotifyStart(zx_time_t start_time) {
+  PostToDispatcher([this, start_time]() {
+    if (input_binding_ && input_binding_->is_bound()) {
+      input_binding_->events().OnStart(start_time);
+    } else if (output_binding_ && output_binding_->is_bound()) {
+      output_binding_->events().OnStart(start_time);
+    }
+  });
+}
+
+// Deliver Stop notification on binding's thread, if binding is valid.
+void VirtualAudioDeviceImpl::NotifyStop(zx_time_t stop_time,
+                                        uint32_t ring_buffer_position) {
+  PostToDispatcher([this, stop_time, ring_buffer_position]() {
+    if (input_binding_ && input_binding_->is_bound()) {
+      input_binding_->events().OnStop(stop_time, ring_buffer_position);
+    } else if (output_binding_ && output_binding_->is_bound()) {
+      output_binding_->events().OnStop(stop_time, ring_buffer_position);
+    }
+  });
+}
+
+void VirtualAudioDeviceImpl::GetPosition(
+    fuchsia::virtualaudio::Device::GetPositionCallback position_callback) {
+  if (stream_ == nullptr) {
+    zxlogf(TRACE, "%s: %p has no stream for this request\n",
+           __PRETTY_FUNCTION__, this);
+    return;
+  }
+
+  stream_->EnqueuePositionRequest(std::move(position_callback));
+}
+
+// Deliver Position notification on binding's thread, if binding is valid.
+void VirtualAudioDeviceImpl::NotifyPosition(uint32_t ring_buffer_position,
+                                            zx_time_t time_for_position) {
+  PostToDispatcher([this, ring_buffer_position, time_for_position]() {
+    if (input_binding_ && input_binding_->is_bound()) {
+      input_binding_->events().OnPositionNotify(ring_buffer_position,
+                                                time_for_position);
+    } else if (output_binding_ && output_binding_->is_bound()) {
+      output_binding_->events().OnPositionNotify(ring_buffer_position,
+                                                 time_for_position);
+    }
+  });
+}
+
 // Change the plug state on-the-fly for this active virtual audio device.
 void VirtualAudioDeviceImpl::ChangePlugState(zx_time_t plug_change_time,
                                              bool plugged) {
diff --git a/garnet/drivers/audio/virtual_audio/virtual_audio_device_impl.h b/garnet/drivers/audio/virtual_audio/virtual_audio_device_impl.h
index 1ac19e8..35caa57 100644
--- a/garnet/drivers/audio/virtual_audio/virtual_audio_device_impl.h
+++ b/garnet/drivers/audio/virtual_audio/virtual_audio_device_impl.h
@@ -67,6 +67,15 @@
   // Used to deliver callbacks or events, from the driver execution domain.
   void PostToDispatcher(fit::closure task_to_post);
 
+  void SetBinding(
+      fidl::Binding<fuchsia::virtualaudio::Input,
+                    fbl::unique_ptr<virtual_audio::VirtualAudioDeviceImpl>>*
+          binding);
+  void SetBinding(
+      fidl::Binding<fuchsia::virtualaudio::Output,
+                    fbl::unique_ptr<virtual_audio::VirtualAudioDeviceImpl>>*
+          binding);
+
   virtual bool CreateStream(zx_device_t* devnode);
   void RemoveStream();
   void ClearStream();
@@ -84,11 +93,13 @@
   void AddFormatRange(uint32_t format_flags, uint32_t min_rate,
                       uint32_t max_rate, uint8_t min_chans, uint8_t max_chans,
                       uint16_t rate_family_flags) override;
+  void ClearFormatRanges() override;
 
   void SetFifoDepth(uint32_t fifo_depth_bytes) override;
   void SetExternalDelay(zx_duration_t external_delay) override;
   void SetRingBufferRestrictions(uint32_t min_frames, uint32_t max_frames,
                                  uint32_t modulo_frames) override;
+
   void SetGainProperties(float min_gain_db, float max_gain_db,
                          float gain_step_db, float current_gain_db,
                          bool can_mute, bool current_mute, bool can_agc,
@@ -104,6 +115,32 @@
   //
   void Add() override;
   void Remove() override;
+
+  void GetFormat(
+      fuchsia::virtualaudio::Device::GetFormatCallback callback) override;
+  virtual void NotifySetFormat(uint32_t frames_per_second,
+                               uint32_t sample_format, uint32_t num_channels,
+                               zx_duration_t external_delay);
+
+  void GetGain(
+      fuchsia::virtualaudio::Device::GetGainCallback callback) override;
+  virtual void NotifySetGain(bool current_mute, bool current_agc,
+                             float current_gain_db);
+
+  void GetBuffer(
+      fuchsia::virtualaudio::Device::GetBufferCallback callback) override;
+  virtual void NotifyBufferCreated(zx::vmo ring_buffer_vmo,
+                                   uint32_t num_ring_buffer_frames,
+                                   uint32_t notifications_per_ring);
+
+  virtual void NotifyStart(zx_time_t start_time);
+  virtual void NotifyStop(zx_time_t stop_time, uint32_t ring_buffer_position);
+
+  void GetPosition(
+      fuchsia::virtualaudio::Device::GetPositionCallback callback) override;
+  virtual void NotifyPosition(uint32_t ring_buffer_position,
+                              zx_time_t start_time);
+
   void ChangePlugState(zx_time_t plug_change_time, bool plugged) override;
 
  protected:
@@ -117,9 +154,21 @@
   fbl::RefPtr<VirtualAudioStream> stream_;
   bool is_input_;
 
-  char device_name_[32];
-  char mfr_name_[64];
-  char prod_name_[64];
+  // When the binding is closed, it is removed from the (ControlImpl-owned)
+  // BindingSet that contains it, which in turn deletes the associated impl
+  // (since the binding holds a unique_ptr<impl>, not an impl*). Something might
+  // get dispatched from another thread at around this time, so we always check
+  // the binding __once we get to our main thread__, wherever these are used.
+  fidl::Binding<fuchsia::virtualaudio::Input,
+                fbl::unique_ptr<virtual_audio::VirtualAudioDeviceImpl>>*
+      input_binding_ = nullptr;
+  fidl::Binding<fuchsia::virtualaudio::Output,
+                fbl::unique_ptr<virtual_audio::VirtualAudioDeviceImpl>>*
+      output_binding_ = nullptr;
+
+  std::string device_name_;
+  std::string mfr_name_;
+  std::string prod_name_;
   uint8_t unique_id_[16];
 
   std::vector<audio_stream_format_range_t> supported_formats_;
@@ -137,8 +186,6 @@
   bool plugged_;
   bool hardwired_;
   bool async_plug_notify_;
-
-  bool default_range_ = true;
 };
 
 }  // namespace virtual_audio
diff --git a/garnet/drivers/audio/virtual_audio/virtual_audio_stream.cpp b/garnet/drivers/audio/virtual_audio/virtual_audio_stream.cpp
index 5836631..628606f 100644
--- a/garnet/drivers/audio/virtual_audio/virtual_audio_stream.cpp
+++ b/garnet/drivers/audio/virtual_audio/virtual_audio_stream.cpp
@@ -32,15 +32,16 @@
 }
 
 zx_status_t VirtualAudioStream::Init() {
-  if (!strlcpy(device_name_, parent_->device_name_, sizeof(device_name_))) {
+  if (!strlcpy(device_name_, parent_->device_name_.c_str(),
+               sizeof(device_name_))) {
     return ZX_ERR_INTERNAL;
   }
 
-  if (!strlcpy(mfr_name_, parent_->mfr_name_, sizeof(mfr_name_))) {
+  if (!strlcpy(mfr_name_, parent_->mfr_name_.c_str(), sizeof(mfr_name_))) {
     return ZX_ERR_INTERNAL;
   }
 
-  if (!strlcpy(prod_name_, parent_->prod_name_, sizeof(prod_name_))) {
+  if (!strlcpy(prod_name_, parent_->prod_name_.c_str(), sizeof(prod_name_))) {
     return ZX_ERR_INTERNAL;
   }
 
@@ -93,6 +94,74 @@
     return status;
   }
 
+  gain_request_wakeup_ = dispatcher::WakeupEvent::Create();
+  if (gain_request_wakeup_ == nullptr) {
+    return ZX_ERR_NO_MEMORY;
+  }
+  dispatcher::WakeupEvent::ProcessHandler gain_wake_handler(
+      [this](dispatcher::WakeupEvent* event) -> zx_status_t {
+        OBTAIN_EXECUTION_DOMAIN_TOKEN(t, domain_);
+        HandleGainRequests();
+        return ZX_OK;
+      });
+  status =
+      gain_request_wakeup_->Activate(domain_, std::move(gain_wake_handler));
+  if (status != ZX_OK) {
+    zxlogf(ERROR, "GetGain WakeupEvent activate failed (%d)\n", status);
+    return status;
+  }
+
+  format_request_wakeup_ = dispatcher::WakeupEvent::Create();
+  if (format_request_wakeup_ == nullptr) {
+    return ZX_ERR_NO_MEMORY;
+  }
+  dispatcher::WakeupEvent::ProcessHandler format_wake_handler(
+      [this](dispatcher::WakeupEvent* event) -> zx_status_t {
+        OBTAIN_EXECUTION_DOMAIN_TOKEN(t, domain_);
+        HandleFormatRequests();
+        return ZX_OK;
+      });
+  status =
+      format_request_wakeup_->Activate(domain_, std::move(format_wake_handler));
+  if (status != ZX_OK) {
+    zxlogf(ERROR, "GetFormat WakeupEvent activate failed (%d)\n", status);
+    return status;
+  }
+
+  buffer_request_wakeup_ = dispatcher::WakeupEvent::Create();
+  if (buffer_request_wakeup_ == nullptr) {
+    return ZX_ERR_NO_MEMORY;
+  }
+  dispatcher::WakeupEvent::ProcessHandler buffer_wake_handler(
+      [this](dispatcher::WakeupEvent* event) -> zx_status_t {
+        OBTAIN_EXECUTION_DOMAIN_TOKEN(t, domain_);
+        HandleBufferRequests();
+        return ZX_OK;
+      });
+  status =
+      buffer_request_wakeup_->Activate(domain_, std::move(buffer_wake_handler));
+  if (status != ZX_OK) {
+    zxlogf(ERROR, "GetBuffer WakeupEvent activate failed (%d)\n", status);
+    return status;
+  }
+
+  position_request_wakeup_ = dispatcher::WakeupEvent::Create();
+  if (position_request_wakeup_ == nullptr) {
+    return ZX_ERR_NO_MEMORY;
+  }
+  dispatcher::WakeupEvent::ProcessHandler position_wake_handler(
+      [this](dispatcher::WakeupEvent* event) -> zx_status_t {
+        OBTAIN_EXECUTION_DOMAIN_TOKEN(t, domain_);
+        HandlePositionRequests();
+        return ZX_OK;
+      });
+  status = position_request_wakeup_->Activate(domain_,
+                                              std::move(position_wake_handler));
+  if (status != ZX_OK) {
+    zxlogf(ERROR, "GetPosition WakeupEvent activate failed (%d)\n", status);
+    return status;
+  }
+
   notify_timer_ = dispatcher::Timer::Create();
   if (notify_timer_ == nullptr) {
     return ZX_ERR_NO_MEMORY;
@@ -111,6 +180,56 @@
   return ZX_OK;
 }
 
+void VirtualAudioStream::EnqueuePlugChange(bool plugged) {
+  {
+    fbl::AutoLock lock(&wakeup_queue_lock_);
+    PlugType plug_change = (plugged ? PlugType::Plug : PlugType::Unplug);
+    plug_queue_.push_back(plug_change);
+  }
+
+  plug_change_wakeup_->Signal();
+}
+
+void VirtualAudioStream::EnqueueGainRequest(
+    fuchsia::virtualaudio::Device::GetGainCallback gain_callback) {
+  {
+    fbl::AutoLock lock(&wakeup_queue_lock_);
+    gain_queue_.push_back(std::move(gain_callback));
+  }
+
+  gain_request_wakeup_->Signal();
+}
+
+void VirtualAudioStream::EnqueueFormatRequest(
+    fuchsia::virtualaudio::Device::GetFormatCallback format_callback) {
+  {
+    fbl::AutoLock lock(&wakeup_queue_lock_);
+    format_queue_.push_back(std::move(format_callback));
+  }
+
+  format_request_wakeup_->Signal();
+}
+
+void VirtualAudioStream::EnqueueBufferRequest(
+    fuchsia::virtualaudio::Device::GetBufferCallback buffer_callback) {
+  {
+    fbl::AutoLock lock(&wakeup_queue_lock_);
+    buffer_queue_.push_back(std::move(buffer_callback));
+  }
+
+  buffer_request_wakeup_->Signal();
+}
+
+void VirtualAudioStream::EnqueuePositionRequest(
+    fuchsia::virtualaudio::Device::GetPositionCallback position_callback) {
+  {
+    fbl::AutoLock lock(&wakeup_queue_lock_);
+    position_queue_.push_back(std::move(position_callback));
+  }
+
+  position_request_wakeup_->Signal();
+}
+
 void VirtualAudioStream::HandlePlugChanges() {
   while (true) {
     PlugType plug_change;
@@ -138,14 +257,139 @@
   }
 }
 
-void VirtualAudioStream::EnqueuePlugChange(bool plugged) {
-  {
-    fbl::AutoLock lock(&wakeup_queue_lock_);
-    PlugType plug_change = (plugged ? PlugType::Plug : PlugType::Unplug);
-    plug_queue_.push_back(plug_change);
-  }
+void VirtualAudioStream::HandleGainRequests() {
+  while (true) {
+    bool current_mute, current_agc;
+    float current_gain_db;
+    fuchsia::virtualaudio::Device::GetGainCallback gain_callback;
 
-  plug_change_wakeup_->Signal();
+    if (fbl::AutoLock lock(&wakeup_queue_lock_); !gain_queue_.empty()) {
+      current_mute = cur_gain_state_.cur_mute;
+      current_agc = cur_gain_state_.cur_agc;
+      current_gain_db = cur_gain_state_.cur_gain;
+
+      gain_callback = std::move(gain_queue_.front());
+      gain_queue_.pop_front();
+    } else {
+      break;
+    }
+
+    parent_->PostToDispatcher([gain_callback = std::move(gain_callback),
+                               current_mute, current_agc, current_gain_db]() {
+      gain_callback(current_mute, current_agc, current_gain_db);
+    });
+  }
+}
+
+void VirtualAudioStream::HandleFormatRequests() {
+  while (true) {
+    uint32_t frames_per_second, sample_format, num_channels;
+    zx_duration_t external_delay;
+    fuchsia::virtualaudio::Device::GetFormatCallback format_callback;
+
+    if (fbl::AutoLock lock(&wakeup_queue_lock_); !format_queue_.empty()) {
+      frames_per_second = frame_rate_;
+      sample_format = sample_format_;
+      num_channels = num_channels_;
+      external_delay = external_delay_nsec_;
+
+      format_callback = std::move(format_queue_.front());
+      format_queue_.pop_front();
+    } else {
+      break;
+    }
+
+    if (frames_per_second == 0) {
+      zxlogf(TRACE, "Format is not set - should not be calling GetFormat\n");
+      return;
+    }
+
+    parent_->PostToDispatcher([format_callback = std::move(format_callback),
+                               frames_per_second, sample_format, num_channels,
+                               external_delay]() {
+      format_callback(frames_per_second, sample_format, num_channels,
+                      external_delay);
+    });
+  }
+}
+
+void VirtualAudioStream::HandleBufferRequests() {
+  while (true) {
+    zx_status_t status;
+    zx::vmo ring_buffer_vmo;
+    uint32_t num_ring_buffer_frames;
+    uint32_t notifications_per_ring;
+    fuchsia::virtualaudio::Device::GetBufferCallback buffer_callback;
+
+    if (fbl::AutoLock lock(&wakeup_queue_lock_); !buffer_queue_.empty()) {
+      status = ring_buffer_vmo_.duplicate(
+          ZX_RIGHT_TRANSFER | ZX_RIGHT_READ | ZX_RIGHT_WRITE | ZX_RIGHT_MAP,
+          &ring_buffer_vmo);
+      num_ring_buffer_frames = num_ring_buffer_frames_;
+      notifications_per_ring = notifications_per_ring_;
+
+      buffer_callback = std::move(buffer_queue_.front());
+      buffer_queue_.pop_front();
+    } else {
+      break;
+    }
+
+    if (status != ZX_OK) {
+      zxlogf(ERROR, "%s failed to duplicate VMO handle - %d\n", __func__,
+             status);
+      return;
+    }
+    if (!ring_buffer_vmo.is_valid()) {
+      zxlogf(TRACE,
+             "Buffer is not set - should not be retrieving ring buffer\n");
+      return;
+    }
+
+    parent_->PostToDispatcher([buffer_callback = std::move(buffer_callback),
+                               rb_vmo = std::move(ring_buffer_vmo),
+                               num_ring_buffer_frames,
+                               notifications_per_ring]() mutable {
+      buffer_callback(std::move(rb_vmo), num_ring_buffer_frames,
+                      notifications_per_ring);
+    });
+  }
+}
+
+void VirtualAudioStream::HandlePositionRequests() {
+  while (true) {
+    zx::time start_time;
+    uint32_t num_rb_frames, frame_size, frame_rate;
+    fuchsia::virtualaudio::Device::GetPositionCallback position_callback;
+
+    if (fbl::AutoLock lock(&wakeup_queue_lock_); !position_queue_.empty()) {
+      start_time = start_time_;
+      num_rb_frames = num_ring_buffer_frames_;
+      frame_size = frame_size_;
+      frame_rate = frame_rate_;
+
+      position_callback = std::move(position_queue_.front());
+      position_queue_.pop_front();
+    } else {
+      break;
+    }
+
+    if (start_time.get() == 0) {
+      zxlogf(TRACE,
+             "Stream is not started -- should not be calling GetPosition\n");
+      return;
+    }
+
+    zx::time now = zx::clock::get_monotonic();
+    zx::duration duration_ns = now - start_time;
+    uint64_t frames = (duration_ns.get() * frame_rate) / ZX_SEC(1);
+    uint32_t ring_buffer_position = (frames % num_rb_frames) * frame_size;
+    zx_time_t time_for_position = now.get();
+
+    parent_->PostToDispatcher([position_callback = std::move(position_callback),
+                               ring_buffer_position, time_for_position]() {
+      position_callback(ring_buffer_position, time_for_position);
+    });
+  }
 }
 
 // Upon success, drivers should return a valid VMO with appropriate
@@ -223,6 +467,19 @@
 
   *out_num_rb_frames = num_ring_buffer_frames_;
 
+  zx::vmo duplicate_vmo;
+  status = ring_buffer_vmo_.duplicate(
+      ZX_RIGHT_TRANSFER | ZX_RIGHT_READ | ZX_RIGHT_WRITE | ZX_RIGHT_MAP,
+      &duplicate_vmo);
+  if (status != ZX_OK) {
+    zxlogf(ERROR, "%s failed to duplicate VMO handle for notification - %d\n",
+           __func__, status);
+    return status;
+  }
+  parent_->NotifyBufferCreated(std::move(duplicate_vmo),
+                               num_ring_buffer_frames_,
+                               notifications_per_ring_);
+
   return ZX_OK;
 }
 
@@ -240,6 +497,10 @@
   bytes_per_sec_ = frame_rate_ * frame_size_;
 
   // (Re)set external_delay_nsec_ and fifo_depth_ before leaving, if needed.
+
+  parent_->NotifySetFormat(frame_rate_, sample_format_, num_channels_,
+                           external_delay_nsec_);
+
   return ZX_OK;
 }
 
@@ -258,6 +519,9 @@
     cur_gain_state_.cur_agc = req.flags & AUDIO_SGF_AGC;
   }
 
+  parent_->NotifySetGain(cur_gain_state_.cur_mute, cur_gain_state_.cur_agc,
+                         cur_gain_state_.cur_gain);
+
   return ZX_OK;
 }
 
@@ -280,14 +544,19 @@
     notify_timer_->Arm(*out_start_time);
   }
 
+  parent_->NotifyStart(*out_start_time);
+
   return ZX_OK;
 }
 
 // Timer handler for sending out position notifications
+// TODO(mpuryear): Establish a notification cadence at the requested ring
+// positions, such as 0, 1000, 0, 1000,... instead of 4, 1006, 8, 1010, 12,...
 zx_status_t VirtualAudioStream::ProcessRingNotification() {
   ZX_DEBUG_ASSERT(us_per_notification_ > 0);
 
   zx::time now = zx::clock::get_monotonic();
+
   notify_timer_->Arm(now.get() + ZX_USEC(us_per_notification_));
 
   ::audio::audio_proto::RingBufPositionNotify resp = {};
@@ -305,16 +574,27 @@
            resp.ring_buffer_pos, now.get());
   }
 
-  return NotifyPosition(resp);
+  zx_status_t status = NotifyPosition(resp);
+
+  parent_->NotifyPosition(ring_buffer_position, now.get());
+
+  return status;
 }
 
 zx_status_t VirtualAudioStream::Stop() {
+  auto stop_time = zx::clock::get_monotonic();
+
   if (kTestPosition) {
-    zxlogf(TRACE, "%s at %ld\n", __PRETTY_FUNCTION__,
-           zx_clock_get(ZX_CLOCK_MONOTONIC));
+    zxlogf(TRACE, "%s at %ld\n", __PRETTY_FUNCTION__, stop_time.get());
   }
 
   notify_timer_->Cancel();
+
+  zx::duration duration_ns = stop_time - start_time_;
+  uint64_t frames = (duration_ns.get() * frame_rate_) / ZX_SEC(1);
+  uint32_t ring_buf_position = (frames % num_ring_buffer_frames_) * frame_size_;
+  parent_->NotifyStop(stop_time.get(), ring_buf_position);
+
   start_time_ = zx::time(0);
 
   return ZX_OK;
diff --git a/garnet/drivers/audio/virtual_audio/virtual_audio_stream.h b/garnet/drivers/audio/virtual_audio/virtual_audio_stream.h
index 30b36ce..bfb5fbb 100644
--- a/garnet/drivers/audio/virtual_audio/virtual_audio_stream.h
+++ b/garnet/drivers/audio/virtual_audio/virtual_audio_stream.h
@@ -21,6 +21,18 @@
 class VirtualAudioStream : public ::audio::SimpleAudioStream {
  public:
   void EnqueuePlugChange(bool plugged) __TA_EXCLUDES(wakeup_queue_lock_);
+  void EnqueueGainRequest(
+      fuchsia::virtualaudio::Device::GetGainCallback gain_callback)
+      __TA_EXCLUDES(wakeup_queue_lock_);
+  void EnqueueFormatRequest(
+      fuchsia::virtualaudio::Device::GetFormatCallback format_callback)
+      __TA_EXCLUDES(wakeup_queue_lock_);
+  void EnqueueBufferRequest(
+      fuchsia::virtualaudio::Device::GetBufferCallback buffer_callback)
+      __TA_EXCLUDES(wakeup_queue_lock_);
+  void EnqueuePositionRequest(
+      fuchsia::virtualaudio::Device::GetPositionCallback position_callback)
+      __TA_EXCLUDES(wakeup_queue_lock_);
 
   static fbl::RefPtr<VirtualAudioStream> CreateStream(
       VirtualAudioDeviceImpl* owner, zx_device_t* devnode, bool is_input);
@@ -64,6 +76,15 @@
       __TA_EXCLUDES(wakeup_queue_lock_);
   void HandlePlugChange(PlugType plug_change) __TA_REQUIRES(domain_->token());
 
+  void HandleGainRequests() __TA_REQUIRES(domain_->token())
+      __TA_EXCLUDES(wakeup_queue_lock_);
+  void HandleFormatRequests() __TA_REQUIRES(domain_->token())
+      __TA_EXCLUDES(wakeup_queue_lock_);
+  void HandleBufferRequests() __TA_REQUIRES(domain_->token())
+      __TA_EXCLUDES(wakeup_queue_lock_);
+  void HandlePositionRequests() __TA_REQUIRES(domain_->token())
+      __TA_EXCLUDES(wakeup_queue_lock_);
+
   // Accessed in GetBuffer, defended by token.
   fzl::VmoMapper ring_buffer_mapper_ __TA_GUARDED(domain_->token());
   zx::vmo ring_buffer_vmo_ __TA_GUARDED(domain_->token());
@@ -87,8 +108,25 @@
 
   fbl::Mutex wakeup_queue_lock_ __TA_ACQUIRED_AFTER(domain_->token());
 
+  // TODO(mpuryear): Refactor to a single queue of lambdas to dedupe this code.
   fbl::RefPtr<::dispatcher::WakeupEvent> plug_change_wakeup_;
   std::deque<PlugType> plug_queue_ __TA_GUARDED(wakeup_queue_lock_);
+
+  fbl::RefPtr<::dispatcher::WakeupEvent> gain_request_wakeup_;
+  std::deque<fuchsia::virtualaudio::Device::GetGainCallback> gain_queue_
+      __TA_GUARDED(wakeup_queue_lock_);
+
+  fbl::RefPtr<::dispatcher::WakeupEvent> format_request_wakeup_;
+  std::deque<fuchsia::virtualaudio::Device::GetFormatCallback> format_queue_
+      __TA_GUARDED(wakeup_queue_lock_);
+
+  fbl::RefPtr<::dispatcher::WakeupEvent> buffer_request_wakeup_;
+  std::deque<fuchsia::virtualaudio::Device::GetBufferCallback> buffer_queue_
+      __TA_GUARDED(wakeup_queue_lock_);
+
+  fbl::RefPtr<::dispatcher::WakeupEvent> position_request_wakeup_;
+  std::deque<fuchsia::virtualaudio::Device::GetPositionCallback> position_queue_
+      __TA_GUARDED(wakeup_queue_lock_);
 };
 
 }  // namespace virtual_audio
diff --git a/sdk/fidl/fuchsia.virtualaudio/virtual_audio.fidl b/sdk/fidl/fuchsia.virtualaudio/virtual_audio.fidl
index b20bb8d..8ded15d 100644
--- a/sdk/fidl/fuchsia.virtualaudio/virtual_audio.fidl
+++ b/sdk/fidl/fuchsia.virtualaudio/virtual_audio.fidl
@@ -9,10 +9,10 @@
 // fuchsia.virtualaudio.Forwarder
 //
 
-/// Using this Simple Layout (C-bound) interface, an intermediary (such as the
-/// virtual audio service) forwards FIDL interface requests to the virtual audio
+/// Using this Simple Layout (C-bound) protocol, an intermediary (such as the
+/// virtual audio service) forwards FIDL protocol requests to the virtual audio
 /// driver, which enables clients to use more full-featured (C++ based) bindings
-/// with this driver -- specifically the Control, Input and Output interfaces.
+/// with this driver -- specifically the Control, Input and Output protocols.
 [Layout = "Simple"]
 protocol Forwarder {
     SendControl(request<Control> control);
@@ -95,7 +95,7 @@
     /// device node will be published and detected by the AudioDeviceManager,
     /// and a driver for the virtual device will be loaded and queried. Device
     /// arrivals are exposed to clients by the
-    /// `fuchsia.media.AudioDeviceEnumerator` interface, in `GetDevices()` and
+    /// `fuchsia.media.AudioDeviceEnumerator` protocol, in `GetDevices()` and
     /// the `->OnDeviceAdded()` event.
     Add();
 
@@ -106,6 +106,58 @@
     /// `GetDevices()` and `->OnDeviceRemoved()`.
     Remove();
 
+    /// Return the format selected by the client, when that client issued an
+    /// `AUDIO_STREAM_CMD_SET_FORMAT` command. This can only occur after a
+    /// device has been added, and is only allowed to occur before the device's
+    /// ring buffer has been returned (i.e., before an
+    /// `AUDIO_RB_CMD_GET_BUFFER` command).
+    GetFormat() -> (uint32 frames_per_second, uint32 sample_format,
+                    uint32 num_channels, zx.duration external_delay);
+
+    /// Notify all listeners, when the above format is set or changed.
+    -> OnSetFormat(uint32 frames_per_second, uint32 sample_format,
+                   uint32 num_channels, zx.duration external_delay);
+
+    /// Return the current gain state for this device. After a device has been
+    /// added, a client can call this at any time -- even before the ring buffer
+    /// has been established, or before the format has been set.
+    GetGain() -> (bool current_mute, bool current_agc, float32 current_gain_db);
+
+    /// Notify all listeners, when the above gain is set or changed.
+    -> OnSetGain(bool current_mute, bool current_agc, float32 current_gain_db);
+
+    /// Return details about the ring buffer that was established in response
+    /// to a client `AUDIO_RB_CMD_GET_BUFFER` command. This will only occur
+    /// after the format is set and other information is retrieved from the
+    /// driver.
+    GetBuffer() -> (handle<vmo> ring_buffer, uint32 num_ring_buffer_frames,
+                    uint32 notifications_per_ring);
+
+    /// Notify all listeners, when the above buffer has been requested (and
+    /// thus at that time has been established).
+    -> OnBufferCreated(handle<vmo> ring_buffer, uint32 num_ring_buffer_frames,
+                       uint32 notifications_per_ring);
+
+    /// Notify a test client when the device is commanded to Start streaming.
+    /// This can only occur after a device is fully configured (format is set;
+    /// ring buffer is established and fetched).
+    -> OnStart(zx.time start_time);
+
+    /// Notify a test client when the device is commanded to Stop streaming.
+    /// This can only occur when the device is currently Started. Stop returns
+    /// the device to a fully-configured state. Restated, upon this command,
+    /// the already-set format and ring buffer are retained without change.
+    -> OnStop(zx.time stop_time, uint32 ring_position);
+
+    /// Return the current position (in bytes) within the ring buffer. This can
+    /// only be called after the ring buffer is established. If the device has
+    /// not yet Started streaming, then zero will always be returned.
+    GetPosition() -> (uint32 ring_position, zx.time clock_time);
+
+    /// Notify all listeners, when any `AUDIO_RB_POSITION_NOTIFY` position
+    /// notification is proactively issued to the client.
+    -> OnPositionNotify(uint32 ring_position, zx.time clock_time);
+
     /// Hot-plug or hot-unplug an active virtual device, at the specified time.
     /// For devices marked as capable of asynchronously notifying the system of
     /// plug changes, the driver will now send the values using
@@ -113,7 +165,7 @@
     /// the driver is next sent an `AUDIO_STREAM_CMD_PLUG_DETECT` command. This
     /// information is used by the system when determining which device is
     /// default. This, in turn, is exposed to clients by the
-    /// `fuchsia.media.AudioDeviceEnumerator` interface: in `GetDevices()`,
+    /// `fuchsia.media.AudioDeviceEnumerator` protocol: in `GetDevices()`,
     /// `GetDefaultInputDevice()`/`GetDefaultOutputDevice()` and the
     /// `->OnDefaultDeviceChanged()` event.
     ChangePlugState(zx.time plug_change_time, bool plugged);
@@ -125,7 +177,7 @@
 /// This protocol is conceptually a base protocol to Device. It exposes the
 /// methods used to specify the properties of a virtual audio device (its
 /// configuration), before the virtual device is instantiated by the call to
-/// `Add()`. Although the non-Add methods on this interface can be called after
+/// `Add()`. Although the non-Add methods on this protocol can be called after
 /// calling `Add()` (i.e., after Configuration has been converted into active
 /// Device), this only changes how future Devices will be created; it has no
 /// effect on already-created Devices.
@@ -141,7 +193,7 @@
     /// this value is returned by the driver in response to an
     /// `AUDIO_STREAM_CMD_GET_STRING` command of string ID
     /// `AUDIO_STREAM_STR_ID_MANUFACTURER`. This information is exposed to
-    /// clients by the `fuchsia.media.AudioDeviceEnumerator` interface: returned
+    /// clients by the `fuchsia.media.AudioDeviceEnumerator` protocol: returned
     /// in an `AudioDeviceInfo` struct by `GetDevices()` and the
     /// `->OnDeviceAdded()` event.
     SetManufacturer(string manufacturer_name);
@@ -151,7 +203,7 @@
     /// value is returned by the driver in response to an
     /// `AUDIO_STREAM_CMD_GET_STRING` command of string ID
     /// `AUDIO_STREAM_STR_ID_PRODUCT`. This information is exposed to clients by
-    /// the `fuchsia.media.AudioDeviceEnumerator` interface: returned in an
+    /// the `fuchsia.media.AudioDeviceEnumerator` protocol: returned in an
     /// `AudioDeviceInfo` struct by `GetDevices()` and the `->OnDeviceAdded()`
     /// event.
     SetProduct(string product_name);
@@ -160,7 +212,7 @@
     /// must be called before calling `Add()`, or after `Remove()`. Once the
     /// device is activated, this value is returned by the driver in response
     /// to an `AUDIO_STREAM_CMD_GET_UNIQUE_ID` command. This value is exposed
-    /// to clients by the `fuchsia.media.AudioDeviceEnumerator` interface:
+    /// to clients by the `fuchsia.media.AudioDeviceEnumerator` protocol:
     /// returned in an `AudioDeviceInfo` struct by `GetDevices()` or
     /// `->OnDeviceAdded()`.
     SetUniqueId(array<uint8>:16 unique_id);
@@ -175,6 +227,11 @@
                    uint32 max_frame_rate, uint8 min_channels,
                    uint8 max_channels, uint16 rate_family_flags);
 
+    /// Remove the minimal format range that is added by default to all
+    /// configurations. As with `AddFormatRange()`, this method must be called
+    /// before calling `Add()`, or after `Remove()`.
+    ClearFormatRanges();
+
     /// Set the virtual audio device's fifo depth, in bytes. This must be called
     /// before calling `Add()`, or after `Remove()`. Once the device is
     /// activated, the depth of its FIFO is returned by the driver in response
@@ -203,7 +260,7 @@
     /// information is returned by the driver in an
     /// `audio_stream_cmd_get_gain_resp` struct, in response to an
     /// `AUDIO_STREAM_CMD_GET_GAIN` command. This information is exposed to
-    /// clients by the `fuchsia.media.AudioDeviceEnumerator` interface: returned
+    /// clients by the `fuchsia.media.AudioDeviceEnumerator` protocol: returned
     /// in an `AudioGainInfo` struct by `GetDeviceGain()` and the
     /// `->OnDeviceGainChanged()` event.
     SetGainProperties(float32 min_gain_db, float32 max_gain_db,
@@ -216,7 +273,7 @@
     /// information is returned by the driver in response to an
     /// `AUDIO_STREAM_CMD_PLUG_DETECT `command. This information is used by the
     /// system when determining which device is default. This in turn is exposed
-    /// to clients by the `fuchsia.media.AudioDeviceEnumerator` interface: in
+    /// to clients by the `fuchsia.media.AudioDeviceEnumerator` protocol: in
     /// `GetDevices()`, `GetDefaultInputDevice()`/`GetDefaultOutputDevice()` and
     /// the `->OnDefaultDeviceChanged()` event.
     SetPlugProperties(zx.time plug_change_time, bool plugged, bool hardwired,