#include "core/internal/mediums/bluetooth_classic.h" #include #include "core/internal/mediums/uuid.h" #include "platform/synchronized.h" namespace location { namespace nearby { namespace connections { template const std::int32_t BluetoothClassic::kMaxConcurrentAcceptLoops = 5; template BluetoothClassic::BluetoothClassic( Ptr> bluetooth_radio) : lock_(Platform::createLock()), bluetooth_radio_(bluetooth_radio), bluetooth_adapter_(Platform::createBluetoothAdapter()), bluetooth_classic_medium_(Platform::createBluetoothClassicMedium()), scan_info_(), original_scan_mode_(BluetoothAdapter::ScanMode::UNKNOWN), original_device_name_(), accept_loops_thread_pool_( Platform::createMultiThreadExecutor(kMaxConcurrentAcceptLoops)), bluetooth_server_sockets_() {} template BluetoothClassic::~BluetoothClassic() { stopDiscovery(); for (BluetoothServerSocketMap::iterator it = bluetooth_server_sockets_.begin(); it != bluetooth_server_sockets_.end(); ++it) { stopAcceptingConnections(it->first); } turnOffDiscoverability(); // All the AcceptLoopRunnable objects in here should already have gotten an // opportunity to shut themselves down cleanly in the calls to // stopAcceptingConnections() above. accept_loops_thread_pool_->shutdown(); original_device_name_.destroy(); scan_info_.destroy(); } template bool BluetoothClassic::isAvailable() { Synchronized s(lock_.get()); return !bluetooth_classic_medium_.isNull() && !bluetooth_adapter_.isNull(); } template bool BluetoothClassic::turnOnDiscoverability( const string& device_name) { Synchronized s(lock_.get()); if (device_name.empty()) { // TODO(ahlee): logger.atSevere().log("Refusing to turn on Bluetooth // discoverability because a null deviceName was passed in."); return false; } if (!bluetooth_radio_->isEnabled()) { // TODO(reznor): log.atSevere().log("Can't turn on Bluetooth discoverability // because Bluetooth isn't enabled."); return false; } if (!isAvailable()) { // TODO(reznor): log.atSevere().log("Can't turn on Bluetooth discoverability // because Bluetooth isn't available."); return false; } if (isDiscoverable()) { // TODO(reznor): log.atSevere().log("Refusing to turn on Bluetooth // discoverability with device name %s because we're already discoverable // with device name %s.", deviceName, bluetoothAdapter.getName()); return false; } if (!modifyDeviceName(device_name)) { // TODO(reznor): log.atSevere().log("Failed to turn on Bluetooth // discoverability because we couldn't set the device name to %s", // deviceName); return false; } if (!modifyScanMode(BluetoothAdapter::ScanMode::CONNECTABLE_DISCOVERABLE)) { // TODO(reznor): log.atSevere().log("Failed to turn on Bluetooth // discoverability because we couldn't set the scan mode to %d", // BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE); // Don't forget to perform this rollback of the partial state changes we've // made til now. restoreDeviceName(); return false; } // TODO(reznor): log.atVerbose().log("Turned on Bluetooth discoverability with // deviceName %s", deviceName); return true; } template void BluetoothClassic::turnOffDiscoverability() { Synchronized s(lock_.get()); if (!isDiscoverable()) { // TODO(reznor): log.atDebug().log("Can't turn off Bluetooth discoverability // because it was never turned on."); return; } restoreScanMode(); restoreDeviceName(); // TODO(reznor): log.atVerbose().log("Turned Bluetooth discoverability off"); } template bool BluetoothClassic::isDiscoverable() const { return ((!original_device_name_.isNull()) && (BluetoothAdapter::ScanMode::CONNECTABLE_DISCOVERABLE == bluetooth_adapter_->getScanMode())); } template bool BluetoothClassic::modifyDeviceName(const string& device_name) { original_device_name_ = bluetooth_adapter_->getName(); if (!bluetooth_adapter_->setName(device_name)) { original_device_name_.destroy(); return false; } return true; } template bool BluetoothClassic::modifyScanMode( BluetoothAdapter::ScanMode::Value scan_mode) { original_scan_mode_ = bluetooth_adapter_->getScanMode(); if (!bluetooth_adapter_->setScanMode(scan_mode)) { original_scan_mode_ = BluetoothAdapter::ScanMode::UNKNOWN; return false; } return true; } template void BluetoothClassic::restoreScanMode() { if (!bluetooth_adapter_->setScanMode(original_scan_mode_)) { // TODO(reznor): log.atWarning().log("Failed to restore original Bluetooth // scan mode to %d", originalScanMode); } // Regardless of whether or not we could actually restore the Bluetooth scan // mode, reset our relevant state. original_scan_mode_ = BluetoothAdapter::ScanMode::UNKNOWN; } template void BluetoothClassic::restoreDeviceName() { if (!bluetooth_adapter_->setName(*original_device_name_)) { // TODO(reznor): log.atWarning().log("Failed to restore original Bluetooth // device name to %s", originalDeviceName); } // Regardless of whether or not we could actually restore the Bluetooth device // name, reset the marker that opens us up for business for the next time // 'round. original_device_name_.destroy(); } template bool BluetoothClassic::startDiscovery( Ptr discovered_device_callback) { Synchronized s(lock_.get()); if (discovered_device_callback.isNull()) { // TODO(reznor): log.atSevere().log("Refusing to start discovery of // Bluetooth devices because a null discoveredDeviceCallback was passed // in."); return false; } // Avoid leaks. ScopedPtr> scoped_discovered_device_callback( discovered_device_callback); if (!bluetooth_radio_->isEnabled()) { // TODO(reznor): log.atSevere().log("Can't discover Bluetooth devices // because Bluetooth isn't enabled."); return false; } if (!isAvailable()) { // TODO(reznor): log.atSevere().log("Can't discover Bluetooth devices // because Bluetooth isn't available."); return false; } if (isDiscovering()) { // TODO(reznor): log.atSevere().log("Refusing to start discovery of // Bluetooth devices because another discovery is already in-progress."); return false; } // Avoid leaks. ScopedPtr> scoped_bluetooth_discovery_callback(new BluetoothDiscoveryCallback( scoped_discovered_device_callback.get())); if (!bluetooth_classic_medium_->startDiscovery( scoped_bluetooth_discovery_callback.get())) { // TODO(reznor): log.atSevere().log("Failed to start discovery of Bluetooth // devices."); return false; } // Mark the fact that we're currently performing a Bluetooth scan. scan_info_ = MakePtr(new ScanInfo(scoped_discovered_device_callback.release(), scoped_bluetooth_discovery_callback.release())); return true; } template void BluetoothClassic::stopDiscovery() { Synchronized s(lock_.get()); if (!isDiscovering()) { // TODO(reznor): log.atDebug().log("Can't stop discovery of Bluetooth // devices because it never started."); return; } if (!bluetooth_classic_medium_->stopDiscovery()) { // TODO(reznor): log.atWarning().log("Failed to stop discovery of Bluetooth // devices."); } // Regardless of whether or not stopDiscovery() succeeded, destroy scan_info_ // to: // // a) Avoid a leak. // b) Mark the fact that we're no longer performing a Bluetooth discovery. scan_info_.destroy(); } template bool BluetoothClassic::isDiscovering() const { return !scan_info_.isNull(); } template class AcceptLoopRunnable : public Runnable { public: AcceptLoopRunnable( Ptr::AcceptedConnectionCallback> accepted_connection_callback, Ptr listening_socket, const string& service_name) : accepted_connection_callback_(accepted_connection_callback), listening_socket_(listening_socket), service_name_(service_name) {} void run() override { while (true) { ExceptionOr> bluetooth_socket = listening_socket_->accept(); if (!bluetooth_socket.ok()) { if (Exception::IO == bluetooth_socket.exception()) { Utils::closeSocket(listening_socket_, "Bluetooth", service_name_); } break; } accepted_connection_callback_->onConnectionAccepted( bluetooth_socket.result()); } } private: ScopedPtr< Ptr::AcceptedConnectionCallback>> accepted_connection_callback_; Ptr listening_socket_; const string service_name_; }; template bool BluetoothClassic::startAcceptingConnections( const string& service_name, Ptr accepted_connection_callback) { Synchronized s(lock_.get()); // Avoid leaks. ScopedPtr> scoped_accepted_connection_callback(accepted_connection_callback); if (scoped_accepted_connection_callback.isNull() || service_name.empty()) { // TODO(reznor): log.atSevere().log("Refusing to start accepting Bluetooth // connections because at least one of serviceName or // acceptedConnectionCallback is null."); return false; } if (!bluetooth_radio_->isEnabled()) { // TODO(reznor): log.atSevere().log("Can't create Bluetooth server socket // for %s because Bluetooth isn't enabled.", serviceName); return false; } if (!isAvailable()) { // TODO(reznor): log.atSevere().log("Can't start accepting BLuetooth // connections for %s because Bluetooth isn't available.", serviceName); return false; } if (isAcceptingConnections(service_name)) { // TODO(reznor): log.atSevere().log("Refusing to start accepting Bluetooth // connections for %s because a Bluetooth server is already in-progress for // that service name.", serviceName); return false; } ExceptionOr> listening_socket = bluetooth_classic_medium_->listenForService( service_name, generateUUIDFromString(service_name)); if (!listening_socket.ok()) { if (Exception::IO == listening_socket.exception()) { // TODO(reznor): log.atSevere().withCause(e).log("Failed to start // accepting Bluetooth connections for %s.", serviceName); return false; } } // Start the accept loop on a dedicated thread - this stays alive and // listening for new incoming connections until stopAcceptingConnections() is // invoked. accept_loops_thread_pool_->execute(MakePtr(new AcceptLoopRunnable( scoped_accepted_connection_callback.release(), listening_socket.result(), service_name))); // Mark the fact that there's an in-progress Bluetooth server accepting // connections. bluetooth_server_sockets_.insert( std::make_pair(service_name, listening_socket.result())); return true; } template bool BluetoothClassic::isAcceptingConnections( const string& service_name) { Synchronized s(lock_.get()); return bluetooth_server_sockets_.find(service_name) != bluetooth_server_sockets_.end(); } template void BluetoothClassic::stopAcceptingConnections( const string& service_name) { Synchronized s(lock_.get()); if (service_name.empty()) { // TODO(ahlee): logger.atSevere().log("Unable to stop accepting Bluetooth // connections because the serviceName is empty."); return; } if (!isAcceptingConnections(service_name)) { // TODO(reznor): log.atDebug().log("Can't stop accepting Bluetooth // connections for %s because it was never started.", serviceName); return; } // Closing the BluetoothServerSocket will kick off the suicide of the thread // in accept_loops_thread_pool_ that blocks on BluetoothServerSocket.accept(). // That may take some time to complete, but there's no particular reason to // wait around for it. BluetoothServerSocketMap::iterator listening_socket_iter = bluetooth_server_sockets_.find(service_name); // Store a handle to the BluetoothServerSocket, so we can use it after // removing the entry from bluetooth_server_sockets_; making it scoped // is a bonus that takes care of deallocation before we leave this method. ScopedPtr> scoped_listening_socket( listening_socket_iter->second); // Regardless of whether or not we fail to close the existing // BluetoothServerSocket, remove it from bluetooth_server_sockets_ so that it // frees up this service for another round. bluetooth_server_sockets_.erase(listening_socket_iter); // Finally, close the BluetoothServerSocket. Exception::Value e = scoped_listening_socket->close(); if (Exception::NONE != e) { if (Exception::IO == e) { // TODO(reznor): log.atSevere().withCause(e).log("Failed to close // Bluetooth server socket for %s.", serviceName); } } } template Ptr BluetoothClassic::connect( Ptr bluetooth_device, const string& service_name) { Synchronized s(lock_.get()); if (bluetooth_device.isNull() || service_name.empty()) { // TODO(reznor): log.atSevere().log("Refusing to create client Bluetooth // socket because at least one of bluetoothDevice or serviceName is null."); return Ptr(); } if (!bluetooth_radio_->isEnabled()) { // TODO(reznor): log.atSevere().log("Can't create client Bluetooth socket to // %s because Bluetooth isn't enabled.", bluetoothSocketName); return Ptr(); } if (!isAvailable()) { // TODO(reznor): log.atSevere().log("Can't create client Bluetooth socket to // %s because Bluetooth isn't available.", bluetoothSocketName); return Ptr(); } // WARNING WARNING WARNING // // This block deviates from the corresponding Java code. // // In Java, we pause an in-progress discovery before attempting this // connection, and then resume it after, but the memory management of the // DiscoveredDeviceCallback is complicated in C++, and would need a severe // deviation from the Java code, so we're choosing the lesser of 2 evils, and // introducing this (simplifying) deviation instead -- also, this deviation is // fairly inconsequential since we don't yet have a use-case that needs a // device that: // // a) uses the C++ code, // b) has Bluetooth Classic support, and // c) plays the role of Discoverer. ExceptionOr> bluetooth_socket = bluetooth_classic_medium_->connectToService( bluetooth_device, generateUUIDFromString(service_name)); if (!bluetooth_socket.ok()) { if (Exception::IO == bluetooth_socket.exception()) { // TODO(reznor): log.atSevere().log("Failed to connect via Bluetooth // socket to %s.", bluetoothSocketName); } return Ptr(); } return bluetooth_socket.result(); } template string BluetoothClassic::generateUUIDFromString(const string& data) { return UUID(data).str(); } } // namespace connections } // namespace nearby } // namespace location