// Copyright 2015 The Chromium Authors // 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 "base/check_op.h" #include "base/debug/dump_without_crashing.h" #include "base/metrics/histogram_macros.h" #include "base/notreached.h" #include "base/ranges/algorithm.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 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& 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 = base::ranges::find(devices_, device_id, &BluetoothDeviceInfo::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 = base::ranges::find(devices_, device_id, &BluetoothDeviceInfo::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::GetWeakPtr() { return weak_factory_.GetWeakPtr(); } void BluetoothChooserController::ClearAllDevices() { devices_.clear(); device_id_to_name_map_.clear(); device_name_counts_.clear(); } } // namespace permissions