chromium/chromeos/ash/services/device_sync/software_feature_manager_impl.cc

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ash/services/device_sync/software_feature_manager_impl.h"

#include <utility>

#include "base/check.h"
#include "base/memory/ptr_util.h"
#include "chromeos/ash/services/device_sync/cryptauth_client.h"
#include "chromeos/ash/services/device_sync/cryptauth_feature_status_setter_impl.h"
#include "chromeos/ash/services/device_sync/proto/cryptauth_api.pb.h"
#include "chromeos/ash/services/device_sync/proto/enum_util.h"

namespace ash {

namespace device_sync {

// static
SoftwareFeatureManagerImpl::Factory*
    SoftwareFeatureManagerImpl::Factory::test_factory_instance_ = nullptr;

// static
std::unique_ptr<SoftwareFeatureManager>
SoftwareFeatureManagerImpl::Factory::Create(
    CryptAuthClientFactory* cryptauth_client_factory,
    CryptAuthFeatureStatusSetter* feature_status_setter) {
  if (test_factory_instance_)
    return test_factory_instance_->CreateInstance(cryptauth_client_factory,
                                                  feature_status_setter);

  return base::WrapUnique(new SoftwareFeatureManagerImpl(
      cryptauth_client_factory, feature_status_setter));
}

void SoftwareFeatureManagerImpl::Factory::SetFactoryForTesting(
    Factory* test_factory) {
  test_factory_instance_ = test_factory;
}

SoftwareFeatureManagerImpl::Factory::~Factory() = default;

SoftwareFeatureManagerImpl::Request::Request(
    std::unique_ptr<cryptauth::ToggleEasyUnlockRequest> toggle_request,
    base::OnceClosure set_software_success_callback,
    base::OnceCallback<void(NetworkRequestError)> error_callback)
    : request_type(RequestType::kSetSoftwareFeature),
      error_callback(std::move(error_callback)),
      toggle_request(std::move(toggle_request)),
      set_software_success_callback(std::move(set_software_success_callback)) {}

SoftwareFeatureManagerImpl::Request::Request(
    const std::string& device_id,
    multidevice::SoftwareFeature feature,
    FeatureStatusChange status_change,
    base::OnceClosure set_feature_status_success_callback,
    base::OnceCallback<void(NetworkRequestError)>
        set_feature_status_error_callback)
    : request_type(RequestType::kSetFeatureStatus),
      device_id(device_id),
      feature(feature),
      status_change(status_change),
      set_feature_status_success_callback(
          std::move(set_feature_status_success_callback)),
      set_feature_status_error_callback(
          std::move(set_feature_status_error_callback)) {}

SoftwareFeatureManagerImpl::Request::Request(
    std::unique_ptr<cryptauth::FindEligibleUnlockDevicesRequest> find_request,
    base::OnceCallback<void(const std::vector<cryptauth::ExternalDeviceInfo>&,
                            const std::vector<cryptauth::IneligibleDevice>&)>
        find_hosts_success_callback,
    base::OnceCallback<void(NetworkRequestError)> error_callback)
    : request_type(RequestType::kFindEligibleMultideviceHosts),
      error_callback(std::move(error_callback)),
      find_request(std::move(find_request)),
      find_hosts_success_callback(std::move(find_hosts_success_callback)) {}

SoftwareFeatureManagerImpl::Request::~Request() = default;

SoftwareFeatureManagerImpl::SoftwareFeatureManagerImpl(
    CryptAuthClientFactory* cryptauth_client_factory,
    CryptAuthFeatureStatusSetter* feature_status_setter)
    : crypt_auth_client_factory_(cryptauth_client_factory),
      feature_status_setter_(feature_status_setter) {}

SoftwareFeatureManagerImpl::~SoftwareFeatureManagerImpl() = default;

void SoftwareFeatureManagerImpl::SetSoftwareFeatureState(
    const std::string& public_key,
    multidevice::SoftwareFeature software_feature,
    bool enabled,
    base::OnceClosure success_callback,
    base::OnceCallback<void(NetworkRequestError)> error_callback,
    bool is_exclusive) {
  // Note: For legacy reasons, this proto message mentions "ToggleEasyUnlock"
  // instead of "SetSoftwareFeature" in its name.
  auto request = std::make_unique<cryptauth::ToggleEasyUnlockRequest>();
  request->set_feature(SoftwareFeatureEnumToString(
      multidevice::ToCryptAuthFeature(software_feature)));
  request->set_enable(enabled);
  request->set_is_exclusive(enabled && is_exclusive);

  // Special case for EasyUnlock: if EasyUnlock is being disabled, set the
  // apply_to_all property to true, and do not set the public_key field.
  bool turn_off_easy_unlock_special_case =
      !enabled &&
      software_feature == multidevice::SoftwareFeature::kSmartLockHost;
  request->set_apply_to_all(turn_off_easy_unlock_special_case);
  if (!turn_off_easy_unlock_special_case)
    request->set_public_key(public_key);

  pending_requests_.emplace(
      std::make_unique<Request>(std::move(request), std::move(success_callback),
                                std::move(error_callback)));
  ProcessRequestQueue();
}

void SoftwareFeatureManagerImpl::SetFeatureStatus(
    const std::string& device_id,
    multidevice::SoftwareFeature feature,
    FeatureStatusChange status_change,
    base::OnceClosure success_callback,
    base::OnceCallback<void(NetworkRequestError)> error_callback) {
  pending_requests_.emplace(std::make_unique<Request>(
      device_id, feature, status_change, std::move(success_callback),
      std::move(error_callback)));
  ProcessRequestQueue();
}

void SoftwareFeatureManagerImpl::FindEligibleDevices(
    multidevice::SoftwareFeature software_feature,
    base::OnceCallback<void(const std::vector<cryptauth::ExternalDeviceInfo>&,
                            const std::vector<cryptauth::IneligibleDevice>&)>
        success_callback,
    base::OnceCallback<void(NetworkRequestError)> error_callback) {
  // Note: For legacy reasons, this proto message mentions "UnlockDevices"
  // instead of "MultiDeviceHosts" in its name.
  auto request =
      std::make_unique<cryptauth::FindEligibleUnlockDevicesRequest>();
  request->set_feature(SoftwareFeatureEnumToString(
      multidevice::ToCryptAuthFeature(software_feature)));

  // For historical reasons, the Bluetooth address is abused to mark a which
  // feature should receive a GCM callback. Read more at
  // https://crbug.com/883915.
  request->set_callback_bluetooth_address(SoftwareFeatureEnumToStringAllCaps(
      multidevice::ToCryptAuthFeature(software_feature)));

  pending_requests_.emplace(
      std::make_unique<Request>(std::move(request), std::move(success_callback),
                                std::move(error_callback)));
  ProcessRequestQueue();
}

void SoftwareFeatureManagerImpl::ProcessRequestQueue() {
  if (current_request_ || pending_requests_.empty())
    return;

  current_request_ = std::move(pending_requests_.front());
  pending_requests_.pop();

  switch (current_request_->request_type) {
    case RequestType::kSetSoftwareFeature:
      ProcessSetSoftwareFeatureStateRequest();
      break;
    case RequestType::kSetFeatureStatus:
      ProcessSetFeatureStatusRequest();
      break;
    case RequestType::kFindEligibleMultideviceHosts:
      ProcessFindEligibleDevicesRequest();
      break;
  }
}

void SoftwareFeatureManagerImpl::ProcessSetSoftwareFeatureStateRequest() {
  DCHECK(!current_cryptauth_client_);
  current_cryptauth_client_ = crypt_auth_client_factory_->CreateInstance();

  current_cryptauth_client_->ToggleEasyUnlock(
      *current_request_->toggle_request,
      base::BindOnce(&SoftwareFeatureManagerImpl::OnToggleEasyUnlockResponse,
                     weak_ptr_factory_.GetWeakPtr()),
      base::BindOnce(&SoftwareFeatureManagerImpl::OnErrorResponse,
                     weak_ptr_factory_.GetWeakPtr()));
}

void SoftwareFeatureManagerImpl::ProcessSetFeatureStatusRequest() {
  DCHECK(feature_status_setter_);

  feature_status_setter_->SetFeatureStatus(
      current_request_->device_id, current_request_->feature,
      current_request_->status_change,
      base::BindOnce(&SoftwareFeatureManagerImpl::OnSetFeatureStatusSuccess,
                     weak_ptr_factory_.GetWeakPtr()),
      base::BindOnce(&SoftwareFeatureManagerImpl::OnSetFeatureStatusError,
                     weak_ptr_factory_.GetWeakPtr()));
}

void SoftwareFeatureManagerImpl::ProcessFindEligibleDevicesRequest() {
  DCHECK(!current_cryptauth_client_);
  current_cryptauth_client_ = crypt_auth_client_factory_->CreateInstance();

  current_cryptauth_client_->FindEligibleUnlockDevices(
      *current_request_->find_request,
      base::BindOnce(
          &SoftwareFeatureManagerImpl::OnFindEligibleUnlockDevicesResponse,
          weak_ptr_factory_.GetWeakPtr()),
      base::BindOnce(&SoftwareFeatureManagerImpl::OnErrorResponse,
                     weak_ptr_factory_.GetWeakPtr()));
}

void SoftwareFeatureManagerImpl::OnToggleEasyUnlockResponse(
    const cryptauth::ToggleEasyUnlockResponse& response) {
  current_cryptauth_client_.reset();
  std::move(current_request_->set_software_success_callback).Run();
  current_request_.reset();
  ProcessRequestQueue();
}

void SoftwareFeatureManagerImpl::OnSetFeatureStatusSuccess() {
  std::move(current_request_->set_feature_status_success_callback).Run();
  current_request_.reset();
  ProcessRequestQueue();
}

void SoftwareFeatureManagerImpl::OnSetFeatureStatusError(
    NetworkRequestError error) {
  std::move(current_request_->set_feature_status_error_callback).Run(error);
  current_request_.reset();
  ProcessRequestQueue();
}

void SoftwareFeatureManagerImpl::OnFindEligibleUnlockDevicesResponse(
    const cryptauth::FindEligibleUnlockDevicesResponse& response) {
  current_cryptauth_client_.reset();
  std::move(current_request_->find_hosts_success_callback)
      .Run(std::vector<cryptauth::ExternalDeviceInfo>(
               response.eligible_devices().begin(),
               response.eligible_devices().end()),
           std::vector<cryptauth::IneligibleDevice>(
               response.ineligible_devices().begin(),
               response.ineligible_devices().end()));
  current_request_.reset();
  ProcessRequestQueue();
}

void SoftwareFeatureManagerImpl::OnErrorResponse(NetworkRequestError error) {
  current_cryptauth_client_.reset();
  std::move(current_request_->error_callback).Run(error);
  current_request_.reset();
  ProcessRequestQueue();
}

}  // namespace device_sync

}  // namespace ash