text: // Copyright 2018 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "device/bluetooth/bluetooth_advertisement_winrt.h"
  
  #include <windows.foundation.collections.h>
  #include <windows.storage.streams.h>
  
  #include <utility>
  #include <vector>
  
  #include "base/bind.h"
  #include "base/bind_helpers.h"
  #include "base/logging.h"
  #include "base/strings/string_piece.h"
  #include "base/threading/thread_task_runner_handle.h"
  #include "base/win/core_winrt_util.h"
  #include "base/win/scoped_hstring.h"
  #include "base/win/winrt_storage_util.h"
  #include "components/device_event_log/device_event_log.h"
  #include "device/bluetooth/event_utils_winrt.h"
  
  namespace device {
  
  namespace {
  
  using ABI::Windows::Devices::Bluetooth::BluetoothError;
  using ABI::Windows::Devices::Bluetooth::BluetoothError_NotSupported;
  using ABI::Windows::Devices::Bluetooth::BluetoothError_RadioNotAvailable;
  using ABI::Windows::Devices::Bluetooth::Advertisement::
      BluetoothLEAdvertisementPublisherStatus;
  using ABI::Windows::Devices::Bluetooth::Advertisement::
      BluetoothLEAdvertisementPublisherStatus_Aborted;
  using ABI::Windows::Devices::Bluetooth::Advertisement::
      BluetoothLEAdvertisementPublisherStatus_Started;
  using ABI::Windows::Devices::Bluetooth::Advertisement::
      BluetoothLEAdvertisementPublisherStatus_Stopped;
  using ABI::Windows::Devices::Bluetooth::Advertisement::
      BluetoothLEManufacturerData;
  using ABI::Windows::Devices::Bluetooth::Advertisement::
      IBluetoothLEAdvertisement;
  using ABI::Windows::Devices::Bluetooth::Advertisement::
      IBluetoothLEAdvertisementPublisher;
  using ABI::Windows::Devices::Bluetooth::Advertisement::
      IBluetoothLEAdvertisementPublisherFactory;
  using ABI::Windows::Devices::Bluetooth::Advertisement::
      IBluetoothLEAdvertisementPublisherStatusChangedEventArgs;
  using ABI::Windows::Devices::Bluetooth::Advertisement::
      IBluetoothLEManufacturerData;
  using ABI::Windows::Devices::Bluetooth::Advertisement::
      IBluetoothLEManufacturerDataFactory;
  using ABI::Windows::Foundation::Collections::IVector;
  using ABI::Windows::Storage::Streams::IBuffer;
  using Microsoft::WRL::ComPtr;
  
  void RemoveStatusChangedHandler(IBluetoothLEAdvertisementPublisher* publisher,
                                  EventRegistrationToken token) {
    HRESULT hr = publisher->remove_StatusChanged(token);
    if (FAILED(hr)) {
      BLUETOOTH_LOG(ERROR) << "Removing StatusChanged Handler failed: "
                           << logging::SystemErrorCodeToString(hr);
    }
  }
  
  }  // namespace
  
  BluetoothAdvertisementWinrt::BluetoothAdvertisementWinrt() {}
  
  bool BluetoothAdvertisementWinrt::Initialize(
      std::unique_ptr<BluetoothAdvertisement::Data> advertisement_data) {
    if (advertisement_data->service_uuids()) {
      BLUETOOTH_LOG(ERROR)
          << "Windows does not support advertising Service UUIDs.";
      return false;
    }
  
    if (advertisement_data->solicit_uuids()) {
      BLUETOOTH_LOG(ERROR)
          << "Windows does not support advertising Solicit UUIDs.";
      return false;
    }
  
    if (advertisement_data->service_data()) {
      BLUETOOTH_LOG(ERROR)
          << "Windows does not support advertising Service Data.";
      return false;
    }
  
    auto manufacturer_data = advertisement_data->manufacturer_data();
    if (!manufacturer_data) {
      BLUETOOTH_LOG(ERROR) << "No Manufacturer Data present.";
      return false;
    }
  
    ComPtr<IBluetoothLEAdvertisement> advertisement;
    HRESULT hr = ActivateBluetoothLEAdvertisementInstance(&advertisement);
    if (FAILED(hr)) {
      BLUETOOTH_LOG(ERROR) << "ActivateBluetoothLEAdvertisementInstance failed: "
                           << logging::SystemErrorCodeToString(hr);
      return false;
    }
  
    ComPtr<IVector<BluetoothLEManufacturerData*>> manufacturer_data_list;
    hr = advertisement->get_ManufacturerData(&manufacturer_data_list);
    if (FAILED(hr)) {
      BLUETOOTH_LOG(ERROR) << "Getting ManufacturerData failed: "
                           << logging::SystemErrorCodeToString(hr);
      return false;
    }
  
    ComPtr<IBluetoothLEManufacturerDataFactory> manufacturer_data_factory;
    hr = GetBluetoothLEManufacturerDataFactory(&manufacturer_data_factory);
    if (FAILED(hr)) {
      BLUETOOTH_LOG(ERROR) << "GetBluetoothLEManufacturerDataFactory failed: "
                           << logging::SystemErrorCodeToString(hr);
      return false;
    }
  
    for (const auto& pair : *manufacturer_data) {
      uint16_t manufacturer = pair.first;
      const std::vector<uint8_t>& data = pair.second;
  
      ComPtr<IBuffer> buffer;
      hr = base::win::CreateIBufferFromData(data.data(), data.size(), &buffer);
      if (FAILED(hr)) {
        BLUETOOTH_LOG(ERROR) << "CreateIBufferFromData() failed: "
                             << logging::SystemErrorCodeToString(hr);
        return false;
      }
  
      ComPtr<IBluetoothLEManufacturerData> manufacturer_data_entry;
      hr = manufacturer_data_factory->Create(manufacturer, buffer.Get(),
                                             &manufacturer_data_entry);
      if (FAILED(hr)) {
        BLUETOOTH_LOG(ERROR) << "Creating BluetoothLEManufacturerData failed: "
                             << logging::SystemErrorCodeToString(hr);
        return false;
      }
  
      hr = manufacturer_data_list->Append(manufacturer_data_entry.Get());
      if (FAILED(hr)) {
        BLUETOOTH_LOG(ERROR) << "Appending BluetoothLEManufacturerData failed: "
                             << logging::SystemErrorCodeToString(hr);
        return false;
      }
    }
  
    ComPtr<IBluetoothLEAdvertisementPublisherFactory> publisher_factory;
    hr =
        GetBluetoothLEAdvertisementPublisherActivationFactory(&publisher_factory);
    if (FAILED(hr)) {
      BLUETOOTH_LOG(ERROR)
          << "GetBluetoothLEAdvertisementPublisherActivationFactory "
             "failed:"
          << logging::SystemErrorCodeToString(hr);
      return false;
    }
  
    hr = publisher_factory->Create(advertisement.Get(), &publisher_);
    if (FAILED(hr)) {
      BLUETOOTH_LOG(ERROR)
          << "Creating IBluetoothLEAdvertisementPublisher failed: "
          << logging::SystemErrorCodeToString(hr);
      return false;
    }
  
    return true;
  }
  
  void BluetoothAdvertisementWinrt::Register(SuccessCallback callback,
                                             ErrorCallback error_callback) {
    // Register should only be called once during initialization.
    DCHECK(!status_changed_token_);
    DCHECK(!pending_register_callbacks_);
    DCHECK(!pending_unregister_callbacks_);
  
    // Register should only be called after successful initialization.
    DCHECK(publisher_);
  
    status_changed_token_ = AddTypedEventHandler(
        publisher_.Get(), &IBluetoothLEAdvertisementPublisher::add_StatusChanged,
        base::BindRepeating(&BluetoothAdvertisementWinrt::OnStatusChanged,
                            weak_ptr_factory_.GetWeakPtr()));
    if (!status_changed_token_) {
      base::ThreadTaskRunnerHandle::Get()->PostTask(
          FROM_HERE, base::BindOnce(std::move(error_callback),
                                    ERROR_STARTING_ADVERTISEMENT));
      return;
    }
  
    HRESULT hr = publisher_->Start();
    if (FAILED(hr)) {
      BLUETOOTH_LOG(ERROR)
          << "Starting IBluetoothLEAdvertisementPublisher failed: "
          << logging::SystemErrorCodeToString(hr);
      base::ThreadTaskRunnerHandle::Get()->PostTask(
          FROM_HERE, base::BindOnce(std::move(error_callback),
                                    ERROR_STARTING_ADVERTISEMENT));
      RemoveStatusChangedHandler(publisher_.Get(), *status_changed_token_);
      status_changed_token_.reset();
      return;
    }
  
    pending_register_callbacks_ = std::make_unique<PendingCallbacks>(
        std::move(callback), std::move(error_callback));
  }
  
  void BluetoothAdvertisementWinrt::Unregister(
      const SuccessCallback& success_callback,
      const ErrorCallback& error_callback) {
    // Unregister() should only be called when an advertisement is registered
    // already, or during destruction. In both of these cases there should be no
    // pending register callbacks and the publisher should be present.
    DCHECK(!pending_register_callbacks_);
    DCHECK(publisher_);
  
    if (pending_unregister_callbacks_) {
      BLUETOOTH_LOG(ERROR) << "An Unregister Operation is already in progress.";
      base::ThreadTaskRunnerHandle::Get()->PostTask(
          FROM_HERE, base::BindOnce(error_callback, ERROR_RESET_ADVERTISING));
      return;
    }
  
    BluetoothLEAdvertisementPublisherStatus status;
    HRESULT hr = publisher_->get_Status(&status);
    if (FAILED(hr)) {
      BLUETOOTH_LOG(ERROR) << "Getting the Publisher Status failed: "
                           << logging::SystemErrorCodeToString(hr);
      base::ThreadTaskRunnerHandle::Get()->PostTask(
          FROM_HERE, base::BindOnce(error_callback, ERROR_RESET_ADVERTISING));
      return;
    }
  
    if (status == BluetoothLEAdvertisementPublisherStatus_Aborted) {
      // Report an error if the publisher is in the aborted state.
      base::ThreadTaskRunnerHandle::Get()->PostTask(
          FROM_HERE, base::BindOnce(error_callback, ERROR_RESET_ADVERTISING));
      return;
    }
  
    if (status == BluetoothLEAdvertisementPublisherStatus_Stopped) {
      // Report success if the publisher is already stopped.
      base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, success_callback);
      return;
    }
  
    hr = publisher_->Stop();
    if (FAILED(hr)) {
      BLUETOOTH_LOG(ERROR)
          << "IBluetoothLEAdvertisementPublisher::Stop() failed: "
          << logging::SystemErrorCodeToString(hr);
      base::ThreadTaskRunnerHandle::Get()->PostTask(
          FROM_HERE, base::BindOnce(error_callback, ERROR_RESET_ADVERTISING));
      return;
    }
  
    pending_unregister_callbacks_ =
        std::make_unique<PendingCallbacks>(success_callback, error_callback);
  }
  
  IBluetoothLEAdvertisementPublisher*
  BluetoothAdvertisementWinrt::GetPublisherForTesting() {
    return publisher_.Get();
  }
  
  BluetoothAdvertisementWinrt::~BluetoothAdvertisementWinrt() {
    if (status_changed_token_) {
      DCHECK(publisher_);
      RemoveStatusChangedHandler(publisher_.Get(), *status_changed_token_);
    }
  
    // Stop any pending register operation.
    if (pending_register_callbacks_) {
      auto callbacks = std::move(pending_register_callbacks_);
      std::move(callbacks->error_callback).Run(ERROR_STARTING_ADVERTISEMENT);
    }
  
    // Unregister the advertisement on a best effort basis if it's not already in
    // process of doing so.
    if (!pending_unregister_callbacks_ && publisher_)
      Unregister(base::DoNothing(), base::DoNothing());
  }
  
  HRESULT
  BluetoothAdvertisementWinrt::
      GetBluetoothLEAdvertisementPublisherActivationFactory(
          IBluetoothLEAdvertisementPublisherFactory** factory) const {
    return base::win::GetActivationFactory<
        IBluetoothLEAdvertisementPublisherFactory,
        RuntimeClass_Windows_Devices_Bluetooth_Advertisement_BluetoothLEAdvertisementPublisher>(
        factory);
  }
  
  HRESULT
  BluetoothAdvertisementWinrt::ActivateBluetoothLEAdvertisementInstance(
      IBluetoothLEAdvertisement** instance) const {
    auto advertisement_hstring = base::win::ScopedHString::Create(
        RuntimeClass_Windows_Devices_Bluetooth_Advertisement_BluetoothLEAdvertisement);
    if (!advertisement_hstring.is_valid())
      return E_FAIL;
  
    ComPtr<IInspectable> inspectable;
    HRESULT hr =
        base::win::RoActivateInstance(advertisement_hstring.get(), &inspectable);
    if (FAILED(hr)) {
      BLUETOOTH_LOG(ERROR) << "RoActivateInstance failed: "
                           << logging::SystemErrorCodeToString(hr);
      return hr;
    }
  
    ComPtr<IBluetoothLEAdvertisement> advertisement;
    hr = inspectable.As(&advertisement);
    if (FAILED(hr)) {
      BLUETOOTH_LOG(ERROR) << "As IBluetoothLEAdvertisementWatcher failed: "
                           << logging::SystemErrorCodeToString(hr);
      return hr;
    }
  
    return advertisement.CopyTo(instance);
  }
  
  HRESULT
  BluetoothAdvertisementWinrt::GetBluetoothLEManufacturerDataFactory(
      IBluetoothLEManufacturerDataFactory** factory) const {
    return base::win::GetActivationFactory<
        IBluetoothLEManufacturerDataFactory,
        RuntimeClass_Windows_Devices_Bluetooth_Advertisement_BluetoothLEManufacturerData>(
        factory);
  }
  
  BluetoothAdvertisementWinrt::PendingCallbacks::PendingCallbacks(
      SuccessCallback callback,
      ErrorCallback error_callback)
      : callback(std::move(callback)),
        error_callback(std::move(error_callback)) {}
  
  BluetoothAdvertisementWinrt::PendingCallbacks::~PendingCallbacks() = default;
  
  void BluetoothAdvertisementWinrt::OnStatusChanged(
      IBluetoothLEAdvertisementPublisher* publisher,
      IBluetoothLEAdvertisementPublisherStatusChangedEventArgs* changed) {
    BluetoothLEAdvertisementPublisherStatus status;
    HRESULT hr = changed->get_Status(&status);
    if (FAILED(hr)) {
      BLUETOOTH_LOG(ERROR) << "Getting the Publisher Status failed: "
                           << logging::SystemErrorCodeToString(hr);
      return;
    }
  
    BLUETOOTH_LOG(EVENT) << "Publisher Status: " << static_cast<int>(status);
    if (status == BluetoothLEAdvertisementPublisherStatus_Stopped) {
      // Notify Observers.
      for (auto& observer : observers_)
        observer.AdvertisementReleased(this);
    }
  
    // Return early if there is no pending action.
    if (!pending_register_callbacks_ && !pending_unregister_callbacks_)
      return;
  
    // Register and Unregister should never be pending at the same time.
    DCHECK(!pending_register_callbacks_ || !pending_unregister_callbacks_);
  
    const bool is_starting = pending_register_callbacks_ != nullptr;
    ErrorCode error_code =
        is_starting ? ERROR_STARTING_ADVERTISEMENT : ERROR_RESET_ADVERTISING;
  
    // Clears out pending callbacks by moving them into a local variable and runs
    // the appropriate error callback with |error_code|.
    auto run_error_cb = [&](ErrorCode error_code) {
      auto callbacks = std::move(is_starting ? pending_register_callbacks_
                                             : pending_unregister_callbacks_);
      std::move(callbacks->error_callback).Run(error_code);
    };
  
    if (status == BluetoothLEAdvertisementPublisherStatus_Aborted) {
      BluetoothError bluetooth_error;
      hr = changed->get_Error(&bluetooth_error);
      if (FAILED(hr)) {
        BLUETOOTH_LOG(ERROR) << "Getting the Publisher Error failed: "
                             << logging::SystemErrorCodeToString(hr);
        run_error_cb(error_code);
        return;
      }
  
      BLUETOOTH_LOG(EVENT) << "Publisher aborted: "
                           << static_cast<int>(bluetooth_error);
      switch (bluetooth_error) {
        case BluetoothError_RadioNotAvailable:
          error_code = ERROR_ADAPTER_POWERED_OFF;
          break;
        case BluetoothError_NotSupported:
          error_code = ERROR_UNSUPPORTED_PLATFORM;
          break;
        default:
          break;
      }
  
      run_error_cb(error_code);
      return;
    }
  
    if (is_starting &&
        status == BluetoothLEAdvertisementPublisherStatus_Started) {
      BLUETOOTH_LOG(EVENT) << "Starting the Publisher was successful.";
      auto callbacks = std::move(pending_register_callbacks_);
      std::move(callbacks->callback).Run();
      return;
    }
  
    if (!is_starting &&
        status == BluetoothLEAdvertisementPublisherStatus_Stopped) {
      BLUETOOTH_LOG(EVENT) << "Stopping the Publisher was successful.";
      auto callbacks = std::move(pending_unregister_callbacks_);
      std::move(callbacks->callback).Run();
      return;
    }
  
    // The other states are temporary and we expect a future StatusChanged
    // event.
  }
  
  }  // namespace device
  