// 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 <utility>

#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/permission_broker_client.h"
#include "components/arc/arc_bridge_service.h"
#include "components/arc/arc_browser_context_keyed_service_factory_base.h"
#include "components/arc/arc_features.h"
#include "components/arc/usb/usb_host_ui_delegate.h"
#include "device/base/device_client.h"
#include "device/usb/mojo/type_converters.h"
#include "device/usb/usb_device_handle.h"
#include "device/usb/usb_device_linux.h"
#include "mojo/public/cpp/system/platform_handle.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());
}

using CheckedCallback =
    base::RepeatingCallback<void(const std::string& guid, bool success)>;

void OnGetDevicesComplete(
    const CheckedCallback& callback,
    const std::vector<scoped_refptr<device::UsbDevice>>& devices) {
  for (const scoped_refptr<device::UsbDevice>& device : devices)
    device->CheckUsbAccess(base::BindOnce(callback, device.get()->guid()));
}

}  // namespace

ArcUsbHostBridge* ArcUsbHostBridge::GetForBrowserContext(
    content::BrowserContext* context) {
  return ArcUsbHostBridgeFactory::GetForBrowserContext(context);
}

ArcUsbHostBridge::ArcUsbHostBridge(content::BrowserContext* context,
                                   ArcBridgeService* bridge_service)
    : arc_bridge_service_(bridge_service),
      usb_observer_(this),
      weak_factory_(this) {
  arc_bridge_service_->usb_host()->SetHost(this);
  arc_bridge_service_->usb_host()->AddObserver(this);

  usb_service_ = device::DeviceClient::Get()->GetUsbService();
  if (usb_service_)
    usb_observer_.Add(usb_service_);
}

ArcUsbHostBridge::~ArcUsbHostBridge() {
  if (usb_service_)
    usb_service_->RemoveObserver(this);
  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) {
  if (guid.empty()) {
    HandleScanDeviceListRequest(package, std::move(callback));
    return;
  }

  VLOG(2) << "USB RequestPermission " << guid << " package " << package;
  // Permission already requested.
  if (HasPermissionForDevice(guid, package)) {
    std::move(callback).Run(true);
    return;
  }

  // The other side was just checking, fail without asking the user.
  if (!interactive) {
    std::move(callback).Run(HasPermissionForDevice(guid, package));
    return;
  }

  // Ask the authorization from the user.
  DoRequestUserAuthorization(guid, package, std::move(callback));
}

void ArcUsbHostBridge::OpenDevice(const std::string& guid,
                                  const base::Optional<std::string>& package,
                                  OpenDeviceCallback callback) {
  if (!usb_service_ || !package) {
    std::move(callback).Run(mojo::ScopedHandle());
    return;
  }

  device::UsbDeviceLinux* device =
      static_cast<device::UsbDeviceLinux*>(usb_service_->GetDevice(guid).get());
  if (!device) {
    std::move(callback).Run(mojo::ScopedHandle());
    return;
  }

  // The RequestPermission was never done, abort.
  if (!HasPermissionForDevice(guid, package.value())) {
    std::move(callback).Run(mojo::ScopedHandle());
    return;
  }

  chromeos::PermissionBrokerClient* client =
      chromeos::DBusThreadManager::Get()->GetPermissionBrokerClient();
  DCHECK(client) << "Could not get permission broker client.";
  auto repeating_callback =
      base::AdaptCallbackForRepeating(std::move(callback));
  client->OpenPath(device->device_path(),
                   base::Bind(&OnDeviceOpened, repeating_callback),
                   base::Bind(&OnDeviceOpenError, repeating_callback));
}

void ArcUsbHostBridge::GetDeviceInfo(const std::string& guid,
                                     GetDeviceInfoCallback callback) {
  if (!usb_service_) {
    std::move(callback).Run(std::string(), nullptr);
    return;
  }
  scoped_refptr<device::UsbDevice> device = usb_service_->GetDevice(guid);
  if (!device.get()) {
    LOG(WARNING) << "Unknown USB device " << guid;
    std::move(callback).Run(std::string(), nullptr);
    return;
  }

  device::mojom::UsbDeviceInfoPtr info =
      device::mojom::UsbDeviceInfo::From(*device);
  // b/69295049 the other side doesn't like optional strings.
  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 =
      static_cast<device::UsbDeviceLinux*>(device.get())->device_path();

  std::move(callback).Run(path, std::move(info));
}

// device::UsbService::Observer callbacks.

void ArcUsbHostBridge::OnDeviceAdded(scoped_refptr<device::UsbDevice> device) {
  device->CheckUsbAccess(base::BindOnce(&ArcUsbHostBridge::OnDeviceChecked,
                                        weak_factory_.GetWeakPtr(),
                                        device.get()->guid()));
}

void ArcUsbHostBridge::OnDeviceRemoved(
    scoped_refptr<device::UsbDevice> device) {
  mojom::UsbHostInstance* usb_host_instance = ARC_GET_INSTANCE_FOR_METHOD(
      arc_bridge_service_->usb_host(), OnDeviceAdded);

  if (!usb_host_instance) {
    VLOG(2) << "UsbInstance not ready yet";
    return;
  }

  usb_host_instance->OnDeviceRemoved(device.get()->guid(),
                                     GetEventReceiverPackages(device));

  if (ui_delegate_)
    ui_delegate_->DeviceRemoved(device.get()->guid());
}

// Notifies the observer that the UsbService it depends on is shutting down.
void ArcUsbHostBridge::WillDestroyUsbService() {
  // Disconnect.
  arc_bridge_service_->usb_host()->SetHost(nullptr);
}

void ArcUsbHostBridge::OnConnectionReady() {
  if (!usb_service_)
    return;
  // Send the (filtered) list of already existing USB devices to the other side.
  usb_service_->GetDevices(
      base::Bind(&OnGetDevicesComplete,
                 base::BindRepeating(&ArcUsbHostBridge::OnDeviceChecked,
                                     weak_factory_.GetWeakPtr())));
}

void ArcUsbHostBridge::OnConnectionClosed() {
  if (ui_delegate_)
    ui_delegate_->ClearPermissionRequests();
}

void ArcUsbHostBridge::Shutdown() {
  ui_delegate_ = nullptr;
}

void ArcUsbHostBridge::SetUiDelegate(ArcUsbHostUiDelegate* ui_delegate) {
  ui_delegate_ = ui_delegate;
}

std::vector<std::string> ArcUsbHostBridge::GetEventReceiverPackages(
    scoped_refptr<device::UsbDevice> device) {
  if (!device) {
    LOG(WARNING) << "Unknown USB device.";
    return std::vector<std::string>();
  }

  if (!ui_delegate_)
    return std::vector<std::string>();

  std::unordered_set<std::string> receivers = ui_delegate_->GetEventPackageList(
      device->guid(), device->serial_number(), device->vendor_id(),
      device->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;

  mojom::UsbHostInstance* usb_host_instance = ARC_GET_INSTANCE_FOR_METHOD(
      arc_bridge_service_->usb_host(), OnDeviceAdded);

  if (!usb_host_instance || !usb_service_)
    return;

  usb_host_instance->OnDeviceAdded(
      guid, GetEventReceiverPackages(usb_service_->GetDevice(guid)));
}

void ArcUsbHostBridge::DoRequestUserAuthorization(
    const std::string& guid,
    const std::string& package,
    RequestPermissionCallback callback) {
  if (!ui_delegate_) {
    std::move(callback).Run(false);
    return;
  }

  if (!usb_service_) {
    std::move(callback).Run(false);
    return;
  }

  scoped_refptr<device::UsbDevice> device = usb_service_->GetDevice(guid);
  if (!device.get()) {
    LOG(WARNING) << "Unknown USB device " << guid;
    std::move(callback).Run(false);
    return;
  }

  ui_delegate_->RequestUsbAccessPermission(
      package, guid, device->serial_number(), device->manufacturer_string(),
      device->product_string(), device->vendor_id(), device->product_id(),
      std::move(callback));
}

bool ArcUsbHostBridge::HasPermissionForDevice(const std::string& guid,
                                              const std::string& package) {
  if (!ui_delegate_)
    return false;

  if (!usb_service_)
    return false;

  scoped_refptr<device::UsbDevice> device = usb_service_->GetDevice(guid);
  if (!device.get()) {
    LOG(WARNING) << "Unknown USB device " << guid;
    return false;
  }

  return ui_delegate_->HasUsbAccessPermission(
      package, guid, device->serial_number(), device->vendor_id(),
      device->product_id());
}

void ArcUsbHostBridge::HandleScanDeviceListRequest(
    const std::string& package,
    RequestPermissionCallback callback) {
  if (!ui_delegate_) {
    std::move(callback).Run(false);
    return;
  }

  VLOG(2) << "USB Request USB scan devicelist permission "
          << "package: " << package;
  ui_delegate_->RequestUsbScanDeviceListPermission(package,
                                                   std::move(callback));
}

}  // namespace arc
