bluetooth_hid_detector_impl.cc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. // Copyright 2022 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ash/components/hid_detection/bluetooth_hid_detector_impl.h"
  5. #include "ash/public/cpp/bluetooth_config_service.h"
  6. #include "base/strings/utf_string_conversions.h"
  7. #include "components/device_event_log/device_event_log.h"
  8. #include "hid_detection_utils.h"
  9. namespace ash::hid_detection {
  10. namespace {
  11. using chromeos::bluetooth_config::mojom::BluetoothDevicePropertiesPtr;
  12. using chromeos::bluetooth_config::mojom::BluetoothSystemState;
  13. using chromeos::bluetooth_config::mojom::DeviceType;
  14. using chromeos::bluetooth_config::mojom::KeyEnteredHandler;
  15. // Returns the BluetoothHidType corresponding with |device|'s device type, or
  16. // absl::nullopt if |device| is not a HID.
  17. absl::optional<BluetoothHidDetector::BluetoothHidType> GetBluetoothHidType(
  18. const BluetoothDevicePropertiesPtr& device) {
  19. switch (device->device_type) {
  20. case DeviceType::kMouse:
  21. [[fallthrough]];
  22. case DeviceType::kTablet:
  23. return BluetoothHidDetector::BluetoothHidType::kPointer;
  24. case DeviceType::kKeyboard:
  25. return BluetoothHidDetector::BluetoothHidType::kKeyboard;
  26. case DeviceType::kKeyboardMouseCombo:
  27. return BluetoothHidDetector::BluetoothHidType::kKeyboardPointerCombo;
  28. default:
  29. return absl::nullopt;
  30. }
  31. }
  32. } // namespace
  33. BluetoothHidDetectorImpl::BluetoothHidDetectorImpl() = default;
  34. BluetoothHidDetectorImpl::~BluetoothHidDetectorImpl() {
  35. DCHECK_EQ(kNotStarted, state_) << " HID detection must be stopped before "
  36. << "BluetoothHidDetectorImpl is destroyed.";
  37. }
  38. void BluetoothHidDetectorImpl::SetInputDevicesStatus(
  39. InputDevicesStatus input_devices_status) {
  40. HID_LOG(EVENT) << "Input devices status set, pointer missing: "
  41. << input_devices_status.pointer_is_missing
  42. << ", keyboard missing: "
  43. << input_devices_status.keyboard_is_missing;
  44. input_devices_status_ = input_devices_status;
  45. if (!current_pairing_device_)
  46. return;
  47. absl::optional<BluetoothHidDetector::BluetoothHidType>
  48. current_pairing_device_hid_type =
  49. GetBluetoothHidType(current_pairing_device_.value());
  50. DCHECK(current_pairing_device_hid_type)
  51. << current_pairing_device_.value()->id
  52. << " does not have a valid HID type, device type: "
  53. << current_pairing_device_.value()->device_type;
  54. if (IsHidTypeMissing(current_pairing_device_hid_type.value()))
  55. return;
  56. // If the HID type of |current_pairing_device_| is no longer missing, this can
  57. // mean 2 things:
  58. // 1. |current_pairing_device_| has successfully finished pairing, and the
  59. // client of this class has now invoked this method to inform this class
  60. // that the HID type is no longer missing. Clear the current pairing state
  61. // and process the next device in the queue.
  62. // 2. |current_pairing_device_| is currently pairing, and a device of the
  63. // same type has been detected as connected. Clear the current pairing
  64. // state, which will cause the current pairing to cancel, and process the
  65. // next device in the queue.
  66. HID_LOG(EVENT) << "Device type of "
  67. << current_pairing_device_.value()->device_type << " for "
  68. << current_pairing_device_.value()->id
  69. << " is no longer missing";
  70. ClearCurrentPairingState();
  71. }
  72. const BluetoothHidDetector::BluetoothHidDetectionStatus
  73. BluetoothHidDetectorImpl::GetBluetoothHidDetectionStatus() {
  74. if (!current_pairing_device_.has_value()) {
  75. return BluetoothHidDetectionStatus(
  76. /*current_pairing_device*/ absl::nullopt,
  77. /*pairing_state=*/absl::nullopt);
  78. }
  79. absl::optional<BluetoothHidPairingState> pairing_state;
  80. if (current_pairing_state_.has_value()) {
  81. pairing_state = BluetoothHidPairingState{
  82. current_pairing_state_.value().code,
  83. current_pairing_state_.value().num_keys_entered};
  84. }
  85. absl::optional<BluetoothHidType> hid_type =
  86. GetBluetoothHidType(current_pairing_device_.value());
  87. DCHECK(hid_type) << " |current_pairing_device_| has an invalid HID type";
  88. return BluetoothHidDetectionStatus{
  89. BluetoothHidMetadata{
  90. base::UTF16ToUTF8(current_pairing_device_.value()->public_name),
  91. hid_type.value()},
  92. std::move(pairing_state)};
  93. }
  94. void BluetoothHidDetectorImpl::PerformStartBluetoothHidDetection(
  95. InputDevicesStatus input_devices_status) {
  96. DCHECK_EQ(kNotStarted, state_);
  97. HID_LOG(EVENT) << "Starting Bluetooth HID detection, pointer missing: "
  98. << input_devices_status.pointer_is_missing
  99. << ", keyboard missing: "
  100. << input_devices_status.keyboard_is_missing;
  101. input_devices_status_ = input_devices_status;
  102. state_ = kStarting;
  103. num_pairing_attempts_ = 0;
  104. GetBluetoothConfigService(
  105. cros_bluetooth_config_remote_.BindNewPipeAndPassReceiver());
  106. cros_bluetooth_config_remote_->ObserveSystemProperties(
  107. system_properties_observer_receiver_.BindNewPipeAndPassRemote());
  108. }
  109. void BluetoothHidDetectorImpl::PerformStopBluetoothHidDetection(
  110. bool is_using_bluetooth) {
  111. DCHECK_NE(kNotStarted, state_)
  112. << " Call to StopBluetoothHidDetection() while "
  113. << "HID detection is inactive.";
  114. HID_LOG(EVENT) << "Stopping Bluetooth HID detection, |is_using_bluetooth|: "
  115. << is_using_bluetooth;
  116. hid_detection::RecordBluetoothPairingAttempts(num_pairing_attempts_);
  117. state_ = kNotStarted;
  118. cros_bluetooth_config_remote_->SetBluetoothHidDetectionInactive(
  119. is_using_bluetooth);
  120. cros_bluetooth_config_remote_.reset();
  121. system_properties_observer_receiver_.reset();
  122. ResetDiscoveryState();
  123. }
  124. void BluetoothHidDetectorImpl::OnPropertiesUpdated(
  125. chromeos::bluetooth_config::mojom::BluetoothSystemPropertiesPtr
  126. properties) {
  127. switch (state_) {
  128. case kNotStarted:
  129. NOTREACHED() << "SystemPropertiesObserver should not be bound while in "
  130. "state |kNotStarted|";
  131. return;
  132. case kStarting:
  133. if (properties->system_state == BluetoothSystemState::kEnabled) {
  134. HID_LOG(EVENT)
  135. << "Bluetooth adapter is already enabled, starting discovery";
  136. state_ = kDetecting;
  137. cros_bluetooth_config_remote_->StartDiscovery(
  138. bluetooth_discovery_delegate_receiver_.BindNewPipeAndPassRemote());
  139. } else if (properties->system_state == BluetoothSystemState::kDisabled ||
  140. properties->system_state == BluetoothSystemState::kDisabling) {
  141. HID_LOG(EVENT) << "Bluetooth adapter is disabled or disabling, "
  142. << "enabling adapter";
  143. state_ = kEnablingAdapter;
  144. cros_bluetooth_config_remote_->SetBluetoothHidDetectionActive();
  145. } else {
  146. HID_LOG(EVENT)
  147. << "Bluetooth adapter is unavailable or enabling, waiting "
  148. << "for next state change";
  149. }
  150. return;
  151. case kEnablingAdapter:
  152. if (properties->system_state == BluetoothSystemState::kEnabled) {
  153. HID_LOG(EVENT)
  154. << "Bluetooth adapter has become enabled, starting discovery";
  155. state_ = kDetecting;
  156. cros_bluetooth_config_remote_->StartDiscovery(
  157. bluetooth_discovery_delegate_receiver_.BindNewPipeAndPassRemote());
  158. }
  159. return;
  160. case kDetecting:
  161. if (properties->system_state != BluetoothSystemState::kEnabled) {
  162. HID_LOG(EVENT) << "Bluetooth adapter has stopped being enabled while "
  163. << "Bluetooth HID detection is in progress";
  164. state_ = kStoppedExternally;
  165. }
  166. return;
  167. case kStoppedExternally:
  168. if (properties->system_state == BluetoothSystemState::kEnabled) {
  169. HID_LOG(EVENT) << "Bluetooth adapter has become enabled after being "
  170. << "unenabled externally, starting discovery";
  171. state_ = kDetecting;
  172. cros_bluetooth_config_remote_->StartDiscovery(
  173. bluetooth_discovery_delegate_receiver_.BindNewPipeAndPassRemote());
  174. }
  175. return;
  176. }
  177. }
  178. void BluetoothHidDetectorImpl::OnBluetoothDiscoveryStarted(
  179. mojo::PendingRemote<chromeos::bluetooth_config::mojom::DevicePairingHandler>
  180. handler) {
  181. HID_LOG(EVENT) << "Bluetooth discovery started.";
  182. DCHECK(!device_pairing_handler_remote_);
  183. device_pairing_handler_remote_.Bind(std::move(handler));
  184. }
  185. void BluetoothHidDetectorImpl::OnBluetoothDiscoveryStopped() {
  186. HID_LOG(EVENT) << "Bluetooth discovery stopped.";
  187. ResetDiscoveryState();
  188. }
  189. void BluetoothHidDetectorImpl::OnDiscoveredDevicesListChanged(
  190. std::vector<BluetoothDevicePropertiesPtr> discovered_devices) {
  191. for (const auto& discovered_device : discovered_devices) {
  192. if (!ShouldAttemptToPairWithDevice(discovered_device))
  193. continue;
  194. if (queued_device_ids_.contains(discovered_device->id))
  195. continue;
  196. queued_device_ids_.insert(discovered_device->id);
  197. queue_->emplace(discovered_device.Clone());
  198. HID_LOG(EVENT) << "Queuing device: " << discovered_device->id << ". ["
  199. << queue_->size() << "] devices now in queue.";
  200. }
  201. ProcessQueue();
  202. }
  203. void BluetoothHidDetectorImpl::RequestPinCode(RequestPinCodeCallback callback) {
  204. DCHECK(current_pairing_device_)
  205. << "RequestPinCode() called with no |current_pairing_device_|";
  206. // RequestPinCode auth is not attributed to HIDs, cancel the pairing.
  207. HID_LOG(EVENT) << "RequestPinCode auth required for "
  208. << current_pairing_device_.value()->id
  209. << ", cancelling pairing";
  210. ClearCurrentPairingState();
  211. }
  212. void BluetoothHidDetectorImpl::RequestPasskey(RequestPasskeyCallback callback) {
  213. DCHECK(current_pairing_device_)
  214. << "RequestPasskey() called with no |current_pairing_device_|";
  215. // RequestPasskey auth is not attributed to HIDs, cancel the pairing.
  216. HID_LOG(EVENT) << "RequestPasskey auth required for "
  217. << current_pairing_device_.value()->id
  218. << ", cancelling pairing";
  219. ClearCurrentPairingState();
  220. }
  221. void BluetoothHidDetectorImpl::DisplayPinCode(
  222. const std::string& pin_code,
  223. mojo::PendingReceiver<KeyEnteredHandler> handler) {
  224. DCHECK(current_pairing_device_)
  225. << "DisplayPinCode() called with no |current_pairing_device_|";
  226. HID_LOG(EVENT) << "DisplayPinCode auth required for "
  227. << current_pairing_device_.value()->id
  228. << ", pin code: " << pin_code;
  229. RequirePairingCode(pin_code, std::move(handler));
  230. }
  231. void BluetoothHidDetectorImpl::DisplayPasskey(
  232. const std::string& passkey,
  233. mojo::PendingReceiver<KeyEnteredHandler> handler) {
  234. DCHECK(current_pairing_device_)
  235. << "DisplayPasskey() called with no |current_pairing_device_|";
  236. HID_LOG(EVENT) << "DisplayPasskey auth required for "
  237. << current_pairing_device_.value()->id
  238. << ", passkey: " << passkey;
  239. RequirePairingCode(passkey, std::move(handler));
  240. }
  241. void BluetoothHidDetectorImpl::ConfirmPasskey(const std::string& passkey,
  242. ConfirmPasskeyCallback callback) {
  243. DCHECK(current_pairing_device_)
  244. << "ConfirmPasskey() called with no |current_pairing_device_|";
  245. // ConfirmPasskey auth is not attributed to HIDs, cancel the pairing.
  246. HID_LOG(EVENT) << "ConfirmPasskey auth required for "
  247. << current_pairing_device_.value()->id
  248. << ", cancelling pairing";
  249. ClearCurrentPairingState();
  250. }
  251. void BluetoothHidDetectorImpl::AuthorizePairing(
  252. AuthorizePairingCallback callback) {
  253. DCHECK(current_pairing_device_)
  254. << "AuthorizePairing() called with no |current_pairing_device_|";
  255. HID_LOG(EVENT) << "AuthorizePairing auth required for "
  256. << current_pairing_device_.value()->id
  257. << ", automatically authorizing";
  258. std::move(callback).Run(/*confirmed=*/true);
  259. }
  260. void BluetoothHidDetectorImpl::HandleKeyEntered(uint8_t num_keys_entered) {
  261. DCHECK(current_pairing_device_)
  262. << "HandleKeyEntered() called with no |current_pairing_device_|";
  263. DCHECK(current_pairing_state_)
  264. << "HandleKeyEntered() called with no |current_pairing_state_|";
  265. HID_LOG(EVENT) << "HandleKeyEntered called with "
  266. << static_cast<unsigned>(num_keys_entered) << " keys entered";
  267. current_pairing_state_->num_keys_entered = num_keys_entered;
  268. NotifyBluetoothHidDetectionStatusChanged();
  269. }
  270. bool BluetoothHidDetectorImpl::IsHidTypeMissing(
  271. BluetoothHidDetector::BluetoothHidType hid_type) {
  272. switch (hid_type) {
  273. case BluetoothHidDetector::BluetoothHidType::kPointer:
  274. return input_devices_status_.pointer_is_missing;
  275. case BluetoothHidDetector::BluetoothHidType::kKeyboard:
  276. return input_devices_status_.keyboard_is_missing;
  277. case BluetoothHidDetector::BluetoothHidType::kKeyboardPointerCombo:
  278. return input_devices_status_.pointer_is_missing ||
  279. input_devices_status_.keyboard_is_missing;
  280. }
  281. }
  282. bool BluetoothHidDetectorImpl::ShouldAttemptToPairWithDevice(
  283. const BluetoothDevicePropertiesPtr& device) {
  284. absl::optional<BluetoothHidDetector::BluetoothHidType> hid_type =
  285. GetBluetoothHidType(device);
  286. if (!hid_type)
  287. return false;
  288. return IsHidTypeMissing(hid_type.value());
  289. }
  290. void BluetoothHidDetectorImpl::ProcessQueue() {
  291. if (current_pairing_device_)
  292. return;
  293. if (queue_->empty()) {
  294. HID_LOG(DEBUG) << "No devices queued";
  295. return;
  296. }
  297. current_pairing_device_ = std::move(queue_->front());
  298. queue_->pop();
  299. HID_LOG(EVENT) << "Popped device with id: "
  300. << current_pairing_device_.value()->id
  301. << " from front of queue. [" << queue_->size()
  302. << "] devices now in queue.";
  303. if (!ShouldAttemptToPairWithDevice(current_pairing_device_.value())) {
  304. HID_LOG(EVENT) << "Device with id " << current_pairing_device_.value()->id
  305. << " no longer should be attempted to be paired with, "
  306. << "processing next device in queue. Device type: "
  307. << current_pairing_device_.value()->device_type;
  308. current_pairing_device_.reset();
  309. ProcessQueue();
  310. return;
  311. }
  312. HID_LOG(EVENT) << "Pairing with device with id: "
  313. << current_pairing_device_.value()->id;
  314. ++num_pairing_attempts_;
  315. device_pairing_handler_remote_->PairDevice(
  316. current_pairing_device_.value()->id,
  317. device_pairing_delegate_receiver_.BindNewPipeAndPassRemote(),
  318. base::BindOnce(&BluetoothHidDetectorImpl::OnPairDevice,
  319. weak_ptr_factory_.GetWeakPtr(),
  320. std::make_unique<base::ElapsedTimer>()));
  321. NotifyBluetoothHidDetectionStatusChanged();
  322. }
  323. void BluetoothHidDetectorImpl::OnPairDevice(
  324. std::unique_ptr<base::ElapsedTimer> pairing_timer,
  325. chromeos::bluetooth_config::mojom::PairingResult pairing_result) {
  326. DCHECK(current_pairing_device_)
  327. << "OnPairDevice() called with no |current_pairing_device_|";
  328. HID_LOG(EVENT) << "Finished pairing with "
  329. << current_pairing_device_.value()->id
  330. << ", result: " << pairing_result << ", [" << queue_->size()
  331. << "] devices still in queue.";
  332. const bool success =
  333. pairing_result ==
  334. chromeos::bluetooth_config::mojom::PairingResult::kSuccess;
  335. hid_detection::RecordBluetoothPairingResult(success,
  336. pairing_timer->Elapsed());
  337. // If pairing has succeeded, wait for SetInputDevicesStatus() to be called
  338. // with the corresponding HID type no longer missing.
  339. if (success) {
  340. HID_LOG(EVENT)
  341. << "Pairing succeeded, waiting for input devices status to update.";
  342. return;
  343. }
  344. HID_LOG(ERROR) << "Pairing failed, clearing current pairing state and "
  345. << "processing the next device in queue.";
  346. ClearCurrentPairingState();
  347. }
  348. void BluetoothHidDetectorImpl::ClearCurrentPairingState() {
  349. // If there is an ongoing pairing, it will be cancelled. Invalidate the
  350. // pairing finished callback.
  351. weak_ptr_factory_.InvalidateWeakPtrs();
  352. queued_device_ids_.erase(current_pairing_device_.value()->id);
  353. current_pairing_device_.reset();
  354. current_pairing_state_.reset();
  355. device_pairing_delegate_receiver_.reset();
  356. key_entered_handler_receiver_.reset();
  357. NotifyBluetoothHidDetectionStatusChanged();
  358. ProcessQueue();
  359. }
  360. void BluetoothHidDetectorImpl::ResetDiscoveryState() {
  361. // Reset Mojo-related properties.
  362. bluetooth_discovery_delegate_receiver_.reset();
  363. device_pairing_handler_remote_.reset();
  364. device_pairing_delegate_receiver_.reset();
  365. key_entered_handler_receiver_.reset();
  366. // Reset queue-related properties.
  367. current_pairing_device_.reset();
  368. current_pairing_state_.reset();
  369. queue_ = std::make_unique<base::queue<
  370. chromeos::bluetooth_config::mojom::BluetoothDevicePropertiesPtr>>();
  371. queued_device_ids_.clear();
  372. // Inform the client that no device is currently pairing.
  373. NotifyBluetoothHidDetectionStatusChanged();
  374. }
  375. void BluetoothHidDetectorImpl::RequirePairingCode(
  376. const std::string& code,
  377. mojo::PendingReceiver<chromeos::bluetooth_config::mojom::KeyEnteredHandler>
  378. handler) {
  379. DCHECK(!current_pairing_state_) << "RequirePairingCode() called "
  380. << "with |current_pairing_state_| already "
  381. << "initialized";
  382. DCHECK(!key_entered_handler_receiver_.is_bound());
  383. key_entered_handler_receiver_.Bind(std::move(handler));
  384. current_pairing_state_ =
  385. BluetoothHidPairingState{code, /*num_keys_entered=*/0u};
  386. NotifyBluetoothHidDetectionStatusChanged();
  387. }
  388. } // namespace ash::hid_detection