| // Copyright 2017 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/arc/usb/usb_host_bridge.h" |
| |
| #include <unordered_set> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #include "base/memory/singleton.h" |
| #include "base/strings/stringprintf.h" |
| #include "chromeos/dbus/permission_broker/permission_broker_client.h" |
| #include "components/arc/arc_browser_context_keyed_service_factory_base.h" |
| #include "components/arc/arc_features.h" |
| #include "components/arc/session/arc_bridge_service.h" |
| #include "components/arc/usb/usb_host_ui_delegate.h" |
| #include "content/public/browser/system_connector.h" |
| #include "mojo/public/cpp/system/platform_handle.h" |
| #include "services/device/public/mojom/constants.mojom.h" |
| #include "services/service_manager/public/cpp/connector.h" |
| |
| namespace arc { |
| namespace { |
| |
| // Singleton factory for ArcUsbHostBridge |
| class ArcUsbHostBridgeFactory |
| : public internal::ArcBrowserContextKeyedServiceFactoryBase< |
| ArcUsbHostBridge, |
| ArcUsbHostBridgeFactory> { |
| public: |
| // Factory name used by ArcBrowserContextKeyedServiceFactoryBase. |
| static constexpr const char* kName = "ArcUsbHostBridgeFactory"; |
| |
| static ArcUsbHostBridgeFactory* GetInstance() { |
| return base::Singleton<ArcUsbHostBridgeFactory>::get(); |
| } |
| |
| private: |
| friend base::DefaultSingletonTraits<ArcUsbHostBridgeFactory>; |
| ArcUsbHostBridgeFactory() = default; |
| ~ArcUsbHostBridgeFactory() override = default; |
| }; |
| |
| void OnDeviceOpened(mojom::UsbHostHost::OpenDeviceCallback callback, |
| base::ScopedFD fd) { |
| if (!fd.is_valid()) { |
| LOG(ERROR) << "Invalid USB device FD"; |
| std::move(callback).Run(mojo::ScopedHandle()); |
| return; |
| } |
| mojo::ScopedHandle wrapped_handle = |
| mojo::WrapPlatformHandle(mojo::PlatformHandle(std::move(fd))); |
| if (!wrapped_handle.is_valid()) { |
| LOG(ERROR) << "Failed to wrap device FD. Closing."; |
| std::move(callback).Run(mojo::ScopedHandle()); |
| return; |
| } |
| std::move(callback).Run(std::move(wrapped_handle)); |
| } |
| |
| void OnDeviceOpenError(mojom::UsbHostHost::OpenDeviceCallback callback, |
| const std::string& error_name, |
| const std::string& error_message) { |
| LOG(WARNING) << "Cannot open USB device: " << error_name << ": " |
| << error_message; |
| std::move(callback).Run(mojo::ScopedHandle()); |
| } |
| |
| std::string GetDevicePath(const device::mojom::UsbDeviceInfo& device_info) { |
| return base::StringPrintf("/dev/bus/usb/%03d/%03d", device_info.bus_number, |
| device_info.port_number); |
| } |
| |
| } // namespace |
| |
| ArcUsbHostBridge* ArcUsbHostBridge::GetForBrowserContext( |
| content::BrowserContext* context) { |
| return ArcUsbHostBridgeFactory::GetForBrowserContext(context); |
| } |
| |
| ArcUsbHostBridge::ArcUsbHostBridge(content::BrowserContext* context, |
| ArcBridgeService* bridge_service) |
| : arc_bridge_service_(bridge_service) { |
| arc_bridge_service_->usb_host()->SetHost(this); |
| arc_bridge_service_->usb_host()->AddObserver(this); |
| } |
| |
| ArcUsbHostBridge::~ArcUsbHostBridge() { |
| arc_bridge_service_->usb_host()->RemoveObserver(this); |
| arc_bridge_service_->usb_host()->SetHost(nullptr); |
| } |
| |
| BrowserContextKeyedServiceFactory* ArcUsbHostBridge::GetFactory() { |
| return ArcUsbHostBridgeFactory::GetInstance(); |
| } |
| |
| void ArcUsbHostBridge::RequestPermission(const std::string& guid, |
| const std::string& package, |
| bool interactive, |
| RequestPermissionCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_); |
| |
| if (guid.empty()) { |
| HandleScanDeviceListRequest(package, std::move(callback)); |
| return; |
| } |
| |
| VLOG(2) << "USB RequestPermission " << guid << " package " << package; |
| |
| // GUIDs are unguessable so device list should be initialized when this |
| // method is being called with a valid GUID. |
| auto iter = devices_.find(guid); |
| if (iter == devices_.end()) { |
| LOG(WARNING) << "Unknown USB device " << guid; |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| // Permission already requested. |
| if (HasPermissionForDevice(*iter->second, package)) { |
| std::move(callback).Run(true); |
| return; |
| } |
| |
| // The other side was just checking, fail without asking the user. |
| if (!interactive) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| DCHECK(ui_delegate_); |
| // Ask the authorization from the user. |
| ui_delegate_->RequestUsbAccessPermission( |
| package, guid, iter->second->serial_number.value_or(base::string16()), |
| iter->second->manufacturer_name.value_or(base::string16()), |
| iter->second->product_name.value_or(base::string16()), |
| iter->second->vendor_id, iter->second->product_id, std::move(callback)); |
| } |
| |
| void ArcUsbHostBridge::OpenDevice(const std::string& guid, |
| const base::Optional<std::string>& package, |
| OpenDeviceCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_); |
| |
| if (!package) { |
| std::move(callback).Run(mojo::ScopedHandle()); |
| return; |
| } |
| |
| // GUIDs are unguessable so device list should be initialized when this |
| // method is being called with a valid GUID. |
| auto iter = devices_.find(guid); |
| if (iter == devices_.end()) { |
| std::move(callback).Run(mojo::ScopedHandle()); |
| return; |
| } |
| |
| // The RequestPermission was never done, abort. |
| if (!HasPermissionForDevice(*iter->second, package.value())) { |
| std::move(callback).Run(mojo::ScopedHandle()); |
| return; |
| } |
| |
| auto repeating_callback = |
| base::AdaptCallbackForRepeating(std::move(callback)); |
| chromeos::PermissionBrokerClient::Get()->OpenPath( |
| GetDevicePath(*iter->second), |
| base::BindOnce(&OnDeviceOpened, repeating_callback), |
| base::BindOnce(&OnDeviceOpenError, repeating_callback)); |
| } |
| |
| void ArcUsbHostBridge::OpenDeviceDeprecated( |
| const std::string& guid, |
| const base::Optional<std::string>& package, |
| OpenDeviceCallback callback) { |
| LOG(ERROR) << "ArcUsbHostBridge::OpenDeviceDeprecated is deprecated"; |
| OpenDevice(guid, package, std::move(callback)); |
| } |
| |
| void ArcUsbHostBridge::GetDeviceInfo(const std::string& guid, |
| GetDeviceInfoCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_); |
| |
| // GUIDs are unguessable so device list should be initialized when this |
| // method is being called with a valid GUID. |
| auto iter = devices_.find(guid); |
| if (iter == devices_.end()) { |
| LOG(WARNING) << "Unknown USB device " << guid; |
| std::move(callback).Run(std::string(), nullptr); |
| return; |
| } |
| |
| device::mojom::UsbDeviceInfoPtr info = iter->second->Clone(); |
| // b/69295049 the other side doesn't like optional strings. |
| info->manufacturer_name = info->manufacturer_name.value_or(base::string16()); |
| info->product_name = info->product_name.value_or(base::string16()); |
| info->serial_number = info->serial_number.value_or(base::string16()); |
| for (const device::mojom::UsbConfigurationInfoPtr& cfg : |
| info->configurations) { |
| cfg->configuration_name = |
| cfg->configuration_name.value_or(base::string16()); |
| for (const device::mojom::UsbInterfaceInfoPtr& iface : cfg->interfaces) { |
| for (const device::mojom::UsbAlternateInterfaceInfoPtr& alt : |
| iface->alternates) { |
| alt->interface_name = alt->interface_name.value_or(base::string16()); |
| } |
| } |
| } |
| |
| std::string path = GetDevicePath(*info); |
| std::move(callback).Run(path, std::move(info)); |
| } |
| |
| void ArcUsbHostBridge::OnConnectionReady() { |
| if (delegate_) |
| delegate_->AttachDevicesToArcVm(); |
| |
| // Receive mojo::Remote<UsbDeviceManager> from DeviceService. |
| content::GetSystemConnector()->Connect( |
| device::mojom::kServiceName, usb_manager_.BindNewPipeAndPassReceiver()); |
| usb_manager_.set_disconnect_handler( |
| base::BindOnce(&ArcUsbHostBridge::Disconnect, base::Unretained(this))); |
| |
| // Listen for added/removed device events. |
| DCHECK(!client_receiver_.is_bound()); |
| usb_manager_->EnumerateDevicesAndSetClient( |
| client_receiver_.BindNewEndpointAndPassRemote(), |
| base::BindOnce(&ArcUsbHostBridge::InitDeviceList, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void ArcUsbHostBridge::OnConnectionClosed() { |
| if (ui_delegate_) |
| ui_delegate_->ClearPermissionRequests(); |
| |
| Disconnect(); |
| } |
| |
| void ArcUsbHostBridge::Shutdown() { |
| ui_delegate_ = nullptr; |
| } |
| |
| void ArcUsbHostBridge::SetUiDelegate(ArcUsbHostUiDelegate* ui_delegate) { |
| ui_delegate_ = ui_delegate; |
| } |
| |
| void ArcUsbHostBridge::SetDelegate(std::unique_ptr<Delegate> delegate) { |
| delegate_ = std::move(delegate); |
| } |
| |
| void ArcUsbHostBridge::InitDeviceList( |
| std::vector<device::mojom::UsbDeviceInfoPtr> devices) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_); |
| for (auto& device_info : devices) { |
| DCHECK(device_info); |
| std::string guid = device_info->guid; |
| devices_.insert(std::make_pair(guid, std::move(device_info))); |
| |
| // Send the (filtered) list of already existing USB devices to the other |
| // side. |
| usb_manager_->CheckAccess(guid, |
| base::BindOnce(&ArcUsbHostBridge::OnDeviceChecked, |
| weak_factory_.GetWeakPtr(), guid)); |
| } |
| } |
| |
| std::vector<std::string> ArcUsbHostBridge::GetEventReceiverPackages( |
| const device::mojom::UsbDeviceInfo& device_info) { |
| DCHECK(ui_delegate_); |
| |
| std::unordered_set<std::string> receivers = ui_delegate_->GetEventPackageList( |
| device_info.guid, device_info.serial_number.value_or(base::string16()), |
| device_info.vendor_id, device_info.product_id); |
| |
| return std::vector<std::string>(receivers.begin(), receivers.end()); |
| } |
| |
| void ArcUsbHostBridge::OnDeviceChecked(const std::string& guid, bool allowed) { |
| if (!base::FeatureList::IsEnabled(arc::kUsbHostFeature)) { |
| VLOG(1) << "AndroidUSBHost: feature is disabled; ignoring"; |
| return; |
| } |
| |
| if (!allowed) |
| return; |
| |
| // Device can be removed between being added and returning back from |
| // CheckAccess(). |
| auto iter = devices_.find(guid); |
| if (iter == devices_.end()) |
| return; |
| |
| mojom::UsbHostInstance* usb_host_instance = ARC_GET_INSTANCE_FOR_METHOD( |
| arc_bridge_service_->usb_host(), OnDeviceAdded); |
| |
| if (!usb_host_instance) |
| return; |
| |
| usb_host_instance->OnDeviceAdded(guid, |
| GetEventReceiverPackages(*iter->second)); |
| } |
| |
| bool ArcUsbHostBridge::HasPermissionForDevice( |
| const device::mojom::UsbDeviceInfo& device_info, |
| const std::string& package) { |
| DCHECK(ui_delegate_); |
| |
| return ui_delegate_->HasUsbAccessPermission( |
| package, device_info.guid, |
| device_info.serial_number.value_or(base::string16()), |
| device_info.vendor_id, device_info.product_id); |
| } |
| |
| void ArcUsbHostBridge::HandleScanDeviceListRequest( |
| const std::string& package, |
| RequestPermissionCallback callback) { |
| DCHECK(ui_delegate_); |
| |
| VLOG(2) << "USB Request USB scan devicelist permission " |
| << "package: " << package; |
| ui_delegate_->RequestUsbScanDeviceListPermission(package, |
| std::move(callback)); |
| } |
| |
| // Disconnect the connection with the DeviceService. |
| void ArcUsbHostBridge::Disconnect() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_); |
| |
| usb_manager_.reset(); |
| client_receiver_.reset(); |
| devices_.clear(); |
| } |
| |
| void ArcUsbHostBridge::OnDeviceAdded( |
| device::mojom::UsbDeviceInfoPtr device_info) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_); |
| DCHECK(device_info); |
| |
| // Update the device list. |
| DCHECK(!base::Contains(devices_, device_info->guid)); |
| std::string guid = device_info->guid; |
| devices_.insert(std::make_pair(guid, std::move(device_info))); |
| |
| usb_manager_->CheckAccess(guid, |
| base::BindOnce(&ArcUsbHostBridge::OnDeviceChecked, |
| weak_factory_.GetWeakPtr(), guid)); |
| } |
| |
| void ArcUsbHostBridge::OnDeviceRemoved( |
| device::mojom::UsbDeviceInfoPtr device_info) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_); |
| DCHECK(device_info); |
| |
| // Update the device list. |
| auto num_removed = devices_.erase(device_info->guid); |
| DCHECK_EQ(num_removed, 1u); |
| |
| mojom::UsbHostInstance* usb_host_instance = ARC_GET_INSTANCE_FOR_METHOD( |
| arc_bridge_service_->usb_host(), OnDeviceRemoved); |
| |
| if (!usb_host_instance) { |
| VLOG(2) << "UsbInstance not ready yet"; |
| return; |
| } |
| |
| usb_host_instance->OnDeviceRemoved(device_info->guid, |
| GetEventReceiverPackages(*device_info)); |
| |
| DCHECK(ui_delegate_); |
| ui_delegate_->DeviceRemoved(device_info->guid); |
| } |
| |
| } // namespace arc |