| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/system/bluetooth/bluetooth_device_list_controller_impl.h" |
| |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/system/bluetooth/bluetooth_detailed_view.h" |
| #include "ash/system/bluetooth/bluetooth_device_list_item_view.h" |
| #include "base/check.h" |
| #include "ui/gfx/paint_vector_icon.h" |
| #include "ui/views/view.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| using bluetooth_config::mojom::PairedBluetoothDevicePropertiesPtr; |
| |
| // Helper function to remove |*view| from its view hierarchy, delete the view, |
| // and reset the value of |*view| to be |nullptr|. |
| template <class T> |
| void RemoveAndResetViewIfExists(T** view) { |
| DCHECK(view); |
| |
| if (!*view) |
| return; |
| |
| views::View* parent = (*view)->parent(); |
| |
| if (parent) { |
| parent->RemoveChildViewT(*view); |
| *view = nullptr; |
| } |
| } |
| |
| } // namespace |
| |
| BluetoothDeviceListControllerImpl::BluetoothDeviceListControllerImpl( |
| BluetoothDetailedView* bluetooth_detailed_view) |
| : bluetooth_detailed_view_(bluetooth_detailed_view) {} |
| |
| BluetoothDeviceListControllerImpl::~BluetoothDeviceListControllerImpl() = |
| default; |
| |
| void BluetoothDeviceListControllerImpl::UpdateBluetoothEnabledState( |
| bool enabled) { |
| if (is_bluetooth_enabled_ && !enabled) { |
| device_id_to_view_map_.clear(); |
| connected_sub_header_ = nullptr; |
| no_device_connected_sub_header_ = nullptr; |
| previously_connected_sub_header_ = nullptr; |
| bluetooth_detailed_view_->device_list()->RemoveAllChildViews(); |
| } |
| is_bluetooth_enabled_ = enabled; |
| } |
| |
| void BluetoothDeviceListControllerImpl::UpdateDeviceList( |
| const PairedBluetoothDevicePropertiesPtrs& connected, |
| const PairedBluetoothDevicePropertiesPtrs& previously_connected) { |
| DCHECK(is_bluetooth_enabled_); |
| |
| // This function will create views for new devices, re-use views for existing |
| // devices, and remove views for devices that no longer exist. To do this, we |
| // keep track of all the preexisting views in |previous_views|, removing a |
| // view from this map when the corresponding device is found in |connected| or |
| // |previously_connected|. Before returning, any view remaining in |
| // |previous_views| is no longer needed and is deleted. |
| base::flat_map<std::string, BluetoothDeviceListItemView*> previous_views = |
| std::move(device_id_to_view_map_); |
| device_id_to_view_map_.clear(); |
| |
| // Since we re-use views when possible, we need to re-order them to match the |
| // order of the devices we are provided with. We use |index| to keep track of |
| // the next index within the device list where a view should be placed, i.e. |
| // all views before |index| are in their final position. |
| size_t index = 0; |
| |
| // The list of connected devices. |
| if (!connected.empty()) { |
| connected_sub_header_ = CreateSubHeaderIfMissingAndReorder( |
| connected_sub_header_, |
| IDS_ASH_STATUS_TRAY_BLUETOOTH_CURRENTLY_CONNECTED_DEVICES, index); |
| |
| // Increment |index| since this position was taken by |
| // |connected_sub_header_|. |
| index++; |
| |
| index = CreateViewsIfMissingAndReorder(connected, &previous_views, index); |
| } else { |
| RemoveAndResetViewIfExists(&connected_sub_header_); |
| } |
| |
| // The previously connected devices. |
| if (!previously_connected.empty()) { |
| previously_connected_sub_header_ = CreateSubHeaderIfMissingAndReorder( |
| previously_connected_sub_header_, |
| IDS_ASH_STATUS_TRAY_BLUETOOTH_PREVIOUSLY_CONNECTED_DEVICES, index); |
| |
| // Increment |index| since this position was taken by |
| // |previously_connected_sub_header_|. |
| index++; |
| |
| // Ignore the returned index since we are now done re-ordering the list. |
| CreateViewsIfMissingAndReorder(previously_connected, &previous_views, |
| index); |
| } else { |
| RemoveAndResetViewIfExists(&previously_connected_sub_header_); |
| } |
| |
| // The header when there are no connected or previously connected devices. |
| if (device_id_to_view_map_.empty()) { |
| no_device_connected_sub_header_ = CreateSubHeaderIfMissingAndReorder( |
| no_device_connected_sub_header_, |
| IDS_ASH_STATUS_TRAY_BLUETOOTH_NO_DEVICE_CONNECTED, index); |
| } else { |
| RemoveAndResetViewIfExists(&no_device_connected_sub_header_); |
| } |
| |
| for (const auto& id_and_view : previous_views) { |
| bluetooth_detailed_view_->device_list()->RemoveChildViewT( |
| id_and_view.second); |
| } |
| bluetooth_detailed_view_->NotifyDeviceListChanged(); |
| } |
| |
| views::View* |
| BluetoothDeviceListControllerImpl::CreateSubHeaderIfMissingAndReorder( |
| views::View* sub_header, |
| int text_id, |
| size_t index) { |
| if (!sub_header) { |
| sub_header = bluetooth_detailed_view_->AddDeviceListSubHeader( |
| gfx::kNoneIcon, text_id); |
| } |
| bluetooth_detailed_view_->device_list()->ReorderChildView(sub_header, index); |
| return sub_header; |
| } |
| |
| size_t BluetoothDeviceListControllerImpl::CreateViewsIfMissingAndReorder( |
| const PairedBluetoothDevicePropertiesPtrs& device_property_list, |
| base::flat_map<std::string, BluetoothDeviceListItemView*>* previous_views, |
| size_t index) { |
| DCHECK(previous_views); |
| |
| BluetoothDeviceListItemView* device_view = nullptr; |
| |
| const size_t device_count = device_property_list.size(); |
| |
| for (size_t i = 0; i < device_count; ++i) { |
| const PairedBluetoothDevicePropertiesPtr& device_properties = |
| device_property_list.at(i); |
| const std::string& device_id = device_properties->device_properties->id; |
| auto it = previous_views->find(device_id); |
| |
| if (it == previous_views->end()) { |
| device_view = bluetooth_detailed_view_->AddDeviceListItem(); |
| } else { |
| device_view = it->second; |
| previous_views->erase(it); |
| } |
| device_id_to_view_map_.emplace(device_id, device_view); |
| |
| device_view->UpdateDeviceProperties(i, device_count, device_properties); |
| bluetooth_detailed_view_->device_list()->ReorderChildView(device_view, |
| index); |
| |
| // Increment |index| since this position was taken by |device_view|. |
| index++; |
| } |
| return index; |
| } |
| |
| } // namespace ash |