1 // Copyright 2020 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 
5 #include "chrome/browser/ui/webui/settings/chromeos/bluetooth_section.h"
6 
7 #include "base/bind.h"
8 #include "base/metrics/histogram_functions.h"
9 #include "base/no_destructor.h"
10 #include "chrome/browser/ui/webui/chromeos/bluetooth_dialog_localized_strings_provider.h"
11 #include "chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom.h"
12 #include "chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom.h"
13 #include "chrome/browser/ui/webui/settings/chromeos/search/search.mojom.h"
14 #include "chrome/browser/ui/webui/settings/chromeos/search/search_result_icon.mojom.h"
15 #include "chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h"
16 #include "chrome/browser/ui/webui/webui_util.h"
17 #include "chrome/common/webui_url_constants.h"
18 #include "chrome/grit/generated_resources.h"
19 #include "content/public/browser/web_ui_data_source.h"
20 #include "device/bluetooth/bluetooth_adapter_factory.h"
21 #include "device/bluetooth/bluetooth_device.h"
22 #include "device/bluetooth/chromeos/bluetooth_utils.h"
23 #include "device/bluetooth/dbus/bluez_dbus_manager.h"
24 #include "device/bluetooth/strings/grit/bluetooth_strings.h"
25 #include "ui/base/l10n/l10n_util.h"
26 #include "ui/base/webui/web_ui_util.h"
27 
28 namespace chromeos {
29 namespace settings {
30 namespace {
31 
GetBluetoothSearchConcepts()32 const std::vector<SearchConcept>& GetBluetoothSearchConcepts() {
33   static const base::NoDestructor<std::vector<SearchConcept>> tags({
34       {IDS_OS_SETTINGS_TAG_BLUETOOTH,
35        mojom::kBluetoothDevicesSubpagePath,
36        mojom::SearchResultIcon::kBluetooth,
37        mojom::SearchResultDefaultRank::kMedium,
38        mojom::SearchResultType::kSubpage,
39        {.subpage = mojom::Subpage::kBluetoothDevices}},
40   });
41   return *tags;
42 }
43 
GetBluetoothOnSearchConcepts()44 const std::vector<SearchConcept>& GetBluetoothOnSearchConcepts() {
45   static const base::NoDestructor<std::vector<SearchConcept>> tags({
46       {IDS_OS_SETTINGS_TAG_BLUETOOTH_TURN_OFF,
47        mojom::kBluetoothDevicesSubpagePath,
48        mojom::SearchResultIcon::kBluetooth,
49        mojom::SearchResultDefaultRank::kMedium,
50        mojom::SearchResultType::kSetting,
51        {.setting = mojom::Setting::kBluetoothOnOff},
52        {IDS_OS_SETTINGS_TAG_BLUETOOTH_TURN_OFF_ALT1,
53         SearchConcept::kAltTagEnd}},
54   });
55   return *tags;
56 }
57 
GetBluetoothOffSearchConcepts()58 const std::vector<SearchConcept>& GetBluetoothOffSearchConcepts() {
59   static const base::NoDestructor<std::vector<SearchConcept>> tags({
60       {IDS_OS_SETTINGS_TAG_BLUETOOTH_TURN_ON,
61        mojom::kBluetoothDevicesSubpagePath,
62        mojom::SearchResultIcon::kBluetooth,
63        mojom::SearchResultDefaultRank::kMedium,
64        mojom::SearchResultType::kSetting,
65        {.setting = mojom::Setting::kBluetoothOnOff},
66        {IDS_OS_SETTINGS_TAG_BLUETOOTH_TURN_ON_ALT1, SearchConcept::kAltTagEnd}},
67   });
68   return *tags;
69 }
70 
GetBluetoothConnectableSearchConcepts()71 const std::vector<SearchConcept>& GetBluetoothConnectableSearchConcepts() {
72   static const base::NoDestructor<std::vector<SearchConcept>> tags({
73       {IDS_OS_SETTINGS_TAG_BLUETOOTH_CONNECT,
74        mojom::kBluetoothDevicesSubpagePath,
75        mojom::SearchResultIcon::kBluetooth,
76        mojom::SearchResultDefaultRank::kMedium,
77        mojom::SearchResultType::kSetting,
78        {.setting = mojom::Setting::kBluetoothConnectToDevice}},
79   });
80   return *tags;
81 }
82 
GetBluetoothConnectedSearchConcepts()83 const std::vector<SearchConcept>& GetBluetoothConnectedSearchConcepts() {
84   static const base::NoDestructor<std::vector<SearchConcept>> tags({
85       {IDS_OS_SETTINGS_TAG_BLUETOOTH_DISCONNECT,
86        mojom::kBluetoothDevicesSubpagePath,
87        mojom::SearchResultIcon::kBluetooth,
88        mojom::SearchResultDefaultRank::kMedium,
89        mojom::SearchResultType::kSetting,
90        {.setting = mojom::Setting::kBluetoothDisconnectFromDevice}},
91   });
92   return *tags;
93 }
94 
GetBluetoothPairableSearchConcepts()95 const std::vector<SearchConcept>& GetBluetoothPairableSearchConcepts() {
96   static const base::NoDestructor<std::vector<SearchConcept>> tags({
97       {IDS_OS_SETTINGS_TAG_BLUETOOTH_PAIR,
98        mojom::kBluetoothDevicesSubpagePath,
99        mojom::SearchResultIcon::kBluetooth,
100        mojom::SearchResultDefaultRank::kMedium,
101        mojom::SearchResultType::kSetting,
102        {.setting = mojom::Setting::kBluetoothPairDevice}},
103   });
104   return *tags;
105 }
106 
GetBluetoothPairedSearchConcepts()107 const std::vector<SearchConcept>& GetBluetoothPairedSearchConcepts() {
108   static const base::NoDestructor<std::vector<SearchConcept>> tags({
109       {IDS_OS_SETTINGS_TAG_BLUETOOTH_UNPAIR,
110        mojom::kBluetoothDevicesSubpagePath,
111        mojom::SearchResultIcon::kBluetooth,
112        mojom::SearchResultDefaultRank::kMedium,
113        mojom::SearchResultType::kSetting,
114        {.setting = mojom::Setting::kBluetoothUnpairDevice},
115        {IDS_OS_SETTINGS_TAG_BLUETOOTH_UNPAIR_ALT1, SearchConcept::kAltTagEnd}},
116   });
117   return *tags;
118 }
119 
120 }  // namespace
121 
BluetoothSection(Profile * profile,SearchTagRegistry * search_tag_registry)122 BluetoothSection::BluetoothSection(Profile* profile,
123                                    SearchTagRegistry* search_tag_registry)
124     : OsSettingsSection(profile, search_tag_registry) {
125   // Note: May be uninitialized in tests.
126   if (bluez::BluezDBusManager::IsInitialized()) {
127     device::BluetoothAdapterFactory::Get()->GetAdapter(
128         base::Bind(&BluetoothSection::OnFetchBluetoothAdapter,
129                    weak_ptr_factory_.GetWeakPtr()));
130   }
131 }
132 
~BluetoothSection()133 BluetoothSection::~BluetoothSection() {
134   if (bluetooth_adapter_)
135     bluetooth_adapter_->RemoveObserver(this);
136 }
137 
AddLoadTimeData(content::WebUIDataSource * html_source)138 void BluetoothSection::AddLoadTimeData(content::WebUIDataSource* html_source) {
139   static constexpr webui::LocalizedString kLocalizedStrings[] = {
140       {"bluetoothConnected", IDS_SETTINGS_BLUETOOTH_CONNECTED},
141       {"bluetoothConnectedWithBattery",
142        IDS_SETTINGS_BLUETOOTH_CONNECTED_WITH_BATTERY},
143       {"bluetoothConnecting", IDS_SETTINGS_BLUETOOTH_CONNECTING},
144       {"bluetoothDeviceListPaired", IDS_SETTINGS_BLUETOOTH_DEVICE_LIST_PAIRED},
145       {"bluetoothDeviceListUnpaired",
146        IDS_SETTINGS_BLUETOOTH_DEVICE_LIST_UNPAIRED},
147       {"bluetoothConnect", IDS_SETTINGS_BLUETOOTH_CONNECT},
148       {"bluetoothDisconnect", IDS_SETTINGS_BLUETOOTH_DISCONNECT},
149       {"bluetoothToggleA11yLabel",
150        IDS_SETTINGS_BLUETOOTH_TOGGLE_ACCESSIBILITY_LABEL},
151       {"bluetoothExpandA11yLabel",
152        IDS_SETTINGS_BLUETOOTH_EXPAND_ACCESSIBILITY_LABEL},
153       {"bluetoothNoDevices", IDS_SETTINGS_BLUETOOTH_NO_DEVICES},
154       {"bluetoothNoDevicesFound", IDS_SETTINGS_BLUETOOTH_NO_DEVICES_FOUND},
155       {"bluetoothNotConnected", IDS_SETTINGS_BLUETOOTH_NOT_CONNECTED},
156       {"bluetoothPageTitle", IDS_SETTINGS_BLUETOOTH},
157       {"bluetoothPairDevicePageTitle",
158        IDS_SETTINGS_BLUETOOTH_PAIR_DEVICE_TITLE},
159       {"bluetoothRemove", IDS_SETTINGS_BLUETOOTH_REMOVE},
160       {"bluetoothPrimaryUserControlled",
161        IDS_SETTINGS_BLUETOOTH_PRIMARY_USER_CONTROLLED},
162       {"bluetoothDeviceWithConnectionStatus",
163        IDS_BLUETOOTH_ACCESSIBILITY_DEVICE_TYPE_AND_CONNECTION_STATUS},
164       {"bluetoothDeviceType_computer",
165        IDS_BLUETOOTH_ACCESSIBILITY_DEVICE_TYPE_COMPUTER},
166       {"bluetoothDeviceType_phone",
167        IDS_BLUETOOTH_ACCESSIBILITY_DEVICE_TYPE_PHONE},
168       {"bluetoothDeviceType_modem",
169        IDS_BLUETOOTH_ACCESSIBILITY_DEVICE_TYPE_MODEM},
170       {"bluetoothDeviceType_audio",
171        IDS_BLUETOOTH_ACCESSIBILITY_DEVICE_TYPE_AUDIO},
172       {"bluetoothDeviceType_carAudio",
173        IDS_BLUETOOTH_ACCESSIBILITY_DEVICE_TYPE_CAR_AUDIO},
174       {"bluetoothDeviceType_video",
175        IDS_BLUETOOTH_ACCESSIBILITY_DEVICE_TYPE_VIDEO},
176       {"bluetoothDeviceType_peripheral",
177        IDS_BLUETOOTH_ACCESSIBILITY_DEVICE_TYPE_PERIPHERAL},
178       {"bluetoothDeviceType_joystick",
179        IDS_BLUETOOTH_ACCESSIBILITY_DEVICE_TYPE_JOYSTICK},
180       {"bluetoothDeviceType_gamepad",
181        IDS_BLUETOOTH_ACCESSIBILITY_DEVICE_TYPE_GAMEPAD},
182       {"bluetoothDeviceType_keyboard",
183        IDS_BLUETOOTH_ACCESSIBILITY_DEVICE_TYPE_KEYBOARD},
184       {"bluetoothDeviceType_mouse",
185        IDS_BLUETOOTH_ACCESSIBILITY_DEVICE_TYPE_MOUSE},
186       {"bluetoothDeviceType_tablet",
187        IDS_BLUETOOTH_ACCESSIBILITY_DEVICE_TYPE_TABLET},
188       {"bluetoothDeviceType_keyboardMouseCombo",
189        IDS_BLUETOOTH_ACCESSIBILITY_DEVICE_TYPE_KEYBOARD_MOUSE_COMBO},
190       {"bluetoothDeviceType_unknown",
191        IDS_BLUETOOTH_ACCESSIBILITY_DEVICE_TYPE_UNKNOWN},
192   };
193   AddLocalizedStringsBulk(html_source, kLocalizedStrings);
194   chromeos::bluetooth_dialog::AddLocalizedStrings(html_source);
195 }
196 
GetSectionNameMessageId() const197 int BluetoothSection::GetSectionNameMessageId() const {
198   return IDS_SETTINGS_BLUETOOTH;
199 }
200 
GetSection() const201 mojom::Section BluetoothSection::GetSection() const {
202   return mojom::Section::kBluetooth;
203 }
204 
GetSectionIcon() const205 mojom::SearchResultIcon BluetoothSection::GetSectionIcon() const {
206   return mojom::SearchResultIcon::kBluetooth;
207 }
208 
GetSectionPath() const209 std::string BluetoothSection::GetSectionPath() const {
210   return mojom::kBluetoothSectionPath;
211 }
212 
LogMetric(mojom::Setting setting,base::Value & value) const213 bool BluetoothSection::LogMetric(mojom::Setting setting,
214                                  base::Value& value) const {
215   switch (setting) {
216     case mojom::Setting::kBluetoothOnOff:
217       base::UmaHistogramBoolean("ChromeOS.Settings.Bluetooth.BluetoothOnOff",
218                                 value.GetBool());
219       return true;
220 
221     default:
222       return false;
223   }
224 }
225 
RegisterHierarchy(HierarchyGenerator * generator) const226 void BluetoothSection::RegisterHierarchy(HierarchyGenerator* generator) const {
227   generator->RegisterTopLevelSubpage(IDS_SETTINGS_BLUETOOTH,
228                                      mojom::Subpage::kBluetoothDevices,
229                                      mojom::SearchResultIcon::kBluetooth,
230                                      mojom::SearchResultDefaultRank::kMedium,
231                                      mojom::kBluetoothDevicesSubpagePath);
232   static constexpr mojom::Setting kBluetoothDevicesSettings[] = {
233       mojom::Setting::kBluetoothOnOff,
234       mojom::Setting::kBluetoothConnectToDevice,
235       mojom::Setting::kBluetoothDisconnectFromDevice,
236       mojom::Setting::kBluetoothPairDevice,
237       mojom::Setting::kBluetoothUnpairDevice,
238   };
239   RegisterNestedSettingBulk(mojom::Subpage::kBluetoothDevices,
240                             kBluetoothDevicesSettings, generator);
241   generator->RegisterTopLevelAltSetting(mojom::Setting::kBluetoothOnOff);
242 }
243 
AdapterPresentChanged(device::BluetoothAdapter * adapter,bool present)244 void BluetoothSection::AdapterPresentChanged(device::BluetoothAdapter* adapter,
245                                              bool present) {
246   UpdateSearchTags();
247 }
248 
AdapterPoweredChanged(device::BluetoothAdapter * adapter,bool powered)249 void BluetoothSection::AdapterPoweredChanged(device::BluetoothAdapter* adapter,
250                                              bool powered) {
251   UpdateSearchTags();
252 }
253 
DeviceAdded(device::BluetoothAdapter * adapter,device::BluetoothDevice * device)254 void BluetoothSection::DeviceAdded(device::BluetoothAdapter* adapter,
255                                    device::BluetoothDevice* device) {
256   UpdateSearchTags();
257 }
258 
DeviceChanged(device::BluetoothAdapter * adapter,device::BluetoothDevice * device)259 void BluetoothSection::DeviceChanged(device::BluetoothAdapter* adapter,
260                                      device::BluetoothDevice* device) {
261   UpdateSearchTags();
262 }
263 
DeviceRemoved(device::BluetoothAdapter * adapter,device::BluetoothDevice * device)264 void BluetoothSection::DeviceRemoved(device::BluetoothAdapter* adapter,
265                                      device::BluetoothDevice* device) {
266   UpdateSearchTags();
267 }
268 
OnFetchBluetoothAdapter(scoped_refptr<device::BluetoothAdapter> bluetooth_adapter)269 void BluetoothSection::OnFetchBluetoothAdapter(
270     scoped_refptr<device::BluetoothAdapter> bluetooth_adapter) {
271   bluetooth_adapter_ = bluetooth_adapter;
272   bluetooth_adapter_->AddObserver(this);
273   UpdateSearchTags();
274 }
275 
UpdateSearchTags()276 void BluetoothSection::UpdateSearchTags() {
277   SearchTagRegistry::ScopedTagUpdater updater = registry()->StartUpdate();
278 
279   // Start with no search tags, then add them below if appropriate.
280   updater.RemoveSearchTags(GetBluetoothSearchConcepts());
281   updater.RemoveSearchTags(GetBluetoothOnSearchConcepts());
282   updater.RemoveSearchTags(GetBluetoothOffSearchConcepts());
283   updater.RemoveSearchTags(GetBluetoothConnectableSearchConcepts());
284   updater.RemoveSearchTags(GetBluetoothConnectedSearchConcepts());
285   updater.RemoveSearchTags(GetBluetoothPairableSearchConcepts());
286   updater.RemoveSearchTags(GetBluetoothPairedSearchConcepts());
287 
288   if (!bluetooth_adapter_->IsPresent())
289     return;
290 
291   updater.AddSearchTags(GetBluetoothSearchConcepts());
292 
293   if (!bluetooth_adapter_->IsPowered()) {
294     updater.AddSearchTags(GetBluetoothOffSearchConcepts());
295     return;
296   }
297 
298   updater.AddSearchTags(GetBluetoothOnSearchConcepts());
299 
300   // Filter devices so that only those shown in the UI are returned. Note that
301   // passing |max_devices| of 0 indicates that there is no maximum.
302   device::BluetoothAdapter::DeviceList devices =
303       device::FilterBluetoothDeviceList(bluetooth_adapter_->GetDevices(),
304                                         device::BluetoothFilterType::KNOWN,
305                                         /*max_devices=*/0);
306 
307   bool connectable_device_exists = false;
308   bool connected_device_exists = false;
309   bool pairable_device_exists = false;
310   bool paired_device_exists = false;
311   for (const device::BluetoothDevice* device : devices) {
312     // Note: Device must be paired to be connectable.
313     if (device->IsPaired() && device->IsConnectable() && !device->IsConnected())
314       connectable_device_exists = true;
315     if (device->IsConnected())
316       connected_device_exists = true;
317     if (device->IsPairable() && !device->IsPaired())
318       pairable_device_exists = true;
319     if (device->IsPaired())
320       paired_device_exists = true;
321   }
322 
323   if (connectable_device_exists)
324     updater.AddSearchTags(GetBluetoothConnectableSearchConcepts());
325   if (connected_device_exists)
326     updater.AddSearchTags(GetBluetoothConnectedSearchConcepts());
327   if (pairable_device_exists)
328     updater.AddSearchTags(GetBluetoothPairableSearchConcepts());
329   if (paired_device_exists)
330     updater.AddSearchTags(GetBluetoothPairedSearchConcepts());
331 }
332 
333 }  // namespace settings
334 }  // namespace chromeos
335