bluetooth_notification_controller.cc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. // Copyright 2014 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/system/bluetooth/bluetooth_notification_controller.h"
  5. #include <memory>
  6. #include <utility>
  7. #include "ash/constants/notifier_catalogs.h"
  8. #include "ash/public/cpp/nearby_share_delegate.h"
  9. #include "ash/public/cpp/notification_utils.h"
  10. #include "ash/public/cpp/system/toast_data.h"
  11. #include "ash/public/cpp/system_tray_client.h"
  12. #include "ash/resources/vector_icons/vector_icons.h"
  13. #include "ash/services/nearby/public/cpp/nearby_client_uuids.h"
  14. #include "ash/session/session_controller_impl.h"
  15. #include "ash/shell.h"
  16. #include "ash/strings/grit/ash_strings.h"
  17. #include "ash/system/model/system_tray_model.h"
  18. #include "ash/system/toast/toast_manager_impl.h"
  19. #include "ash/system/tray/tray_popup_utils.h"
  20. #include "base/bind.h"
  21. #include "base/callback.h"
  22. #include "base/logging.h"
  23. #include "base/strings/string_util.h"
  24. #include "base/strings/stringprintf.h"
  25. #include "base/strings/utf_string_conversions.h"
  26. #include "device/bluetooth/bluetooth_adapter_factory.h"
  27. #include "device/bluetooth/bluetooth_device.h"
  28. #include "device/bluetooth/chromeos/bluetooth_utils.h"
  29. #include "ui/base/l10n/l10n_util.h"
  30. #include "ui/message_center/message_center.h"
  31. #include "ui/message_center/public/cpp/notification.h"
  32. #include "ui/message_center/public/cpp/notification_delegate.h"
  33. using device::BluetoothAdapter;
  34. using device::BluetoothAdapterFactory;
  35. using device::BluetoothDevice;
  36. using message_center::MessageCenter;
  37. using message_center::Notification;
  38. namespace ash {
  39. namespace {
  40. const char kNotifierBluetooth[] = "ash.bluetooth";
  41. const char kPairedNotificationPrefix[] =
  42. "cros_bluetooth_device_paired_notification_id-";
  43. // The BluetoothPairingNotificationDelegate handles user interaction with the
  44. // pairing notification and sending the confirmation, rejection or cancellation
  45. // back to the underlying device.
  46. class BluetoothPairingNotificationDelegate
  47. : public message_center::NotificationDelegate {
  48. public:
  49. BluetoothPairingNotificationDelegate(scoped_refptr<BluetoothAdapter> adapter,
  50. const std::string& address,
  51. const std::string& notification_id);
  52. BluetoothPairingNotificationDelegate(
  53. const BluetoothPairingNotificationDelegate&) = delete;
  54. BluetoothPairingNotificationDelegate& operator=(
  55. const BluetoothPairingNotificationDelegate&) = delete;
  56. protected:
  57. ~BluetoothPairingNotificationDelegate() override;
  58. // message_center::NotificationDelegate overrides.
  59. void Close(bool by_user) override;
  60. void Click(const absl::optional<int>& button_index,
  61. const absl::optional<std::u16string>& reply) override;
  62. private:
  63. // Buttons that appear in notifications.
  64. enum Button { BUTTON_ACCEPT, BUTTON_REJECT };
  65. // Reference to the underlying Bluetooth Adapter, holding onto this
  66. // reference ensures the adapter object doesn't go out of scope while we have
  67. // a pending request and user interaction.
  68. scoped_refptr<BluetoothAdapter> adapter_;
  69. // Address of the device being paired.
  70. const std::string address_;
  71. const std::string notification_id_;
  72. };
  73. BluetoothPairingNotificationDelegate::BluetoothPairingNotificationDelegate(
  74. scoped_refptr<BluetoothAdapter> adapter,
  75. const std::string& address,
  76. const std::string& notification_id)
  77. : adapter_(adapter), address_(address), notification_id_(notification_id) {}
  78. BluetoothPairingNotificationDelegate::~BluetoothPairingNotificationDelegate() =
  79. default;
  80. void BluetoothPairingNotificationDelegate::Close(bool by_user) {
  81. VLOG(1) << "Pairing notification closed. by_user = " << by_user;
  82. // Ignore notification closes generated as a result of pairing completion.
  83. if (!by_user)
  84. return;
  85. // Cancel the pairing of the device, if the object still exists.
  86. BluetoothDevice* device = adapter_->GetDevice(address_);
  87. if (device)
  88. device->CancelPairing();
  89. }
  90. void BluetoothPairingNotificationDelegate::Click(
  91. const absl::optional<int>& button_index,
  92. const absl::optional<std::u16string>& reply) {
  93. if (!button_index)
  94. return;
  95. VLOG(1) << "Pairing notification, button click: " << *button_index;
  96. // If the device object still exists, send the appropriate response either
  97. // confirming or rejecting the pairing.
  98. BluetoothDevice* device = adapter_->GetDevice(address_);
  99. if (device) {
  100. switch (*button_index) {
  101. case BUTTON_ACCEPT:
  102. device->ConfirmPairing();
  103. break;
  104. case BUTTON_REJECT:
  105. device->RejectPairing();
  106. break;
  107. }
  108. }
  109. // In any case, remove this pairing notification.
  110. MessageCenter::Get()->RemoveNotification(notification_id_,
  111. false /* by_user */);
  112. }
  113. void ShowToast(const std::string& id,
  114. ToastCatalogName catalog_name,
  115. const std::u16string& text) {
  116. ash::ToastManager::Get()->Show(ash::ToastData(id, catalog_name, text));
  117. }
  118. } // namespace
  119. const char
  120. BluetoothNotificationController::kBluetoothDeviceDiscoverableToastId[] =
  121. "cros_bluetooth_device_discoverable_toast_id";
  122. const char
  123. BluetoothNotificationController::kBluetoothDevicePairingNotificationId[] =
  124. "cros_bluetooth_device_pairing_notification_id";
  125. // This class handles opening the Bluetooth Settings UI when the user clicks
  126. // on the Paired Notification.
  127. class BluetoothNotificationController::BluetoothPairedNotificationDelegate
  128. : public message_center::NotificationDelegate {
  129. public:
  130. BluetoothPairedNotificationDelegate() = default;
  131. BluetoothPairedNotificationDelegate(
  132. const BluetoothPairedNotificationDelegate&) = delete;
  133. BluetoothPairedNotificationDelegate& operator=(
  134. const BluetoothPairedNotificationDelegate&) = delete;
  135. protected:
  136. ~BluetoothPairedNotificationDelegate() override = default;
  137. // message_center::NotificationDelegate:
  138. void Click(const absl::optional<int>& button_index,
  139. const absl::optional<std::u16string>& reply) override {
  140. if (TrayPopupUtils::CanOpenWebUISettings())
  141. Shell::Get()->system_tray_model()->client()->ShowBluetoothSettings();
  142. }
  143. };
  144. BluetoothNotificationController::BluetoothNotificationController(
  145. message_center::MessageCenter* message_center)
  146. : message_center_(message_center) {
  147. BluetoothAdapterFactory::Get()->GetAdapter(
  148. base::BindOnce(&BluetoothNotificationController::OnGetAdapter,
  149. weak_ptr_factory_.GetWeakPtr()));
  150. }
  151. BluetoothNotificationController::~BluetoothNotificationController() {
  152. if (adapter_.get()) {
  153. adapter_->RemoveObserver(this);
  154. adapter_->RemovePairingDelegate(this);
  155. adapter_.reset();
  156. }
  157. }
  158. void BluetoothNotificationController::AdapterDiscoverableChanged(
  159. BluetoothAdapter* adapter,
  160. bool discoverable) {
  161. if (discoverable)
  162. NotifyAdapterDiscoverable();
  163. }
  164. void BluetoothNotificationController::DeviceAdded(BluetoothAdapter* adapter,
  165. BluetoothDevice* device) {
  166. // Add the new device to the list of currently paired devices; it doesn't
  167. // receive a notification since it's assumed it was previously notified.
  168. if (device->IsPaired())
  169. paired_devices_.insert(device->GetAddress());
  170. }
  171. void BluetoothNotificationController::DeviceChanged(BluetoothAdapter* adapter,
  172. BluetoothDevice* device) {
  173. // If the device is already in the list of paired devices, then don't
  174. // notify.
  175. if (paired_devices_.find(device->GetAddress()) != paired_devices_.end())
  176. return;
  177. // Otherwise if it's marked as paired then it must be newly paired, so
  178. // notify the user about that.
  179. if (device->IsPaired()) {
  180. paired_devices_.insert(device->GetAddress());
  181. NotifyPairedDevice(device);
  182. }
  183. }
  184. void BluetoothNotificationController::DeviceRemoved(BluetoothAdapter* adapter,
  185. BluetoothDevice* device) {
  186. paired_devices_.erase(device->GetAddress());
  187. }
  188. void BluetoothNotificationController::RequestPinCode(BluetoothDevice* device) {
  189. // Cannot provide keyboard entry in a notification; these devices (old car
  190. // audio systems for the most part) will need pairing to be initiated from
  191. // the Chromebook.
  192. device->CancelPairing();
  193. }
  194. void BluetoothNotificationController::RequestPasskey(BluetoothDevice* device) {
  195. // Cannot provide keyboard entry in a notification; fortunately the spec
  196. // doesn't allow for this to be an option when we're receiving the pairing
  197. // request anyway.
  198. device->CancelPairing();
  199. }
  200. void BluetoothNotificationController::DisplayPinCode(
  201. BluetoothDevice* device,
  202. const std::string& pincode) {
  203. std::u16string message = l10n_util::GetStringFUTF16(
  204. IDS_ASH_STATUS_TRAY_BLUETOOTH_DISPLAY_PINCODE,
  205. device->GetNameForDisplay(), base::UTF8ToUTF16(pincode));
  206. NotifyPairing(device, message, false);
  207. }
  208. void BluetoothNotificationController::DisplayPasskey(BluetoothDevice* device,
  209. uint32_t passkey) {
  210. std::u16string message = l10n_util::GetStringFUTF16(
  211. IDS_ASH_STATUS_TRAY_BLUETOOTH_DISPLAY_PASSKEY,
  212. device->GetNameForDisplay(),
  213. base::UTF8ToUTF16(base::StringPrintf("%06i", passkey)));
  214. NotifyPairing(device, message, false);
  215. }
  216. void BluetoothNotificationController::KeysEntered(BluetoothDevice* device,
  217. uint32_t entered) {
  218. // Ignored since we don't have CSS in the notification to update.
  219. }
  220. void BluetoothNotificationController::ConfirmPasskey(BluetoothDevice* device,
  221. uint32_t passkey) {
  222. std::u16string message = l10n_util::GetStringFUTF16(
  223. IDS_ASH_STATUS_TRAY_BLUETOOTH_CONFIRM_PASSKEY,
  224. device->GetNameForDisplay(),
  225. base::UTF8ToUTF16(base::StringPrintf("%06i", passkey)));
  226. NotifyPairing(device, message, true);
  227. }
  228. void BluetoothNotificationController::AuthorizePairing(
  229. BluetoothDevice* device) {
  230. std::u16string message = l10n_util::GetStringFUTF16(
  231. IDS_ASH_STATUS_TRAY_BLUETOOTH_AUTHORIZE_PAIRING,
  232. device->GetNameForDisplay());
  233. NotifyPairing(device, message, true);
  234. }
  235. // static
  236. std::string BluetoothNotificationController::GetPairedNotificationId(
  237. const BluetoothDevice* device) {
  238. return kPairedNotificationPrefix + base::ToLowerASCII(device->GetAddress());
  239. }
  240. void BluetoothNotificationController::OnGetAdapter(
  241. scoped_refptr<BluetoothAdapter> adapter) {
  242. DCHECK(!adapter_.get());
  243. adapter_ = adapter;
  244. adapter_->AddObserver(this);
  245. adapter_->AddPairingDelegate(this,
  246. BluetoothAdapter::PAIRING_DELEGATE_PRIORITY_LOW);
  247. // Notify a user if the adapter is already in the discoverable state.
  248. if (adapter_->IsDiscoverable())
  249. NotifyAdapterDiscoverable();
  250. // Build a list of the currently paired devices; these don't receive
  251. // notifications since it's assumed they were previously notified.
  252. BluetoothAdapter::DeviceList devices = adapter_->GetDevices();
  253. for (BluetoothAdapter::DeviceList::const_iterator iter = devices.begin();
  254. iter != devices.end(); ++iter) {
  255. const BluetoothDevice* device = *iter;
  256. if (device->IsPaired())
  257. paired_devices_.insert(device->GetAddress());
  258. }
  259. }
  260. void BluetoothNotificationController::NotifyAdapterDiscoverable() {
  261. // Do not show toast in kiosk app mode.
  262. if (Shell::Get()->session_controller()->IsRunningInAppMode())
  263. return;
  264. // If Nearby Share has made the local device discoverable, do not
  265. // unnecessarily display this toast.
  266. // TODO(crbug.com/1155669): Generalize this logic to prevent leaking Nearby
  267. // implementation details.
  268. auto* nearby_share_delegate = Shell::Get()->nearby_share_delegate();
  269. if (nearby_share_delegate &&
  270. (nearby_share_delegate->IsEnableHighVisibilityRequestActive() ||
  271. nearby_share_delegate->IsHighVisibilityOn())) {
  272. return;
  273. }
  274. ShowToast(
  275. kBluetoothDeviceDiscoverableToastId,
  276. ToastCatalogName::kBluetoothAdapterDiscoverable,
  277. l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_BLUETOOTH_DISCOVERABLE,
  278. base::UTF8ToUTF16(adapter_->GetName())));
  279. }
  280. void BluetoothNotificationController::NotifyPairing(
  281. BluetoothDevice* device,
  282. const std::u16string& message,
  283. bool with_buttons) {
  284. message_center::RichNotificationData optional;
  285. if (with_buttons) {
  286. optional.buttons.push_back(message_center::ButtonInfo(
  287. l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_BLUETOOTH_ACCEPT)));
  288. optional.buttons.push_back(message_center::ButtonInfo(
  289. l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_BLUETOOTH_REJECT)));
  290. }
  291. std::unique_ptr<Notification> notification = CreateSystemNotification(
  292. message_center::NOTIFICATION_TYPE_SIMPLE,
  293. kBluetoothDevicePairingNotificationId, std::u16string() /* title */,
  294. message, std::u16string() /* display source */, GURL(),
  295. message_center::NotifierId(
  296. message_center::NotifierType::SYSTEM_COMPONENT, kNotifierBluetooth,
  297. NotificationCatalogName::kBluetoothPairingRequest),
  298. optional,
  299. base::MakeRefCounted<BluetoothPairingNotificationDelegate>(
  300. adapter_, device->GetAddress(),
  301. kBluetoothDevicePairingNotificationId),
  302. kNotificationBluetoothIcon,
  303. message_center::SystemNotificationWarningLevel::NORMAL);
  304. message_center_->AddNotification(std::move(notification));
  305. }
  306. void BluetoothNotificationController::NotifyPairedDevice(
  307. BluetoothDevice* device) {
  308. // Remove the currently presented pairing notification; since only one
  309. // pairing request is queued at a time, this is guaranteed to be the device
  310. // that just became paired.
  311. if (message_center_->FindVisibleNotificationById(
  312. kBluetoothDevicePairingNotificationId)) {
  313. message_center_->RemoveNotification(kBluetoothDevicePairingNotificationId,
  314. false /* by_user */);
  315. }
  316. // If the newly paired device is connected via a Nearby Connections client
  317. // (e.g., Nearby Share), do not display this notification.
  318. // TODO(crbug.com/1155669): Generalize this logic to prevent leaking Nearby
  319. // implementation details.
  320. for (const auto& uuid : device->GetUUIDs()) {
  321. if (nearby::IsNearbyClientUuid(uuid)) {
  322. return;
  323. }
  324. }
  325. // If bluetooth revamp flag is on, do not show a notification, this is
  326. // because notifications will be handled by BluetoothDeviceStatusUiHandler
  327. // when flag is on.
  328. if (ash::features::IsBluetoothRevampEnabled()) {
  329. return;
  330. }
  331. std::unique_ptr<Notification> notification = CreateSystemNotification(
  332. message_center::NOTIFICATION_TYPE_SIMPLE, GetPairedNotificationId(device),
  333. std::u16string() /* title */,
  334. l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_BLUETOOTH_PAIRED,
  335. device->GetNameForDisplay()),
  336. std::u16string() /* display source */, GURL(),
  337. message_center::NotifierId(
  338. message_center::NotifierType::SYSTEM_COMPONENT, kNotifierBluetooth,
  339. NotificationCatalogName::kBluetoothPairedDevice),
  340. message_center::RichNotificationData(),
  341. base::MakeRefCounted<BluetoothPairedNotificationDelegate>(),
  342. kNotificationBluetoothIcon,
  343. message_center::SystemNotificationWarningLevel::NORMAL);
  344. message_center_->AddNotification(std::move(notification));
  345. device::RecordUiSurfaceDisplayed(
  346. device::BluetoothUiSurface::kPairedNotification);
  347. }
  348. } // namespace ash