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