| // 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_low_energy_device_watcher_mac.h" |
| |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/files/file_util.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/task/task_traits.h" |
| #include "device/bluetooth/bluetooth_adapter_mac.h" |
| |
| namespace device { |
| |
| namespace { |
| |
| constexpr char kBluetoothPlistFilePath[] = |
| "/Library/Preferences/com.apple.Bluetooth.plist"; |
| |
| } // namespace |
| |
| // static |
| scoped_refptr<BluetoothLowEnergyDeviceWatcherMac> |
| BluetoothLowEnergyDeviceWatcherMac::CreateAndStartWatching( |
| scoped_refptr<base::SequencedTaskRunner> ui_thread_task_runner, |
| LowEnergyDeviceListUpdatedCallback |
| low_energy_device_list_updated_callback) { |
| auto watcher = base::MakeRefCounted<BluetoothLowEnergyDeviceWatcherMac>( |
| std::move(ui_thread_task_runner), |
| std::move(low_energy_device_list_updated_callback)); |
| watcher->Init(); |
| return watcher; |
| } |
| |
| BluetoothLowEnergyDeviceWatcherMac::BluetoothLowEnergyDeviceWatcherMac( |
| scoped_refptr<base::SequencedTaskRunner> ui_thread_task_runner, |
| LowEnergyDeviceListUpdatedCallback low_energy_device_list_updated_callback) |
| : ui_thread_task_runner_(std::move(ui_thread_task_runner)), |
| low_energy_device_list_updated_callback_( |
| std::move(low_energy_device_list_updated_callback)) { |
| DETACH_FROM_SEQUENCE(sequence_checker_); |
| } |
| |
| BluetoothLowEnergyDeviceWatcherMac::~BluetoothLowEnergyDeviceWatcherMac() { |
| file_thread_task_runner_->DeleteSoon(FROM_HERE, |
| property_list_watcher_.release()); |
| } |
| |
| void BluetoothLowEnergyDeviceWatcherMac::OnPropertyListFileChangedOnFileThread( |
| const base::FilePath& path, |
| bool error) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (error) { |
| LOG(WARNING) << "Failed to read com.apple.Bluetooth.plist."; |
| return; |
| } |
| |
| // Bluetooth property list file is expected to have the following format: |
| // |
| // "CoreBluetoothCache" => { |
| // "E7F8589A-A7D9-4B94-9A08-D89076A159F4" => { |
| // "DeviceAddress" => "11-11-11-11-11-11" |
| // "DeviceAddressType" => 1 |
| // "ServiceChangedHandle" => 3 |
| // "ServiceChangedSubscribed" => 0 |
| // "ServiceDiscoveryComplete" => 0 |
| // } |
| // "D3CAC59E-C501-4599-97DA-2DF491544EEE" => { |
| // "DeviceAddress" => "22-22-22-22-22-22" |
| // "DeviceAddressType" => 1 |
| // "ServiceChangedHandle" => 3 |
| // "ServiceChangedSubscribed" => 0 |
| // "ServiceDiscoveryComplete" => 0 |
| // } |
| // } |
| NSString* plist_file_path = base::SysUTF8ToNSString(path.value()); |
| NSDictionary* bluetooth_info_dictionary = |
| [NSDictionary dictionaryWithContentsOfFile:plist_file_path]; |
| |
| // |bluetooth_info_dictionary| is nil if there was an error reading the file |
| // or if the content of the read file cannot be represented by a dictionary. |
| if (!bluetooth_info_dictionary) |
| return; |
| |
| auto parsed_data = |
| ParseBluetoothDevicePropertyListData(bluetooth_info_dictionary); |
| ui_thread_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(low_energy_device_list_updated_callback_, |
| std::move(parsed_data))); |
| } |
| |
| void BluetoothLowEnergyDeviceWatcherMac::Init() { |
| file_thread_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&BluetoothLowEnergyDeviceWatcherMac:: |
| AddBluetoothPropertyListFileWatcher, |
| this)); |
| } |
| |
| void BluetoothLowEnergyDeviceWatcherMac::ReadBluetoothPropertyListFile() { |
| file_thread_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&BluetoothLowEnergyDeviceWatcherMac:: |
| OnPropertyListFileChangedOnFileThread, |
| this, BluetoothPlistFilePath(), false /* error */)); |
| } |
| |
| std::map<std::string, std::string> |
| BluetoothLowEnergyDeviceWatcherMac::ParseBluetoothDevicePropertyListData( |
| NSDictionary* data) { |
| std::map<std::string, std::string> updated_low_energy_devices_info; |
| NSDictionary* low_energy_devices_info = data[@"CoreBluetoothCache"]; |
| if (!low_energy_devices_info) |
| return updated_low_energy_devices_info; |
| |
| for (NSString* identifier in low_energy_devices_info) { |
| NSDictionary* device_info = low_energy_devices_info[identifier]; |
| if (!device_info) |
| continue; |
| |
| NSString* raw_device_address = device_info[@"DeviceAddress"]; |
| if (!raw_device_address) |
| continue; |
| |
| NSString* formatted_device_address = |
| [raw_device_address stringByReplacingOccurrencesOfString:@"-" |
| withString:@":"]; |
| updated_low_energy_devices_info[base::SysNSStringToUTF8(identifier)] = |
| base::SysNSStringToUTF8(formatted_device_address); |
| } |
| |
| return updated_low_energy_devices_info; |
| } |
| |
| // static |
| const base::FilePath& |
| BluetoothLowEnergyDeviceWatcherMac::BluetoothPlistFilePath() { |
| static const base::FilePath file_path(kBluetoothPlistFilePath); |
| return file_path; |
| } |
| |
| void BluetoothLowEnergyDeviceWatcherMac::AddBluetoothPropertyListFileWatcher() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| property_list_watcher_->Watch( |
| BluetoothPlistFilePath(), false /* recursive */, |
| base::BindRepeating(&BluetoothLowEnergyDeviceWatcherMac:: |
| OnPropertyListFileChangedOnFileThread, |
| this)); |
| } |
| |
| } // namespace device |