Commit 02b1b8c1 authored by rkc's avatar rkc Committed by Commit bot

Revert of Revert of Add CPP API for BLE advertisments. (patchset #1 id:1 of...

Revert of Revert of Add CPP API for BLE advertisments. (patchset #1 id:1 of https://codereview.chromium.org/1111563002/)

Reason for revert:
Can't seem to find any bots this was failing on. Trybots on the original CL that had component=shared_library are working fine and locally it builds fine too.

Original issue's description:
> Revert of Add CPP API for BLE advertisments. (patchset #12 id:220001 of https://codereview.chromium.org/1054743003/)
>
> Reason for revert:
> Breaks component=shared_library build (missing export?)
>
> ../../device/bluetooth/bluetooth_adapter_chromeos.cc:321: error: undefined reference to 'chromeos::BluetoothAdvertisementChromeOS::BluetoothAdvertisementChromeOS(scoped_ptr<device::BluetoothAdvertisement::Data, base::DefaultDeleter<device::BluetoothAdvertisement::Data> >, scoped_refptr<chromeos::BluetoothAdapterChromeOS>)'
> ../../base/memory/scoped_ptr.h:128: error: undefined reference to 'device::BluetoothAdvertisement::Data::~Data()'
> ../../device/bluetooth/bluetooth_adapter_chromeos.cc:322: error: undefined reference to 'chromeos::BluetoothAdvertisementChromeOS::Register(base::Callback<void ()> const&, base::Callback<void (device::BluetoothAdvertisement::ErrorCode)> const&)'
> obj/device/bluetooth/device_bluetooth.bluetooth_adapter_chromeos.o(.debug_addr+0x6f20): error: undefined reference to 'chromeos::BluetoothAdvertisementChromeOS::Register(base::Callback<void ()> const&, base::Callback<void (device::BluetoothAdvertisement::ErrorCode)> const&)'
>
> Original issue's description:
> > Add CPP API for BLE advertisments.
> >
> > This CL adds the new classes, changes to existing classes and tests for adding
> > the CPP API for LE advertisements. The design for this is available at
> > http://go/chrome-ble-advertising.
> >
> > R=armansito@chromium.org, jamuraa@chromium.org
> > BUG=466375
> >
> > Committed: https://crrev.com/c96da18077ef4b5ab28cb8b2684cd84386075e5a
> > Cr-Commit-Position: refs/heads/master@{#327128}
>
> TBR=armansito@chromium.org,jamuraa@chromium.org,scheib@chromium.org,rkc@chromium.org
> NOPRESUBMIT=true
> NOTREECHECKS=true
> NOTRY=true
> BUG=466375
>
> Committed: https://crrev.com/28bc6bec94e67f23a6a9a16490e6bdf721cf6b27
> Cr-Commit-Position: refs/heads/master@{#327155}

TBR=armansito@chromium.org,jamuraa@chromium.org,scheib@chromium.org,spang@chromium.org
NOPRESUBMIT=true
NOTREECHECKS=true
NOTRY=true
BUG=466375

Review URL: https://codereview.chromium.org/1108403002

Cr-Commit-Position: refs/heads/master@{#327316}
parent fefb7cfc
...@@ -37,7 +37,6 @@ class BluetoothAdvertisementServiceProviderImpl ...@@ -37,7 +37,6 @@ class BluetoothAdvertisementServiceProviderImpl
: origin_thread_id_(base::PlatformThread::CurrentId()), : origin_thread_id_(base::PlatformThread::CurrentId()),
bus_(bus), bus_(bus),
delegate_(delegate), delegate_(delegate),
object_path_(object_path),
type_(type), type_(type),
service_uuids_(service_uuids.Pass()), service_uuids_(service_uuids.Pass()),
manufacturer_data_(manufacturer_data.Pass()), manufacturer_data_(manufacturer_data.Pass()),
...@@ -49,6 +48,7 @@ class BluetoothAdvertisementServiceProviderImpl ...@@ -49,6 +48,7 @@ class BluetoothAdvertisementServiceProviderImpl
VLOG(1) << "Creating Bluetooth Advertisement: " << object_path_.value(); VLOG(1) << "Creating Bluetooth Advertisement: " << object_path_.value();
object_path_ = object_path;
exported_object_ = bus_->GetExportedObject(object_path_); exported_object_ = bus_->GetExportedObject(object_path_);
// Export Bluetooth Advertisement interface methods. // Export Bluetooth Advertisement interface methods.
...@@ -358,10 +358,6 @@ class BluetoothAdvertisementServiceProviderImpl ...@@ -358,10 +358,6 @@ class BluetoothAdvertisementServiceProviderImpl
// owns this one, and must outlive it. // owns this one, and must outlive it.
Delegate* delegate_; Delegate* delegate_;
// D-Bus object path of object we are exporting, kept so we can unregister
// again in our destructor.
dbus::ObjectPath object_path_;
// Advertisement data that needs to be provided to BlueZ when requested. // Advertisement data that needs to be provided to BlueZ when requested.
AdvertisementType type_; AdvertisementType type_;
scoped_ptr<UUIDList> service_uuids_; scoped_ptr<UUIDList> service_uuids_;
...@@ -391,7 +387,7 @@ BluetoothLEAdvertisementServiceProvider:: ...@@ -391,7 +387,7 @@ BluetoothLEAdvertisementServiceProvider::
} }
// static // static
BluetoothLEAdvertisementServiceProvider* scoped_ptr<BluetoothLEAdvertisementServiceProvider>
BluetoothLEAdvertisementServiceProvider::Create( BluetoothLEAdvertisementServiceProvider::Create(
dbus::Bus* bus, dbus::Bus* bus,
const dbus::ObjectPath& object_path, const dbus::ObjectPath& object_path,
...@@ -402,12 +398,12 @@ BluetoothLEAdvertisementServiceProvider::Create( ...@@ -402,12 +398,12 @@ BluetoothLEAdvertisementServiceProvider::Create(
scoped_ptr<UUIDList> solicit_uuids, scoped_ptr<UUIDList> solicit_uuids,
scoped_ptr<ServiceData> service_data) { scoped_ptr<ServiceData> service_data) {
if (!DBusThreadManager::Get()->IsUsingStub(DBusClientBundle::BLUETOOTH)) { if (!DBusThreadManager::Get()->IsUsingStub(DBusClientBundle::BLUETOOTH)) {
return new BluetoothAdvertisementServiceProviderImpl( return make_scoped_ptr(new BluetoothAdvertisementServiceProviderImpl(
bus, object_path, delegate, type, service_uuids.Pass(), bus, object_path, delegate, type, service_uuids.Pass(),
manufacturer_data.Pass(), solicit_uuids.Pass(), service_data.Pass()); manufacturer_data.Pass(), solicit_uuids.Pass(), service_data.Pass()));
} else { } else {
return new FakeBluetoothLEAdvertisementServiceProvider(object_path, return make_scoped_ptr(
delegate); new FakeBluetoothLEAdvertisementServiceProvider(object_path, delegate));
} }
} }
......
...@@ -50,11 +50,13 @@ class CHROMEOS_EXPORT BluetoothLEAdvertisementServiceProvider { ...@@ -50,11 +50,13 @@ class CHROMEOS_EXPORT BluetoothLEAdvertisementServiceProvider {
virtual ~BluetoothLEAdvertisementServiceProvider(); virtual ~BluetoothLEAdvertisementServiceProvider();
const dbus::ObjectPath& object_path() { return object_path_; }
// Creates the instance where |bus| is the D-Bus bus connection to export // Creates the instance where |bus| is the D-Bus bus connection to export
// the object onto, |object_path| is the object path that it should have // the object onto, |object_path| is the object path that it should have
// and |delegate| is the object to which all method calls will be passed // and |delegate| is the object to which all method calls will be passed
// and responses generated from. // and responses generated from.
static BluetoothLEAdvertisementServiceProvider* Create( static scoped_ptr<BluetoothLEAdvertisementServiceProvider> Create(
dbus::Bus* bus, dbus::Bus* bus,
const dbus::ObjectPath& object_path, const dbus::ObjectPath& object_path,
Delegate* delegate, Delegate* delegate,
...@@ -67,6 +69,10 @@ class CHROMEOS_EXPORT BluetoothLEAdvertisementServiceProvider { ...@@ -67,6 +69,10 @@ class CHROMEOS_EXPORT BluetoothLEAdvertisementServiceProvider {
protected: protected:
BluetoothLEAdvertisementServiceProvider(); BluetoothLEAdvertisementServiceProvider();
// D-Bus object path of object we are exporting, kept so we can unregister
// again in our destructor.
dbus::ObjectPath object_path_;
private: private:
DISALLOW_COPY_AND_ASSIGN(BluetoothLEAdvertisementServiceProvider); DISALLOW_COPY_AND_ASSIGN(BluetoothLEAdvertisementServiceProvider);
}; };
......
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
// found in the LICENSE file. // found in the LICENSE file.
#include "chromeos/dbus/dbus_thread_manager.h" #include "chromeos/dbus/dbus_thread_manager.h"
#include "fake_bluetooth_le_advertisement_service_provider.h" #include "chromeos/dbus/fake_bluetooth_le_advertisement_service_provider.h"
#include "fake_bluetooth_le_advertising_manager_client.h" #include "chromeos/dbus/fake_bluetooth_le_advertising_manager_client.h"
namespace chromeos { namespace chromeos {
...@@ -12,7 +12,8 @@ FakeBluetoothLEAdvertisementServiceProvider:: ...@@ -12,7 +12,8 @@ FakeBluetoothLEAdvertisementServiceProvider::
FakeBluetoothLEAdvertisementServiceProvider( FakeBluetoothLEAdvertisementServiceProvider(
const dbus::ObjectPath& object_path, const dbus::ObjectPath& object_path,
Delegate* delegate) Delegate* delegate)
: object_path_(object_path), delegate_(delegate) { : delegate_(delegate) {
object_path_ = object_path;
VLOG(1) << "Creating Bluetooth Advertisement: " << object_path_.value(); VLOG(1) << "Creating Bluetooth Advertisement: " << object_path_.value();
FakeBluetoothLEAdvertisingManagerClient* FakeBluetoothLEAdvertisingManagerClient*
......
...@@ -36,9 +36,6 @@ class CHROMEOS_EXPORT FakeBluetoothLEAdvertisementServiceProvider ...@@ -36,9 +36,6 @@ class CHROMEOS_EXPORT FakeBluetoothLEAdvertisementServiceProvider
private: private:
friend class FakeBluetoothLEAdvertisingManagerClient; friend class FakeBluetoothLEAdvertisingManagerClient;
// D-Bus object path we are faking.
dbus::ObjectPath object_path_;
// All incoming method calls are passed on to the Delegate and a callback // All incoming method calls are passed on to the Delegate and a callback
// passed to generate the reply. |delegate_| is generally the object that // passed to generate the reply. |delegate_| is generally the object that
// owns this one, and must outlive it. // owns this one, and must outlive it.
......
...@@ -54,9 +54,6 @@ void FakeBluetoothLEAdvertisingManagerClient::RegisterAdvertisement( ...@@ -54,9 +54,6 @@ void FakeBluetoothLEAdvertisingManagerClient::RegisterAdvertisement(
} else if (!currently_registered_.value().empty()) { } else if (!currently_registered_.value().empty()) {
error_callback.Run(bluetooth_advertising_manager::kErrorFailed, error_callback.Run(bluetooth_advertising_manager::kErrorFailed,
"Maximum advertisements reached"); "Maximum advertisements reached");
} else if (advertisement_object_path != currently_registered_) {
error_callback.Run(bluetooth_advertising_manager::kErrorAlreadyExists,
"Already advertising.");
} else { } else {
currently_registered_ = advertisement_object_path; currently_registered_ = advertisement_object_path;
base::MessageLoop::current()->PostTask(FROM_HERE, callback); base::MessageLoop::current()->PostTask(FROM_HERE, callback);
......
...@@ -11,8 +11,8 @@ ...@@ -11,8 +11,8 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/callback.h" #include "base/callback.h"
#include "base/observer_list.h" #include "base/observer_list.h"
#include "bluetooth_le_advertising_manager_client.h"
#include "chromeos/chromeos_export.h" #include "chromeos/chromeos_export.h"
#include "chromeos/dbus/bluetooth_le_advertising_manager_client.h"
#include "dbus/object_path.h" #include "dbus/object_path.h"
#include "dbus/property.h" #include "dbus/property.h"
......
...@@ -20,6 +20,7 @@ test("device_unittests") { ...@@ -20,6 +20,7 @@ test("device_unittests") {
"bluetooth/bluetooth_adapter_profile_chromeos_unittest.cc", "bluetooth/bluetooth_adapter_profile_chromeos_unittest.cc",
"bluetooth/bluetooth_adapter_unittest.cc", "bluetooth/bluetooth_adapter_unittest.cc",
"bluetooth/bluetooth_adapter_win_unittest.cc", "bluetooth/bluetooth_adapter_win_unittest.cc",
"bluetooth/bluetooth_advertisement_chromeos_unittest.cc",
"bluetooth/bluetooth_audio_sink_chromeos_unittest.cc", "bluetooth/bluetooth_audio_sink_chromeos_unittest.cc",
"bluetooth/bluetooth_chromeos_unittest.cc", "bluetooth/bluetooth_chromeos_unittest.cc",
"bluetooth/bluetooth_device_unittest.cc", "bluetooth/bluetooth_device_unittest.cc",
......
...@@ -31,6 +31,10 @@ component("bluetooth") { ...@@ -31,6 +31,10 @@ component("bluetooth") {
"bluetooth_adapter_profile_chromeos.h", "bluetooth_adapter_profile_chromeos.h",
"bluetooth_adapter_win.cc", "bluetooth_adapter_win.cc",
"bluetooth_adapter_win.h", "bluetooth_adapter_win.h",
"bluetooth_advertisement.cc",
"bluetooth_advertisement.h",
"bluetooth_advertisement_chromeos.cc",
"bluetooth_advertisement_chromeos.h",
"bluetooth_audio_sink.cc", "bluetooth_audio_sink.cc",
"bluetooth_audio_sink.h", "bluetooth_audio_sink.h",
"bluetooth_audio_sink_chromeos.cc", "bluetooth_audio_sink_chromeos.cc",
......
...@@ -35,6 +35,10 @@ ...@@ -35,6 +35,10 @@
"bluetooth_adapter_profile_chromeos.h", "bluetooth_adapter_profile_chromeos.h",
'bluetooth_adapter_win.cc', 'bluetooth_adapter_win.cc',
'bluetooth_adapter_win.h', 'bluetooth_adapter_win.h',
'bluetooth_advertisement.cc',
'bluetooth_advertisement.h',
'bluetooth_advertisement_chromeos.cc',
'bluetooth_advertisement_chromeos.h',
'bluetooth_audio_sink.cc', 'bluetooth_audio_sink.cc',
'bluetooth_audio_sink.h', 'bluetooth_audio_sink.h',
'bluetooth_audio_sink_chromeos.cc', 'bluetooth_audio_sink_chromeos.cc',
......
...@@ -14,12 +14,14 @@ ...@@ -14,12 +14,14 @@
#include "base/callback.h" #include "base/callback.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "device/bluetooth/bluetooth_advertisement.h"
#include "device/bluetooth/bluetooth_audio_sink.h" #include "device/bluetooth/bluetooth_audio_sink.h"
#include "device/bluetooth/bluetooth_device.h" #include "device/bluetooth/bluetooth_device.h"
#include "device/bluetooth/bluetooth_export.h" #include "device/bluetooth/bluetooth_export.h"
namespace device { namespace device {
class BluetoothAdvertisement;
class BluetoothDiscoveryFilter; class BluetoothDiscoveryFilter;
class BluetoothDiscoverySession; class BluetoothDiscoverySession;
class BluetoothGattCharacteristic; class BluetoothGattCharacteristic;
...@@ -190,6 +192,10 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapter ...@@ -190,6 +192,10 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapter
CreateServiceErrorCallback; CreateServiceErrorCallback;
typedef base::Callback<void(scoped_refptr<BluetoothAudioSink>)> typedef base::Callback<void(scoped_refptr<BluetoothAudioSink>)>
AcquiredCallback; AcquiredCallback;
typedef base::Callback<void(scoped_refptr<BluetoothAdvertisement>)>
CreateAdvertisementCallback;
typedef base::Callback<void(BluetoothAdvertisement::ErrorCode)>
CreateAdvertisementErrorCallback;
// Returns a weak pointer to a new adapter. For platforms with asynchronous // Returns a weak pointer to a new adapter. For platforms with asynchronous
// initialization, the returned adapter will run the |init_callback| once // initialization, the returned adapter will run the |init_callback| once
...@@ -369,6 +375,13 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapter ...@@ -369,6 +375,13 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapter
const AcquiredCallback& callback, const AcquiredCallback& callback,
const BluetoothAudioSink::ErrorCallback& error_callback) = 0; const BluetoothAudioSink::ErrorCallback& error_callback) = 0;
// Creates and registers an advertisement for broadcast over the LE channel.
// The created advertisement will be returned via the success callback.
virtual void RegisterAdvertisement(
scoped_ptr<BluetoothAdvertisement::Data> advertisement_data,
const CreateAdvertisementCallback& callback,
const CreateAdvertisementErrorCallback& error_callback) = 0;
protected: protected:
friend class base::RefCounted<BluetoothAdapter>; friend class base::RefCounted<BluetoothAdapter>;
friend class BluetoothDiscoverySession; friend class BluetoothDiscoverySession;
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
#include "chromeos/dbus/bluetooth_input_client.h" #include "chromeos/dbus/bluetooth_input_client.h"
#include "chromeos/dbus/dbus_thread_manager.h" #include "chromeos/dbus/dbus_thread_manager.h"
#include "device/bluetooth/bluetooth_adapter_profile_chromeos.h" #include "device/bluetooth/bluetooth_adapter_profile_chromeos.h"
#include "device/bluetooth/bluetooth_advertisement_chromeos.h"
#include "device/bluetooth/bluetooth_audio_sink_chromeos.h" #include "device/bluetooth/bluetooth_audio_sink_chromeos.h"
#include "device/bluetooth/bluetooth_device.h" #include "device/bluetooth/bluetooth_device.h"
#include "device/bluetooth/bluetooth_device_chromeos.h" #include "device/bluetooth/bluetooth_device_chromeos.h"
...@@ -312,6 +313,15 @@ void BluetoothAdapterChromeOS::RegisterAudioSink( ...@@ -312,6 +313,15 @@ void BluetoothAdapterChromeOS::RegisterAudioSink(
error_callback); error_callback);
} }
void BluetoothAdapterChromeOS::RegisterAdvertisement(
scoped_ptr<device::BluetoothAdvertisement::Data> advertisement_data,
const CreateAdvertisementCallback& callback,
const CreateAdvertisementErrorCallback& error_callback) {
scoped_refptr<BluetoothAdvertisementChromeOS> advertisement(
new BluetoothAdvertisementChromeOS(advertisement_data.Pass(), this));
advertisement->Register(base::Bind(callback, advertisement), error_callback);
}
void BluetoothAdapterChromeOS::RemovePairingDelegateInternal( void BluetoothAdapterChromeOS::RemovePairingDelegateInternal(
BluetoothDevice::PairingDelegate* pairing_delegate) { BluetoothDevice::PairingDelegate* pairing_delegate) {
// Before removing a pairing delegate make sure that there aren't any devices // Before removing a pairing delegate make sure that there aren't any devices
......
...@@ -96,6 +96,11 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterChromeOS ...@@ -96,6 +96,11 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterChromeOS
const device::BluetoothAdapter::AcquiredCallback& callback, const device::BluetoothAdapter::AcquiredCallback& callback,
const device::BluetoothAudioSink::ErrorCallback& error_callback) override; const device::BluetoothAudioSink::ErrorCallback& error_callback) override;
void RegisterAdvertisement(
scoped_ptr<device::BluetoothAdvertisement::Data> advertisement_data,
const CreateAdvertisementCallback& callback,
const CreateAdvertisementErrorCallback& error_callback) override;
// Locates the device object by object path (the devices map and // Locates the device object by object path (the devices map and
// BluetoothDevice methods are by address). // BluetoothDevice methods are by address).
BluetoothDeviceChromeOS* GetDeviceWithPath( BluetoothDeviceChromeOS* GetDeviceWithPath(
......
...@@ -71,6 +71,10 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterMac ...@@ -71,6 +71,10 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterMac
const BluetoothAudioSink::Options& options, const BluetoothAudioSink::Options& options,
const AcquiredCallback& callback, const AcquiredCallback& callback,
const BluetoothAudioSink::ErrorCallback& error_callback) override; const BluetoothAudioSink::ErrorCallback& error_callback) override;
void RegisterAdvertisement(
scoped_ptr<BluetoothAdvertisement::Data> advertisement_data,
const CreateAdvertisementCallback& callback,
const CreateAdvertisementErrorCallback& error_callback) override;
// BluetoothDiscoveryManagerMac::Observer overrides // BluetoothDiscoveryManagerMac::Observer overrides
void DeviceFound(IOBluetoothDevice* device) override; void DeviceFound(IOBluetoothDevice* device) override;
......
...@@ -140,6 +140,14 @@ void BluetoothAdapterMac::RegisterAudioSink( ...@@ -140,6 +140,14 @@ void BluetoothAdapterMac::RegisterAudioSink(
error_callback.Run(BluetoothAudioSink::ERROR_UNSUPPORTED_PLATFORM); error_callback.Run(BluetoothAudioSink::ERROR_UNSUPPORTED_PLATFORM);
} }
void BluetoothAdapterMac::RegisterAdvertisement(
scoped_ptr<BluetoothAdvertisement::Data> advertisement_data,
const CreateAdvertisementCallback& callback,
const CreateAdvertisementErrorCallback& error_callback) {
NOTIMPLEMENTED();
error_callback.Run(BluetoothAdvertisement::ERROR_UNSUPPORTED_PLATFORM);
}
void BluetoothAdapterMac::DeviceFound(IOBluetoothDevice* device) { void BluetoothAdapterMac::DeviceFound(IOBluetoothDevice* device) {
DeviceAdded(device); DeviceAdded(device);
} }
......
...@@ -71,6 +71,11 @@ class TestBluetoothAdapter : public BluetoothAdapter { ...@@ -71,6 +71,11 @@ class TestBluetoothAdapter : public BluetoothAdapter {
const AcquiredCallback& callback, const AcquiredCallback& callback,
const BluetoothAudioSink::ErrorCallback& error_callback) override {} const BluetoothAudioSink::ErrorCallback& error_callback) override {}
void RegisterAdvertisement(
scoped_ptr<BluetoothAdvertisement::Data> advertisement_data,
const CreateAdvertisementCallback& callback,
const CreateAdvertisementErrorCallback& error_callback) override {}
void TestErrorCallback() {} void TestErrorCallback() {}
ScopedVector<BluetoothDiscoverySession> discovery_sessions_; ScopedVector<BluetoothDiscoverySession> discovery_sessions_;
......
...@@ -182,6 +182,14 @@ void BluetoothAdapterWin::RegisterAudioSink( ...@@ -182,6 +182,14 @@ void BluetoothAdapterWin::RegisterAudioSink(
error_callback.Run(BluetoothAudioSink::ERROR_UNSUPPORTED_PLATFORM); error_callback.Run(BluetoothAudioSink::ERROR_UNSUPPORTED_PLATFORM);
} }
void BluetoothAdapterWin::RegisterAdvertisement(
scoped_ptr<BluetoothAdvertisement::Data> advertisement_data,
const CreateAdvertisementCallback& callback,
const CreateAdvertisementErrorCallback& error_callback) {
NOTIMPLEMENTED();
error_callback.Run(BluetoothAdvertisement::ERROR_UNSUPPORTED_PLATFORM);
}
void BluetoothAdapterWin::RemovePairingDelegateInternal( void BluetoothAdapterWin::RemovePairingDelegateInternal(
BluetoothDevice::PairingDelegate* pairing_delegate) { BluetoothDevice::PairingDelegate* pairing_delegate) {
} }
......
...@@ -71,6 +71,10 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterWin ...@@ -71,6 +71,10 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterWin
const BluetoothAudioSink::Options& options, const BluetoothAudioSink::Options& options,
const AcquiredCallback& callback, const AcquiredCallback& callback,
const BluetoothAudioSink::ErrorCallback& error_callback) override; const BluetoothAudioSink::ErrorCallback& error_callback) override;
void RegisterAdvertisement(
scoped_ptr<BluetoothAdvertisement::Data> advertisement_data,
const CreateAdvertisementCallback& callback,
const CreateAdvertisementErrorCallback& error_callback) override;
// BluetoothTaskManagerWin::Observer override // BluetoothTaskManagerWin::Observer override
void AdapterStateChanged( void AdapterStateChanged(
......
// Copyright 2015 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.h"
namespace device {
BluetoothAdvertisement::Data::Data(AdvertisementType type)
: type_(type), include_tx_power_(false) {
}
BluetoothAdvertisement::Data::~Data() {
}
BluetoothAdvertisement::Data::Data()
: type_(ADVERTISEMENT_TYPE_BROADCAST), include_tx_power_(false) {
}
void BluetoothAdvertisement::AddObserver(
BluetoothAdvertisement::Observer* observer) {
CHECK(observer);
observers_.AddObserver(observer);
}
void BluetoothAdvertisement::RemoveObserver(
BluetoothAdvertisement::Observer* observer) {
CHECK(observer);
observers_.RemoveObserver(observer);
}
BluetoothAdvertisement::BluetoothAdvertisement() {
}
BluetoothAdvertisement::~BluetoothAdvertisement() {
}
} // namespace device
// Copyright 2015 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.
#ifndef DEVICE_BLUETOOTH_BLUETOOTH_ADVERTISEMENT_H_
#define DEVICE_BLUETOOTH_BLUETOOTH_ADVERTISEMENT_H_
#include <map>
#include <string>
#include <vector>
#include "base/callback.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/observer_list.h"
#include "device/bluetooth/bluetooth_export.h"
namespace device {
// BluetoothAdvertisement represents an advertisement which advertises over the
// LE channel during its lifetime.
class DEVICE_BLUETOOTH_EXPORT BluetoothAdvertisement
: public base::RefCounted<BluetoothAdvertisement> {
public:
// Possible types of error raised while registering or unregistering
// advertisements.
enum ErrorCode {
ERROR_UNSUPPORTED_PLATFORM, // Bluetooth advertisement not supported on
// current platform.
ERROR_ADVERTISEMENT_ALREADY_EXISTS, // An advertisement is already
// registered.
ERROR_ADVERTISEMENT_DOES_NOT_EXIST, // Unregistering an advertisement which
// is not registered.
ERROR_ADVERTISEMENT_INVALID_LENGTH, // Advertisement is not of a valid
// length.
INVALID_ADVERTISEMENT_ERROR_CODE
};
// Type of advertisement.
enum AdvertisementType {
// This advertises with the type set to ADV_NONCONN_IND, which indicates
// to receivers that our device is not connectable.
ADVERTISEMENT_TYPE_BROADCAST,
// This advertises with the type set to ADV_IND or ADV_SCAN_IND, which
// indicates to receivers that our device is connectable.
ADVERTISEMENT_TYPE_PERIPHERAL
};
using UUIDList = std::vector<std::string>;
using ManufacturerData = std::map<uint16_t, std::vector<uint8_t>>;
using ServiceData = std::map<std::string, std::vector<uint8_t>>;
// Structure that holds the data for an advertisement.
class DEVICE_BLUETOOTH_EXPORT Data {
public:
Data(AdvertisementType type);
~Data();
AdvertisementType type() { return type_; }
scoped_ptr<UUIDList> service_uuids() { return service_uuids_.Pass(); }
scoped_ptr<ManufacturerData> manufacturer_data() {
return manufacturer_data_.Pass();
}
scoped_ptr<UUIDList> solicit_uuids() { return solicit_uuids_.Pass(); }
scoped_ptr<ServiceData> service_data() { return service_data_.Pass(); }
void set_service_uuids(scoped_ptr<UUIDList> service_uuids) {
service_uuids_ = service_uuids.Pass();
}
void set_manufacturer_data(scoped_ptr<ManufacturerData> manufacturer_data) {
manufacturer_data_ = manufacturer_data.Pass();
}
void set_solicit_uuids(scoped_ptr<UUIDList> solicit_uuids) {
solicit_uuids = solicit_uuids_.Pass();
}
void set_service_data(scoped_ptr<ServiceData> service_data) {
service_data = service_data_.Pass();
}
void set_include_tx_power(bool include_tx_power) {
include_tx_power_ = include_tx_power;
}
private:
Data();
AdvertisementType type_;
scoped_ptr<UUIDList> service_uuids_;
scoped_ptr<ManufacturerData> manufacturer_data_;
scoped_ptr<UUIDList> solicit_uuids_;
scoped_ptr<ServiceData> service_data_;
bool include_tx_power_;
DISALLOW_COPY_AND_ASSIGN(Data);
};
// Interface for observing changes to this advertisement.
class Observer {
public:
virtual ~Observer() {}
// Called when this advertisement is released and is no longer advertising.
virtual void AdvertisementReleased(
BluetoothAdvertisement* advertisement) = 0;
};
// Adds and removes observers for events for this advertisement.
void AddObserver(BluetoothAdvertisement::Observer* observer);
void RemoveObserver(BluetoothAdvertisement::Observer* observer);
// Unregisters this advertisement. Called on destruction of this object
// automatically but can be called directly to explicitly unregister this
// object.
using SuccessCallback = base::Closure;
using ErrorCallback = base::Callback<void(ErrorCode)>;
virtual void Unregister(const SuccessCallback& success_callback,
const ErrorCallback& error_callback) = 0;
protected:
friend class base::RefCounted<BluetoothAdvertisement>;
BluetoothAdvertisement();
// The destructor will unregister this advertisement.
virtual ~BluetoothAdvertisement();
// List of observers interested in event notifications from us. Objects in
// |observers_| are expected to outlive a BluetoothAdvertisement object.
ObserverList<BluetoothAdvertisement::Observer> observers_;
private:
DISALLOW_COPY_AND_ASSIGN(BluetoothAdvertisement);
};
} // namespace device
#endif // DEVICE_BLUETOOTH_BLUETOOTH_ADVERTISEMENT_H_
// Copyright 2015 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_chromeos.h"
#include <string>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/guid.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "chromeos/dbus/bluetooth_le_advertising_manager_client.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "dbus/bus.h"
#include "dbus/object_path.h"
#include "device/bluetooth/bluetooth_adapter_chromeos.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
namespace {
void UnregisterFailure(device::BluetoothAdvertisement::ErrorCode error) {
LOG(ERROR)
<< "BluetoothAdvertisementChromeOS::Unregister failed with error code = "
<< error;
}
void ErrorCallbackConnector(
const device::BluetoothAdapter::CreateAdvertisementErrorCallback&
error_callback,
const std::string& error_name,
const std::string& error_message) {
LOG(WARNING) << "Error while registering advertisement. error_name = "
<< error_name << ", error_message = " << error_message;
device::BluetoothAdvertisement::ErrorCode error_code;
if (error_name == bluetooth_advertising_manager::kErrorFailed ||
error_name == bluetooth_advertising_manager::kErrorAlreadyExists) {
error_code = device::BluetoothAdvertisement::ErrorCode::
ERROR_ADVERTISEMENT_ALREADY_EXISTS;
} else if (error_name ==
bluetooth_advertising_manager::kErrorInvalidArguments) {
error_code = device::BluetoothAdvertisement::ErrorCode::
ERROR_ADVERTISEMENT_INVALID_LENGTH;
} else if (error_name == bluetooth_advertising_manager::kErrorDoesNotExist) {
error_code = device::BluetoothAdvertisement::ErrorCode::
ERROR_ADVERTISEMENT_DOES_NOT_EXIST;
}
error_callback.Run(error_code);
}
} // namespace
namespace chromeos {
BluetoothAdvertisementChromeOS::BluetoothAdvertisementChromeOS(
scoped_ptr<device::BluetoothAdvertisement::Data> data,
scoped_refptr<BluetoothAdapterChromeOS> adapter)
: adapter_(adapter) {
dbus::ObjectPath advertisement_object_path = dbus::ObjectPath(
"/org/chromium/bluetooth_advertisement/" + base::GenerateGUID());
DCHECK(DBusThreadManager::Get());
provider_ = BluetoothLEAdvertisementServiceProvider::Create(
DBusThreadManager::Get()->GetSystemBus(), advertisement_object_path, this,
static_cast<BluetoothLEAdvertisementServiceProvider::AdvertisementType>(
data->type()),
data->service_uuids().Pass(), data->manufacturer_data().Pass(),
data->solicit_uuids().Pass(), data->service_data().Pass());
}
void BluetoothAdvertisementChromeOS::Register(
const base::Closure& success_callback,
const device::BluetoothAdapter::CreateAdvertisementErrorCallback&
error_callback) {
DCHECK(DBusThreadManager::Get());
DBusThreadManager::Get()
->GetBluetoothLEAdvertisingManagerClient()
->RegisterAdvertisement(
adapter_->object_path(), provider_->object_path(), success_callback,
base::Bind(&ErrorCallbackConnector, error_callback));
}
BluetoothAdvertisementChromeOS::~BluetoothAdvertisementChromeOS() {
Unregister(base::Bind(&base::DoNothing), base::Bind(&UnregisterFailure));
}
void BluetoothAdvertisementChromeOS::Unregister(
const SuccessCallback& success_callback,
const ErrorCallback& error_callback) {
// If we don't have a provider, that means we have already been unregistered,
// return an error.
if (!provider_) {
error_callback.Run(device::BluetoothAdvertisement::ErrorCode::
ERROR_ADVERTISEMENT_DOES_NOT_EXIST);
return;
}
DCHECK(DBusThreadManager::Get());
DBusThreadManager::Get()
->GetBluetoothLEAdvertisingManagerClient()
->UnregisterAdvertisement(
adapter_->object_path(), provider_->object_path(), success_callback,
base::Bind(&ErrorCallbackConnector, error_callback));
provider_.reset();
}
void BluetoothAdvertisementChromeOS::Released() {
LOG(WARNING) << "Advertisement released.";
provider_.reset();
FOR_EACH_OBSERVER(BluetoothAdvertisement::Observer, observers_,
AdvertisementReleased(this));
}
} // namespace chromeos
// Copyright 2015 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.
#ifndef DEVICE_BLUETOOTH_BLUETOOTH_ADVERTISEMENT_CHROMEOS_H_
#define DEVICE_BLUETOOTH_BLUETOOTH_ADVERTISEMENT_CHROMEOS_H_
#include "base/macros.h"
#include "chromeos/dbus/bluetooth_le_advertisement_service_provider.h"
#include "device/bluetooth/bluetooth_adapter.h"
#include "device/bluetooth/bluetooth_advertisement.h"
#include "device/bluetooth/bluetooth_export.h"
namespace chromeos {
class BluetoothLEAdvertisementServiceProvider;
class BluetoothAdapterChromeOS;
// The BluetoothAdvertisementChromeOS class implements BluetoothAdvertisement
// for the Chrome OS platform.
class DEVICE_BLUETOOTH_EXPORT BluetoothAdvertisementChromeOS
: public device::BluetoothAdvertisement,
public BluetoothLEAdvertisementServiceProvider::Delegate {
public:
BluetoothAdvertisementChromeOS(
scoped_ptr<device::BluetoothAdvertisement::Data> data,
scoped_refptr<BluetoothAdapterChromeOS> adapter);
// BluetoothAdvertisement overrides:
void Unregister(const SuccessCallback& success_callback,
const ErrorCallback& error_callback) override;
// BluetoothLEAdvertisementServiceProvider::Delegate overrides:
void Released() override;
void Register(
const base::Closure& success_callback,
const device::BluetoothAdapter::CreateAdvertisementErrorCallback&
error_callback);
// Used from tests to be able to trigger events on the fake advertisement
// provider.
BluetoothLEAdvertisementServiceProvider* provider() {
return provider_.get();
}
private:
~BluetoothAdvertisementChromeOS() override;
// Adapter this advertisement is advertising on.
scoped_refptr<BluetoothAdapterChromeOS> adapter_;
scoped_ptr<BluetoothLEAdvertisementServiceProvider> provider_;
DISALLOW_COPY_AND_ASSIGN(BluetoothAdvertisementChromeOS);
};
} // namespace chromeos
#endif // DEVICE_BLUETOOTH_BLUETOOTH_ADVERTISEMENT_CHROMEOS_H_
// Copyright 2015 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 <vector>
#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/fake_bluetooth_le_advertisement_service_provider.h"
#include "device/bluetooth/bluetooth_adapter.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/bluetooth_advertisement.h"
#include "device/bluetooth/bluetooth_advertisement_chromeos.h"
#include "testing/gtest/include/gtest/gtest.h"
using device::BluetoothAdapter;
using device::BluetoothAdapterFactory;
using device::BluetoothAdvertisement;
namespace chromeos {
class TestAdvertisementObserver : public BluetoothAdvertisement::Observer {
public:
explicit TestAdvertisementObserver(
scoped_refptr<BluetoothAdvertisement> advertisement)
: released_(false), advertisement_(advertisement) {
advertisement_->AddObserver(this);
}
~TestAdvertisementObserver() override {
advertisement_->RemoveObserver(this);
}
// BluetoothAdvertisement::Observer overrides:
void AdvertisementReleased(BluetoothAdvertisement* advertisement) override {
released_ = true;
}
bool released() { return released_; }
private:
bool released_;
scoped_refptr<BluetoothAdvertisement> advertisement_;
DISALLOW_COPY_AND_ASSIGN(TestAdvertisementObserver);
};
class BluetoothAdvertisementChromeOSTest : public testing::Test {
public:
void SetUp() override {
DBusThreadManager::Initialize();
callback_count_ = 0;
error_callback_count_ = 0;
last_callback_count_ = 0;
last_error_callback_count_ = 0;
last_error_code_ = BluetoothAdvertisement::INVALID_ADVERTISEMENT_ERROR_CODE;
GetAdapter();
}
void TearDown() override {
observer_.reset();
// The adapter should outlive the advertisement.
advertisement_ = nullptr;
adapter_ = nullptr;
DBusThreadManager::Shutdown();
}
// Gets the existing Bluetooth adapter.
void GetAdapter() {
BluetoothAdapterFactory::GetAdapter(
base::Bind(&BluetoothAdvertisementChromeOSTest::GetAdapterCallback,
base::Unretained(this)));
}
// Called whenever BluetoothAdapter is retrieved successfully.
void GetAdapterCallback(scoped_refptr<BluetoothAdapter> adapter) {
adapter_ = adapter;
ASSERT_NE(adapter_.get(), nullptr);
ASSERT_TRUE(adapter_->IsInitialized());
}
scoped_ptr<BluetoothAdvertisement::Data> CreateAdvertisementData() {
scoped_ptr<BluetoothAdvertisement::Data> data =
make_scoped_ptr(new BluetoothAdvertisement::Data(
BluetoothAdvertisement::ADVERTISEMENT_TYPE_BROADCAST));
data->set_service_uuids(
make_scoped_ptr(new BluetoothAdvertisement::UUIDList()).Pass());
data->set_manufacturer_data(
make_scoped_ptr(new BluetoothAdvertisement::ManufacturerData()).Pass());
data->set_solicit_uuids(
make_scoped_ptr(new BluetoothAdvertisement::UUIDList()).Pass());
data->set_service_data(
make_scoped_ptr(new BluetoothAdvertisement::ServiceData()).Pass());
return data.Pass();
}
// Creates and registers an advertisement with the adapter.
scoped_refptr<BluetoothAdvertisement> CreateAdvertisement() {
// Clear the last advertisement we created.
advertisement_ = nullptr;
adapter_->RegisterAdvertisement(
CreateAdvertisementData().Pass(),
base::Bind(&BluetoothAdvertisementChromeOSTest::RegisterCallback,
base::Unretained(this)),
base::Bind(
&BluetoothAdvertisementChromeOSTest::AdvertisementErrorCallback,
base::Unretained(this)));
message_loop_.RunUntilIdle();
return advertisement_;
}
void UnregisterAdvertisement(
scoped_refptr<BluetoothAdvertisement> advertisement) {
advertisement->Unregister(
base::Bind(&BluetoothAdvertisementChromeOSTest::Callback,
base::Unretained(this)),
base::Bind(
&BluetoothAdvertisementChromeOSTest::AdvertisementErrorCallback,
base::Unretained(this)));
message_loop_.RunUntilIdle();
}
void TriggerReleased(scoped_refptr<BluetoothAdvertisement> advertisement) {
BluetoothAdvertisementChromeOS* adv =
static_cast<BluetoothAdvertisementChromeOS*>(advertisement.get());
FakeBluetoothLEAdvertisementServiceProvider* provider =
static_cast<FakeBluetoothLEAdvertisementServiceProvider*>(
adv->provider());
provider->Release();
}
// Called whenever RegisterAdvertisement is completed successfully.
void RegisterCallback(scoped_refptr<BluetoothAdvertisement> advertisement) {
++callback_count_;
advertisement_ = advertisement;
ASSERT_NE(advertisement_.get(), nullptr);
}
void AdvertisementErrorCallback(
BluetoothAdvertisement::ErrorCode error_code) {
++error_callback_count_;
last_error_code_ = error_code;
}
// Generic callbacks.
void Callback() { ++callback_count_; }
void ErrorCallback() { ++error_callback_count_; }
void ExpectSuccess() {
EXPECT_EQ(last_error_callback_count_, error_callback_count_);
EXPECT_EQ(last_callback_count_ + 1, callback_count_);
last_callback_count_ = callback_count_;
last_error_callback_count_ = error_callback_count_;
}
void ExpectError(BluetoothAdvertisement::ErrorCode error_code) {
EXPECT_EQ(last_callback_count_, callback_count_);
EXPECT_EQ(last_error_callback_count_ + 1, error_callback_count_);
last_callback_count_ = callback_count_;
last_error_callback_count_ = error_callback_count_;
EXPECT_EQ(error_code, last_error_code_);
}
protected:
int callback_count_;
int error_callback_count_;
int last_callback_count_;
int last_error_callback_count_;
BluetoothAdvertisement::ErrorCode last_error_code_;
base::MessageLoopForIO message_loop_;
scoped_ptr<TestAdvertisementObserver> observer_;
scoped_refptr<BluetoothAdapter> adapter_;
scoped_refptr<BluetoothAdvertisement> advertisement_;
};
TEST_F(BluetoothAdvertisementChromeOSTest, RegisterSucceeded) {
scoped_refptr<BluetoothAdvertisement> advertisement = CreateAdvertisement();
ExpectSuccess();
EXPECT_NE(nullptr, advertisement);
UnregisterAdvertisement(advertisement);
ExpectSuccess();
}
TEST_F(BluetoothAdvertisementChromeOSTest, DoubleRegisterFailed) {
scoped_refptr<BluetoothAdvertisement> advertisement = CreateAdvertisement();
ExpectSuccess();
EXPECT_NE(nullptr, advertisement);
// Creating a second advertisement should give us an error.
scoped_refptr<BluetoothAdvertisement> advertisement2 = CreateAdvertisement();
ExpectError(BluetoothAdvertisement::ERROR_ADVERTISEMENT_ALREADY_EXISTS);
EXPECT_EQ(nullptr, advertisement2);
}
TEST_F(BluetoothAdvertisementChromeOSTest, DoubleUnregisterFailed) {
scoped_refptr<BluetoothAdvertisement> advertisement = CreateAdvertisement();
ExpectSuccess();
EXPECT_NE(nullptr, advertisement);
UnregisterAdvertisement(advertisement);
ExpectSuccess();
// Unregistering an already unregistered advertisement should give us an
// error.
UnregisterAdvertisement(advertisement);
ExpectError(BluetoothAdvertisement::ERROR_ADVERTISEMENT_DOES_NOT_EXIST);
}
TEST_F(BluetoothAdvertisementChromeOSTest, UnregisterAfterReleasedFailed) {
scoped_refptr<BluetoothAdvertisement> advertisement = CreateAdvertisement();
ExpectSuccess();
EXPECT_NE(nullptr, advertisement);
observer_.reset(new TestAdvertisementObserver(advertisement));
TriggerReleased(advertisement);
EXPECT_TRUE(observer_->released());
// Unregistering an advertisement that has been released should give us an
// error.
UnregisterAdvertisement(advertisement);
ExpectError(BluetoothAdvertisement::ERROR_ADVERTISEMENT_DOES_NOT_EXIST);
}
} // namespace chromeos
...@@ -46,4 +46,10 @@ void MockBluetoothAdapter::StartDiscoverySessionWithFilter( ...@@ -46,4 +46,10 @@ void MockBluetoothAdapter::StartDiscoverySessionWithFilter(
error_callback); error_callback);
} }
void MockBluetoothAdapter::RegisterAdvertisement(
scoped_ptr<BluetoothAdvertisement::Data> advertisement_data,
const CreateAdvertisementCallback& callback,
const CreateAdvertisementErrorCallback& error_callback) {
}
} // namespace device } // namespace device
...@@ -109,6 +109,10 @@ class MockBluetoothAdapter : public BluetoothAdapter { ...@@ -109,6 +109,10 @@ class MockBluetoothAdapter : public BluetoothAdapter {
void SetDiscoveryFilter(scoped_ptr<BluetoothDiscoveryFilter> discovery_filter, void SetDiscoveryFilter(scoped_ptr<BluetoothDiscoveryFilter> discovery_filter,
const base::Closure& callback, const base::Closure& callback,
const ErrorCallback& error_callback) override; const ErrorCallback& error_callback) override;
void RegisterAdvertisement(
scoped_ptr<BluetoothAdvertisement::Data> advertisement_data,
const CreateAdvertisementCallback& callback,
const CreateAdvertisementErrorCallback& error_callback) override;
virtual ~MockBluetoothAdapter(); virtual ~MockBluetoothAdapter();
MOCK_METHOD1(RemovePairingDelegateInternal, MOCK_METHOD1(RemovePairingDelegateInternal,
......
...@@ -36,6 +36,7 @@ ...@@ -36,6 +36,7 @@
'bluetooth/bluetooth_adapter_profile_chromeos_unittest.cc', 'bluetooth/bluetooth_adapter_profile_chromeos_unittest.cc',
'bluetooth/bluetooth_adapter_unittest.cc', 'bluetooth/bluetooth_adapter_unittest.cc',
'bluetooth/bluetooth_adapter_win_unittest.cc', 'bluetooth/bluetooth_adapter_win_unittest.cc',
'bluetooth/bluetooth_advertisement_chromeos_unittest.cc',
'bluetooth/bluetooth_audio_sink_chromeos_unittest.cc', 'bluetooth/bluetooth_audio_sink_chromeos_unittest.cc',
'bluetooth/bluetooth_chromeos_unittest.cc', 'bluetooth/bluetooth_chromeos_unittest.cc',
'bluetooth/bluetooth_device_unittest.cc', 'bluetooth/bluetooth_device_unittest.cc',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment