chromium/media/midi/midi_manager_winrt.cc

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "media/midi/midi_manager_winrt.h"
#include "base/memory/raw_ptr.h"

#pragma warning(disable : 4467)

#define INITGUID

#include <objbase.h>

#include <initguid.h>
#include <windows.h>

#include <cfgmgr32.h>
#include <comdef.h>
#include <devpkey.h>
#include <robuffer.h>
#include <windows.devices.enumeration.h>
#include <windows.devices.midi.h>
#include <wrl/client.h>
#include <wrl/event.h>

#include <iomanip>
#include <memory>
#include <unordered_map>
#include <unordered_set>

#include "base/containers/heap_array.h"
#include "base/functional/bind.h"
#include "base/scoped_generic.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/timer/timer.h"
#include "base/win/core_winrt_util.h"
#include "base/win/scoped_hstring.h"
#include "base/win/winrt_storage_util.h"
#include "media/midi/midi_service.h"
#include "media/midi/task_service.h"

namespace midi {
namespace {

namespace WRL = Microsoft::WRL;
namespace Win = ABI::Windows;

using base::win::GetActivationFactory;
using base::win::ScopedHString;
using mojom::PortState;
using mojom::Result;
using Win::Devices::Enumeration::DeviceInformationUpdate;
using Win::Devices::Enumeration::DeviceWatcher;
using Win::Devices::Enumeration::IDeviceInformation;
using Win::Devices::Enumeration::IDeviceInformationUpdate;
using Win::Devices::Enumeration::IDeviceWatcher;
using Win::Foundation::IAsyncOperation;
using Win::Foundation::ITypedEventHandler;

// Alias for printing HRESULT.
const auto PrintHr = logging::SystemErrorCodeToString;

enum {
  kDefaultTaskRunner = TaskService::kDefaultRunnerId,
  kComTaskRunner
};

template <typename T>
std::string GetIdString(T* obj) {
  HSTRING result;
  HRESULT hr = obj->get_Id(&result);
  if (FAILED(hr)) {
    VLOG(1) << "get_Id failed: " << PrintHr(hr);
    return std::string();
  }
  return ScopedHString(result).GetAsUTF8();
}

template <typename T>
std::string GetDeviceIdString(T* obj) {
  HSTRING result;
  HRESULT hr = obj->get_DeviceId(&result);
  if (FAILED(hr)) {
    VLOG(1) << "get_DeviceId failed: " << PrintHr(hr);
    return std::string();
  }
  return ScopedHString(result).GetAsUTF8();
}

std::string GetNameString(IDeviceInformation* info) {
  HSTRING result;
  HRESULT hr = info->get_Name(&result);
  if (FAILED(hr)) {
    VLOG(1) << "get_Name failed: " << PrintHr(hr);
    return std::string();
  }
  return ScopedHString(result).GetAsUTF8();
}

// Checks if given DeviceInformation represent a Microsoft GS Wavetable Synth
// instance.
bool IsMicrosoftSynthesizer(IDeviceInformation* info) {
  WRL::ComPtr<Win::Devices::Midi::IMidiSynthesizerStatics>
      midi_synthesizer_statics;
  HRESULT hr =
      GetActivationFactory<Win::Devices::Midi::IMidiSynthesizerStatics,
                           RuntimeClass_Windows_Devices_Midi_MidiSynthesizer>(
          &midi_synthesizer_statics);
  if (FAILED(hr)) {
    VLOG(1) << "IMidiSynthesizerStatics factory failed: " << PrintHr(hr);
    return false;
  }
  boolean result = FALSE;
  hr = midi_synthesizer_statics->IsSynthesizer(info, &result);
  VLOG_IF(1, FAILED(hr)) << "IsSynthesizer failed: " << PrintHr(hr);
  return result != FALSE;
}

void GetDevPropString(DEVINST handle,
                      const DEVPROPKEY* devprop_key,
                      std::string* out) {
  DEVPROPTYPE devprop_type;
  unsigned long buffer_size = 0;

  // Retrieve |buffer_size| and allocate buffer later for receiving data.
  CONFIGRET cr = CM_Get_DevNode_Property(handle, devprop_key, &devprop_type,
                                         nullptr, &buffer_size, 0);
  if (cr != CR_BUFFER_SMALL) {
    // Here we print error codes in hex instead of using PrintHr() with
    // HRESULT_FROM_WIN32() and CM_MapCrToWin32Err(), since only a minor set of
    // CONFIGRET values are mapped to Win32 errors. Same for following VLOG()s.
    VLOG(1) << "CM_Get_DevNode_Property failed: CONFIGRET 0x" << std::hex << cr;
    return;
  }
  if (devprop_type != DEVPROP_TYPE_STRING) {
    VLOG(1) << "CM_Get_DevNode_Property returns wrong data type, "
            << "expected DEVPROP_TYPE_STRING";
    return;
  }

  auto buffer = base::HeapArray<uint8_t>::Uninit(buffer_size);

  // Receive property data.
  cr = CM_Get_DevNode_Property(handle, devprop_key, &devprop_type,
                               buffer.data(), &buffer_size, 0);
  if (cr != CR_SUCCESS)
    VLOG(1) << "CM_Get_DevNode_Property failed: CONFIGRET 0x" << std::hex << cr;
  else
    *out = base::WideToUTF8(reinterpret_cast<wchar_t*>(buffer.data()));
}

// Retrieves manufacturer (provider) and version information of underlying
// device driver through PnP Configuration Manager, given device (interface) ID
// provided by WinRT. |out_manufacturer| and |out_driver_version| won't be
// modified if retrieval fails.
//
// Device instance ID is extracted from device (interface) ID provided by WinRT
// APIs, for example from the following interface ID:
// \\?\SWD#MMDEVAPI#MIDII_60F39FCA.P_0002#{504be32c-ccf6-4d2c-b73f-6f8b3747e22b}
// we extract the device instance ID: SWD\MMDEVAPI\MIDII_60F39FCA.P_0002
//
// However the extracted device instance ID represent a "software device"
// provided by Microsoft, which is an interface on top of the hardware for each
// input/output port. Therefore we further locate its parent device, which is
// the actual hardware device, for driver information.
void GetDriverInfoFromDeviceId(const std::string& dev_id,
                               std::string* out_manufacturer,
                               std::string* out_driver_version) {
  std::wstring dev_instance_id =
      base::UTF8ToWide(dev_id.substr(4, dev_id.size() - 43));
  base::ReplaceChars(dev_instance_id, L"#", L"\\", &dev_instance_id);

  DEVINST dev_instance_handle;
  CONFIGRET cr = CM_Locate_DevNode(&dev_instance_handle, &dev_instance_id[0],
                                   CM_LOCATE_DEVNODE_NORMAL);
  if (cr != CR_SUCCESS) {
    VLOG(1) << "CM_Locate_DevNode failed: CONFIGRET 0x" << std::hex << cr;
    return;
  }

  DEVINST parent_handle;
  cr = CM_Get_Parent(&parent_handle, dev_instance_handle, 0);
  if (cr != CR_SUCCESS) {
    VLOG(1) << "CM_Get_Parent failed: CONFIGRET 0x" << std::hex << cr;
    return;
  }

  GetDevPropString(parent_handle, &DEVPKEY_Device_DriverProvider,
                   out_manufacturer);
  GetDevPropString(parent_handle, &DEVPKEY_Device_DriverVersion,
                   out_driver_version);
}

// Tokens with value = 0 are considered invalid (as in <wrl/event.h>).
const int64_t kInvalidTokenValue = 0;

template <typename InterfaceType>
struct MidiPort {
  MidiPort() = default;

  MidiPort(const MidiPort&) = delete;
  MidiPort& operator=(const MidiPort&) = delete;

  uint32_t index;
  WRL::ComPtr<InterfaceType> handle;
  EventRegistrationToken token_MessageReceived;
};

}  // namespace

template <typename InterfaceType,
          typename RuntimeType,
          typename StaticsInterfaceType,
          wchar_t const* runtime_class_id>
class MidiManagerWinrt::MidiPortManager {
 public:
  // MidiPortManager instances should be constructed on the kComTaskRunner.
  MidiPortManager(MidiManagerWinrt* midi_manager)
      : midi_service_(midi_manager->service()), midi_manager_(midi_manager) {}

  virtual ~MidiPortManager() {
    DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
  }

  bool StartWatcher() {
    DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));

    HRESULT hr = GetActivationFactory<StaticsInterfaceType, runtime_class_id>(
        &midi_port_statics_);
    if (FAILED(hr)) {
      VLOG(1) << "StaticsInterfaceType factory failed: " << PrintHr(hr);
      return false;
    }

    HSTRING device_selector = nullptr;
    hr = midi_port_statics_->GetDeviceSelector(&device_selector);
    if (FAILED(hr)) {
      VLOG(1) << "GetDeviceSelector failed: " << PrintHr(hr);
      return false;
    }

    WRL::ComPtr<Win::Devices::Enumeration::IDeviceInformationStatics>
        dev_info_statics;
    hr = GetActivationFactory<
        Win::Devices::Enumeration::IDeviceInformationStatics,
        RuntimeClass_Windows_Devices_Enumeration_DeviceInformation>(
        &dev_info_statics);
    if (FAILED(hr)) {
      VLOG(1) << "IDeviceInformationStatics failed: " << PrintHr(hr);
      return false;
    }

    hr = dev_info_statics->CreateWatcherAqsFilter(device_selector, &watcher_);
    if (FAILED(hr)) {
      VLOG(1) << "CreateWatcherAqsFilter failed: " << PrintHr(hr);
      return false;
    }

    // Register callbacks to WinRT that post state-modifying tasks back to
    // kComTaskRunner. All posted tasks run only during the MidiPortManager
    // instance is alive. This is ensured by MidiManagerWinrt by calling
    // UnbindInstance() before destructing any MidiPortManager instance. Thus
    // we can handle raw pointers safely in the following blocks.
    MidiPortManager* port_manager = this;
    TaskService* task_service = midi_service_->task_service();

    hr = watcher_->add_Added(
        WRL::Callback<ITypedEventHandler<
            DeviceWatcher*, Win::Devices::Enumeration::DeviceInformation*>>(
            [port_manager, task_service](IDeviceWatcher* watcher,
                                         IDeviceInformation* info) {
              if (!info) {
                VLOG(1) << "DeviceWatcher.Added callback provides null "
                           "pointer, ignoring";
                return S_OK;
              }

              // Disable Microsoft GS Wavetable Synth due to security reasons.
              // http://crbug.com/499279
              if (IsMicrosoftSynthesizer(info))
                return S_OK;

              std::string dev_id = GetIdString(info),
                          dev_name = GetNameString(info);

              task_service->PostBoundTask(
                  kComTaskRunner, base::BindOnce(&MidiPortManager::OnAdded,
                                                 base::Unretained(port_manager),
                                                 dev_id, dev_name));

              return S_OK;
            })
            .Get(),
        &token_Added_);
    if (FAILED(hr)) {
      VLOG(1) << "add_Added failed: " << PrintHr(hr);
      return false;
    }

    hr = watcher_->add_EnumerationCompleted(
        WRL::Callback<ITypedEventHandler<DeviceWatcher*, IInspectable*>>(
            [port_manager, task_service](IDeviceWatcher* watcher,
                                         IInspectable* insp) {
              task_service->PostBoundTask(
                  kComTaskRunner,
                  base::BindOnce(&MidiPortManager::OnEnumerationCompleted,
                                 base::Unretained(port_manager)));

              return S_OK;
            })
            .Get(),
        &token_EnumerationCompleted_);
    if (FAILED(hr)) {
      VLOG(1) << "add_EnumerationCompleted failed: " << PrintHr(hr);
      return false;
    }

    hr = watcher_->add_Removed(
        WRL::Callback<
            ITypedEventHandler<DeviceWatcher*, DeviceInformationUpdate*>>(
            [port_manager, task_service](IDeviceWatcher* watcher,
                                         IDeviceInformationUpdate* update) {
              if (!update) {
                VLOG(1) << "DeviceWatcher.Removed callback provides null "
                           "pointer, ignoring";
                return S_OK;
              }

              std::string dev_id = GetIdString(update);

              task_service->PostBoundTask(
                  kComTaskRunner,
                  base::BindOnce(&MidiPortManager::OnRemoved,
                                 base::Unretained(port_manager), dev_id));

              return S_OK;
            })
            .Get(),
        &token_Removed_);
    if (FAILED(hr)) {
      VLOG(1) << "add_Removed failed: " << PrintHr(hr);
      return false;
    }

    hr = watcher_->add_Stopped(
        WRL::Callback<ITypedEventHandler<DeviceWatcher*, IInspectable*>>(
            [](IDeviceWatcher* watcher, IInspectable* insp) {
              // Placeholder, does nothing for now.
              return S_OK;
            })
            .Get(),
        &token_Stopped_);
    if (FAILED(hr)) {
      VLOG(1) << "add_Stopped failed: " << PrintHr(hr);
      return false;
    }

    hr = watcher_->add_Updated(
        WRL::Callback<
            ITypedEventHandler<DeviceWatcher*, DeviceInformationUpdate*>>(
            [](IDeviceWatcher* watcher, IDeviceInformationUpdate* update) {
              // TODO(shaochuan): Check for fields to be updated here.
              return S_OK;
            })
            .Get(),
        &token_Updated_);
    if (FAILED(hr)) {
      VLOG(1) << "add_Updated failed: " << PrintHr(hr);
      return false;
    }

    hr = watcher_->Start();
    if (FAILED(hr)) {
      VLOG(1) << "Start failed: " << PrintHr(hr);
      return false;
    }

    is_initialized_ = true;
    return true;
  }

  void StopWatcher() {
    DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));

    HRESULT hr;

    for (const auto& entry : ports_)
      RemovePortEventHandlers(entry.second.get());

    if (token_Added_.value != kInvalidTokenValue) {
      hr = watcher_->remove_Added(token_Added_);
      VLOG_IF(1, FAILED(hr)) << "remove_Added failed: " << PrintHr(hr);
      token_Added_.value = kInvalidTokenValue;
    }
    if (token_EnumerationCompleted_.value != kInvalidTokenValue) {
      hr = watcher_->remove_EnumerationCompleted(token_EnumerationCompleted_);
      VLOG_IF(1, FAILED(hr)) << "remove_EnumerationCompleted failed: "
                             << PrintHr(hr);
      token_EnumerationCompleted_.value = kInvalidTokenValue;
    }
    if (token_Removed_.value != kInvalidTokenValue) {
      hr = watcher_->remove_Removed(token_Removed_);
      VLOG_IF(1, FAILED(hr)) << "remove_Removed failed: " << PrintHr(hr);
      token_Removed_.value = kInvalidTokenValue;
    }
    if (token_Stopped_.value != kInvalidTokenValue) {
      hr = watcher_->remove_Stopped(token_Stopped_);
      VLOG_IF(1, FAILED(hr)) << "remove_Stopped failed: " << PrintHr(hr);
      token_Stopped_.value = kInvalidTokenValue;
    }
    if (token_Updated_.value != kInvalidTokenValue) {
      hr = watcher_->remove_Updated(token_Updated_);
      VLOG_IF(1, FAILED(hr)) << "remove_Updated failed: " << PrintHr(hr);
      token_Updated_.value = kInvalidTokenValue;
    }

    if (is_initialized_) {
      hr = watcher_->Stop();
      VLOG_IF(1, FAILED(hr)) << "Stop failed: " << PrintHr(hr);
      is_initialized_ = false;
    }
  }

  MidiPort<InterfaceType>* GetPortByDeviceId(std::string dev_id) {
    DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
    CHECK(is_initialized_);

    auto it = ports_.find(dev_id);
    if (it == ports_.end())
      return nullptr;
    return it->second.get();
  }

  MidiPort<InterfaceType>* GetPortByIndex(uint32_t port_index) {
    DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
    CHECK(is_initialized_);

    return GetPortByDeviceId(port_ids_[port_index]);
  }

 protected:
  // Points to the MidiService instance, which is expected to outlive the
  // MidiPortManager instance.
  raw_ptr<MidiService> midi_service_;

  // Points to the MidiManagerWinrt instance, which is safe to be accessed
  // from tasks that are invoked by TaskService.
  raw_ptr<MidiManagerWinrt> midi_manager_;

 private:
  // DeviceWatcher callbacks:
  void OnAdded(std::string dev_id, std::string dev_name) {
    DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
    CHECK(is_initialized_);

    port_names_[dev_id] = dev_name;

    ScopedHString dev_id_hstring = ScopedHString::Create(dev_id);
    if (!dev_id_hstring.is_valid())
      return;

    IAsyncOperation<RuntimeType*>* async_op;

    HRESULT hr =
        midi_port_statics_->FromIdAsync(dev_id_hstring.get(), &async_op);
    if (FAILED(hr)) {
      VLOG(1) << "FromIdAsync failed: " << PrintHr(hr);
      return;
    }

    MidiPortManager* port_manager = this;
    TaskService* task_service = midi_service_->task_service();

    hr = async_op->put_Completed(
        WRL::Callback<
            Win::Foundation::IAsyncOperationCompletedHandler<RuntimeType*>>(
            [port_manager, task_service](
                IAsyncOperation<RuntimeType*>* async_op, AsyncStatus status) {
              // A reference to |async_op| is kept in |async_ops_|, safe to pass
              // outside.
              task_service->PostBoundTask(
                  kComTaskRunner,
                  base::BindOnce(
                      &MidiPortManager::OnCompletedGetPortFromIdAsync,
                      base::Unretained(port_manager),
                      base::Unretained(async_op)));

              return S_OK;
            })
            .Get());
    if (FAILED(hr)) {
      VLOG(1) << "put_Completed failed: " << PrintHr(hr);
      return;
    }

    // Keep a reference to incompleted |async_op| for releasing later.
    async_ops_.insert(async_op);
  }

  void OnEnumerationCompleted() {
    DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
    CHECK(is_initialized_);

    if (async_ops_.empty())
      midi_manager_->OnPortManagerReady();
    else
      enumeration_completed_not_ready_ = true;
  }

  void OnRemoved(std::string dev_id) {
    DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
    CHECK(is_initialized_);

    // Note: in case Microsoft GS Wavetable Synth triggers this event for some
    // reason, it will be ignored here with log emitted.
    MidiPort<InterfaceType>* port = GetPortByDeviceId(dev_id);
    if (!port) {
      VLOG(1) << "Removing non-existent port " << dev_id;
      return;
    }

    SetPortState(port->index, PortState::DISCONNECTED);

    RemovePortEventHandlers(port);
    port->handle = nullptr;
  }

  void OnCompletedGetPortFromIdAsync(IAsyncOperation<RuntimeType*>* async_op) {
    DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));
    CHECK(is_initialized_);

    InterfaceType* handle = nullptr;
    HRESULT hr = async_op->GetResults(&handle);
    if (FAILED(hr)) {
      VLOG(1) << "GetResults failed: " << PrintHr(hr);
      return;
    }

    // Manually release COM interface to completed |async_op|.
    auto it = async_ops_.find(async_op);
    CHECK(it != async_ops_.end());
    (*it)->Release();
    async_ops_.erase(it);

    if (!handle) {
      VLOG(1) << "Midi{In,Out}Port.FromIdAsync callback provides null pointer, "
                 "ignoring";
      return;
    }

    EventRegistrationToken token = {kInvalidTokenValue};
    if (!RegisterOnMessageReceived(handle, &token))
      return;

    std::string dev_id = GetDeviceIdString(handle);

    MidiPort<InterfaceType>* port = GetPortByDeviceId(dev_id);

    if (port == nullptr) {
      std::string manufacturer = "Unknown", driver_version = "Unknown";
      GetDriverInfoFromDeviceId(dev_id, &manufacturer, &driver_version);

      AddPort(mojom::PortInfo(dev_id, manufacturer, port_names_[dev_id],
                              driver_version, PortState::OPENED));

      port = new MidiPort<InterfaceType>;
      port->index = static_cast<uint32_t>(port_ids_.size());

      ports_[dev_id].reset(port);
      port_ids_.push_back(dev_id);
    } else {
      SetPortState(port->index, PortState::CONNECTED);
    }

    port->handle = handle;
    port->token_MessageReceived = token;

    if (enumeration_completed_not_ready_ && async_ops_.empty()) {
      midi_manager_->OnPortManagerReady();
      enumeration_completed_not_ready_ = false;
    }
  }

  // Overridden by MidiInPortManager to listen to input ports.
  virtual bool RegisterOnMessageReceived(InterfaceType* handle,
                                         EventRegistrationToken* p_token) {
    return true;
  }

  // Overridden by MidiInPortManager to remove MessageReceived event handler.
  virtual void RemovePortEventHandlers(MidiPort<InterfaceType>* port) {}

  // Calls midi_manager_->Add{Input,Output}Port.
  virtual void AddPort(mojom::PortInfo info) = 0;

  // Calls midi_manager_->Set{Input,Output}PortState.
  virtual void SetPortState(uint32_t port_index, PortState state) = 0;

  // Midi{In,Out}PortStatics instance.
  WRL::ComPtr<StaticsInterfaceType> midi_port_statics_;

  // DeviceWatcher instance and event registration tokens for unsubscribing
  // events in destructor.
  WRL::ComPtr<IDeviceWatcher> watcher_;
  EventRegistrationToken token_Added_ = {kInvalidTokenValue},
                         token_EnumerationCompleted_ = {kInvalidTokenValue},
                         token_Removed_ = {kInvalidTokenValue},
                         token_Stopped_ = {kInvalidTokenValue},
                         token_Updated_ = {kInvalidTokenValue};

  // All manipulations to these fields should be done on kComTaskRunner.
  std::unordered_map<std::string, std::unique_ptr<MidiPort<InterfaceType>>>
      ports_;
  std::vector<std::string> port_ids_;
  std::unordered_map<std::string, std::string> port_names_;

  // Keeps AsyncOperation references before the operation completes. Note that
  // raw pointers are used here and the COM interfaces should be released
  // manually.
  std::unordered_set<IAsyncOperation<RuntimeType*>*> async_ops_;

  // Set when device enumeration is completed but OnPortManagerReady() is not
  // called since some ports are not yet ready (i.e. |async_ops_| is not empty).
  // In such cases, OnPortManagerReady() will be called in
  // OnCompletedGetPortFromIdAsync() when the last pending port is ready.
  bool enumeration_completed_not_ready_ = false;

  // Set if the instance is initialized without error. Should be checked in all
  // methods on kComTaskRunner except StartWatcher().
  bool is_initialized_ = false;
};

class MidiManagerWinrt::MidiInPortManager final
    : public MidiPortManager<Win::Devices::Midi::IMidiInPort,
                             Win::Devices::Midi::MidiInPort,
                             Win::Devices::Midi::IMidiInPortStatics,
                             RuntimeClass_Windows_Devices_Midi_MidiInPort> {
 public:
  MidiInPortManager(MidiManagerWinrt* midi_manager)
      : MidiPortManager(midi_manager) {}

  MidiInPortManager(const MidiInPortManager&) = delete;
  MidiInPortManager& operator=(const MidiInPortManager&) = delete;

 private:
  // MidiPortManager overrides:
  bool RegisterOnMessageReceived(Win::Devices::Midi::IMidiInPort* handle,
                                 EventRegistrationToken* p_token) override {
    DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));

    MidiInPortManager* port_manager = this;
    TaskService* task_service = midi_service_->task_service();

    HRESULT hr = handle->add_MessageReceived(
        WRL::Callback<ITypedEventHandler<
            Win::Devices::Midi::MidiInPort*,
            Win::Devices::Midi::MidiMessageReceivedEventArgs*>>(
            [port_manager, task_service](
                Win::Devices::Midi::IMidiInPort* handle,
                Win::Devices::Midi::IMidiMessageReceivedEventArgs* args) {
              const base::TimeTicks now = base::TimeTicks::Now();

              std::string dev_id = GetDeviceIdString(handle);

              WRL::ComPtr<Win::Devices::Midi::IMidiMessage> message;
              HRESULT hr = args->get_Message(&message);
              if (FAILED(hr)) {
                VLOG(1) << "get_Message failed: " << PrintHr(hr);
                return hr;
              }

              WRL::ComPtr<Win::Storage::Streams::IBuffer> buffer;
              hr = message->get_RawData(&buffer);
              if (FAILED(hr)) {
                VLOG(1) << "get_RawData failed: " << PrintHr(hr);
                return hr;
              }

              uint8_t* p_buffer_data = nullptr;
              uint32_t data_length = 0;
              hr = base::win::GetPointerToBufferData(
                  buffer.Get(), &p_buffer_data, &data_length);
              if (FAILED(hr))
                return hr;

              std::vector<uint8_t> data(p_buffer_data,
                                        p_buffer_data + data_length);

              task_service->PostBoundTask(
                  kComTaskRunner,
                  base::BindOnce(&MidiInPortManager::OnMessageReceived,
                                 base::Unretained(port_manager), dev_id, data,
                                 now));

              return S_OK;
            })
            .Get(),
        p_token);
    if (FAILED(hr)) {
      VLOG(1) << "add_MessageReceived failed: " << PrintHr(hr);
      return false;
    }

    return true;
  }

  void RemovePortEventHandlers(
      MidiPort<Win::Devices::Midi::IMidiInPort>* port) override {
    if (!(port->handle &&
          port->token_MessageReceived.value != kInvalidTokenValue))
      return;

    HRESULT hr =
        port->handle->remove_MessageReceived(port->token_MessageReceived);
    VLOG_IF(1, FAILED(hr)) << "remove_MessageReceived failed: " << PrintHr(hr);
    port->token_MessageReceived.value = kInvalidTokenValue;
  }

  void AddPort(mojom::PortInfo info) final {
    midi_manager_->AddInputPort(info);
  }

  void SetPortState(uint32_t port_index, PortState state) final {
    midi_manager_->SetInputPortState(port_index, state);
  }

  // Callback on receiving MIDI input message.
  void OnMessageReceived(std::string dev_id,
                         std::vector<uint8_t> data,
                         base::TimeTicks time) {
    DCHECK(midi_service_->task_service()->IsOnTaskRunner(kComTaskRunner));

    MidiPort<Win::Devices::Midi::IMidiInPort>* port = GetPortByDeviceId(dev_id);
    CHECK(port);

    midi_manager_->ReceiveMidiData(port->index, &data[0], data.size(), time);
  }
};

class MidiManagerWinrt::MidiOutPortManager final
    : public MidiPortManager<Win::Devices::Midi::IMidiOutPort,
                             Win::Devices::Midi::IMidiOutPort,
                             Win::Devices::Midi::IMidiOutPortStatics,
                             RuntimeClass_Windows_Devices_Midi_MidiOutPort> {
 public:
  MidiOutPortManager(MidiManagerWinrt* midi_manager)
      : MidiPortManager(midi_manager) {}

  MidiOutPortManager(const MidiOutPortManager&) = delete;
  MidiOutPortManager& operator=(const MidiOutPortManager&) = delete;

 private:
  // MidiPortManager overrides:
  void AddPort(mojom::PortInfo info) final {
    midi_manager_->AddOutputPort(info);
  }

  void SetPortState(uint32_t port_index, PortState state) final {
    midi_manager_->SetOutputPortState(port_index, state);
  }
};

namespace {

// FinalizeOnComRunner() run on kComTaskRunner even after the MidiManager
// instance destruction.
void FinalizeOnComRunner(
    std::unique_ptr<MidiManagerWinrt::MidiInPortManager> port_manager_in,
    std::unique_ptr<MidiManagerWinrt::MidiOutPortManager> port_manager_out) {
  if (port_manager_in)
    port_manager_in->StopWatcher();

  if (port_manager_out)
    port_manager_out->StopWatcher();
}

}  // namespace

MidiManagerWinrt::MidiManagerWinrt(MidiService* service)
    : MidiManager(service) {}

MidiManagerWinrt::~MidiManagerWinrt() {
  // Unbind and take a lock to ensure that InitializeOnComRunner should not run
  // after here.
  if (!service()->task_service()->UnbindInstance())
    return;

  base::AutoLock auto_lock(lazy_init_member_lock_);
  service()->task_service()->PostStaticTask(
      kComTaskRunner,
      base::BindOnce(&FinalizeOnComRunner, std::move(port_manager_in_),
                     std::move(port_manager_out_)));
}

void MidiManagerWinrt::StartInitialization() {
  if (!service()->task_service()->BindInstance())
    return CompleteInitialization(Result::INITIALIZATION_ERROR);

  service()->task_service()->PostBoundTask(
      kComTaskRunner, base::BindOnce(&MidiManagerWinrt::InitializeOnComRunner,
                                     base::Unretained(this)));
}

void MidiManagerWinrt::DispatchSendMidiData(MidiManagerClient* client,
                                            uint32_t port_index,
                                            const std::vector<uint8_t>& data,
                                            base::TimeTicks timestamp) {
  base::TimeDelta delay = MidiService::TimestampToTimeDeltaDelay(timestamp);
  service()->task_service()->PostBoundDelayedTask(
      kComTaskRunner,
      base::BindOnce(&MidiManagerWinrt::SendOnComRunner, base::Unretained(this),
                     port_index, data),
      delay);
  service()->task_service()->PostBoundDelayedTask(
      kComTaskRunner,
      base::BindOnce(&MidiManagerWinrt::AccumulateMidiBytesSent,
                     base::Unretained(this), client, data.size()),
      delay);
}

void MidiManagerWinrt::InitializeOnComRunner() {
  base::AutoLock auto_lock(lazy_init_member_lock_);

  DCHECK(service()->task_service()->IsOnTaskRunner(kComTaskRunner));

  port_manager_in_ = std::make_unique<MidiInPortManager>(this);
  port_manager_out_ = std::make_unique<MidiOutPortManager>(this);

  if (!(port_manager_in_->StartWatcher() &&
        port_manager_out_->StartWatcher())) {
    port_manager_in_->StopWatcher();
    port_manager_out_->StopWatcher();
    service()->task_service()->PostBoundTask(
        kDefaultTaskRunner,
        base::BindOnce(&MidiManagerWinrt::CompleteInitialization,
                       base::Unretained(this), Result::INITIALIZATION_ERROR));
  }
}

void MidiManagerWinrt::SendOnComRunner(uint32_t port_index,
                                       const std::vector<uint8_t>& data) {
  DCHECK(service()->task_service()->IsOnTaskRunner(kComTaskRunner));

  base::AutoLock auto_lock(lazy_init_member_lock_);
  MidiPort<Win::Devices::Midi::IMidiOutPort>* port =
      port_manager_out_->GetPortByIndex(port_index);
  if (!(port && port->handle)) {
    VLOG(1) << "Port not available: " << port_index;
    return;
  }

  WRL::ComPtr<Win::Storage::Streams::IBuffer> buffer;
  HRESULT hr = base::win::CreateIBufferFromData(
      data.data(), static_cast<UINT32>(data.size()), &buffer);
  if (FAILED(hr)) {
    VLOG(1) << "CreateIBufferFromData failed: " << PrintHr(hr);
    return;
  }

  hr = port->handle->SendBuffer(buffer.Get());
  if (FAILED(hr)) {
    VLOG(1) << "SendBuffer failed: " << PrintHr(hr);
    return;
  }
}

void MidiManagerWinrt::OnPortManagerReady() {
  DCHECK(service()->task_service()->IsOnTaskRunner(kComTaskRunner));
  DCHECK(port_manager_ready_count_ < 2);

  if (++port_manager_ready_count_ == 2) {
    service()->task_service()->PostBoundTask(
        kDefaultTaskRunner,
        base::BindOnce(&MidiManagerWinrt::CompleteInitialization,
                       base::Unretained(this), Result::OK));
  }
}

}  // namespace midi