| // Copyright 2015 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 "components/permissions/bluetooth_chooser_controller.h" |
| |
| #include <algorithm> |
| |
| #include "base/check_op.h" |
| #include "base/debug/dump_without_crashing.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/notreached.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| namespace permissions { |
| |
| namespace { |
| |
| void RecordInteractionWithChooser(bool has_null_handler) { |
| UMA_HISTOGRAM_BOOLEAN("Bluetooth.Web.ChooserInteraction", has_null_handler); |
| } |
| |
| } // namespace |
| |
| BluetoothChooserController::BluetoothChooserController( |
| content::RenderFrameHost* owner, |
| const content::BluetoothChooser::EventHandler& event_handler, |
| std::u16string title) |
| : ChooserController(title), event_handler_(event_handler) {} |
| |
| BluetoothChooserController::~BluetoothChooserController() { |
| if (event_handler_) { |
| event_handler_.Run(content::BluetoothChooserEvent::CANCELLED, |
| std::string()); |
| } |
| } |
| |
| bool BluetoothChooserController::ShouldShowIconBeforeText() const { |
| return true; |
| } |
| |
| bool BluetoothChooserController::ShouldShowReScanButton() const { |
| return true; |
| } |
| |
| std::u16string BluetoothChooserController::GetNoOptionsText() const { |
| return l10n_util::GetStringUTF16( |
| IDS_BLUETOOTH_DEVICE_CHOOSER_NO_DEVICES_FOUND_PROMPT); |
| } |
| |
| std::u16string BluetoothChooserController::GetOkButtonLabel() const { |
| return l10n_util::GetStringUTF16( |
| IDS_BLUETOOTH_DEVICE_CHOOSER_PAIR_BUTTON_TEXT); |
| } |
| |
| std::pair<std::u16string, std::u16string> |
| BluetoothChooserController::GetThrobberLabelAndTooltip() const { |
| return { |
| l10n_util::GetStringUTF16(IDS_BLUETOOTH_DEVICE_CHOOSER_SCANNING_LABEL), |
| l10n_util::GetStringUTF16( |
| IDS_BLUETOOTH_DEVICE_CHOOSER_SCANNING_LABEL_TOOLTIP)}; |
| } |
| |
| size_t BluetoothChooserController::NumOptions() const { |
| return devices_.size(); |
| } |
| |
| int BluetoothChooserController::GetSignalStrengthLevel(size_t index) const { |
| return devices_[index].signal_strength_level; |
| } |
| |
| bool BluetoothChooserController::IsConnected(size_t index) const { |
| return devices_[index].is_connected; |
| } |
| |
| bool BluetoothChooserController::IsPaired(size_t index) const { |
| return devices_[index].is_paired; |
| } |
| |
| std::u16string BluetoothChooserController::GetOption(size_t index) const { |
| // Change these back to DCHECKs once https://crbug.com/1292234 is resolved. |
| if (index >= devices_.size()) |
| base::debug::DumpWithoutCrashing(); |
| const std::string& device_id = devices_[index].id; |
| const auto& device_name_it = device_id_to_name_map_.find(device_id); |
| if (device_name_it == device_id_to_name_map_.end()) |
| base::debug::DumpWithoutCrashing(); |
| const auto& it = device_name_counts_.find(device_name_it->second); |
| if (it == device_name_counts_.end()) |
| base::debug::DumpWithoutCrashing(); |
| return it->second == 1 |
| ? device_name_it->second |
| : l10n_util::GetStringFUTF16( |
| IDS_DEVICE_CHOOSER_DEVICE_NAME_WITH_ID, |
| device_name_it->second, base::UTF8ToUTF16(device_id)); |
| } |
| |
| void BluetoothChooserController::RefreshOptions() { |
| RecordInteractionWithChooser(event_handler_.is_null()); |
| if (event_handler_.is_null()) |
| return; |
| ClearAllDevices(); |
| event_handler_.Run(content::BluetoothChooserEvent::RESCAN, std::string()); |
| } |
| |
| void BluetoothChooserController::Select(const std::vector<size_t>& indices) { |
| DCHECK_EQ(1u, indices.size()); |
| size_t index = indices[0]; |
| RecordInteractionWithChooser(event_handler_.is_null()); |
| if (event_handler_.is_null()) { |
| return; |
| } |
| DCHECK_LT(index, devices_.size()); |
| event_handler_.Run(content::BluetoothChooserEvent::SELECTED, |
| devices_[index].id); |
| event_handler_.Reset(); |
| } |
| |
| void BluetoothChooserController::Cancel() { |
| RecordInteractionWithChooser(event_handler_.is_null()); |
| if (event_handler_.is_null()) |
| return; |
| event_handler_.Run(content::BluetoothChooserEvent::CANCELLED, std::string()); |
| event_handler_.Reset(); |
| } |
| |
| void BluetoothChooserController::Close() { |
| RecordInteractionWithChooser(event_handler_.is_null()); |
| if (event_handler_.is_null()) |
| return; |
| event_handler_.Run(content::BluetoothChooserEvent::CANCELLED, std::string()); |
| event_handler_.Reset(); |
| } |
| |
| void BluetoothChooserController::OnAdapterPresenceChanged( |
| content::BluetoothChooser::AdapterPresence presence) { |
| ClearAllDevices(); |
| switch (presence) { |
| case content::BluetoothChooser::AdapterPresence::ABSENT: |
| NOTREACHED(); |
| break; |
| case content::BluetoothChooser::AdapterPresence::POWERED_OFF: |
| if (view()) { |
| view()->OnAdapterEnabledChanged( |
| false /* Bluetooth adapter is turned off */); |
| } |
| break; |
| case content::BluetoothChooser::AdapterPresence::POWERED_ON: |
| if (view()) { |
| view()->OnAdapterEnabledChanged( |
| true /* Bluetooth adapter is turned on */); |
| } |
| break; |
| case content::BluetoothChooser::AdapterPresence::UNAUTHORIZED: |
| if (view()) { |
| view()->OnAdapterAuthorizationChanged(/*authorized=*/false); |
| } |
| break; |
| } |
| } |
| |
| void BluetoothChooserController::OnDiscoveryStateChanged( |
| content::BluetoothChooser::DiscoveryState state) { |
| switch (state) { |
| case content::BluetoothChooser::DiscoveryState::DISCOVERING: |
| if (view()) { |
| view()->OnRefreshStateChanged( |
| true /* Refreshing options is in progress */); |
| } |
| break; |
| case content::BluetoothChooser::DiscoveryState::IDLE: |
| case content::BluetoothChooser::DiscoveryState::FAILED_TO_START: |
| if (view()) { |
| view()->OnRefreshStateChanged( |
| false /* Refreshing options is complete */); |
| } |
| break; |
| } |
| } |
| |
| void BluetoothChooserController::AddOrUpdateDevice( |
| const std::string& device_id, |
| bool should_update_name, |
| const std::u16string& device_name, |
| bool is_gatt_connected, |
| bool is_paired, |
| int signal_strength_level) { |
| auto name_it = device_id_to_name_map_.find(device_id); |
| if (name_it != device_id_to_name_map_.end()) { |
| if (should_update_name) { |
| std::u16string previous_device_name = name_it->second; |
| name_it->second = device_name; |
| |
| const auto& it = device_name_counts_.find(previous_device_name); |
| DCHECK(it != device_name_counts_.end()); |
| DCHECK_GT(it->second, 0); |
| |
| if (--(it->second) == 0) |
| device_name_counts_.erase(it); |
| |
| ++device_name_counts_[device_name]; |
| } |
| |
| auto device_it = |
| std::find_if(devices_.begin(), devices_.end(), |
| [&device_id](const BluetoothDeviceInfo& device) { |
| return device.id == device_id; |
| }); |
| |
| DCHECK(device_it != devices_.end()); |
| // When Bluetooth device scanning stops, the |signal_strength_level| |
| // is -1, and in this case, should still use the previously stored |
| // signal strength level value. |
| if (signal_strength_level != -1) |
| device_it->signal_strength_level = signal_strength_level; |
| device_it->is_connected = is_gatt_connected; |
| device_it->is_paired = is_paired; |
| if (view()) |
| view()->OnOptionUpdated(device_it - devices_.begin()); |
| return; |
| } |
| |
| devices_.push_back( |
| {device_id, signal_strength_level, is_gatt_connected, is_paired}); |
| device_id_to_name_map_.insert({device_id, device_name}); |
| ++device_name_counts_[device_name]; |
| if (view()) |
| view()->OnOptionAdded(devices_.size() - 1); |
| } |
| |
| void BluetoothChooserController::RemoveDevice(const std::string& device_id) { |
| const auto& name_it = device_id_to_name_map_.find(device_id); |
| if (name_it == device_id_to_name_map_.end()) |
| return; |
| |
| auto device_it = |
| std::find_if(devices_.begin(), devices_.end(), |
| [&device_id](const BluetoothDeviceInfo& device) { |
| return device.id == device_id; |
| }); |
| |
| if (device_it != devices_.end()) { |
| size_t index = device_it - devices_.begin(); |
| devices_.erase(device_it); |
| |
| const auto& it = device_name_counts_.find(name_it->second); |
| DCHECK(it != device_name_counts_.end()); |
| DCHECK_GT(it->second, 0); |
| |
| if (--(it->second) == 0) |
| device_name_counts_.erase(it); |
| |
| device_id_to_name_map_.erase(name_it); |
| |
| if (view()) |
| view()->OnOptionRemoved(index); |
| } |
| } |
| |
| void BluetoothChooserController::ResetEventHandler() { |
| event_handler_.Reset(); |
| } |
| |
| base::WeakPtr<BluetoothChooserController> |
| BluetoothChooserController::GetWeakPtr() { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| void BluetoothChooserController::ClearAllDevices() { |
| devices_.clear(); |
| device_id_to_name_map_.clear(); |
| device_name_counts_.clear(); |
| } |
| |
| } // namespace permissions |