chromium/chromeos/ash/services/multidevice_setup/public/cpp/multidevice_setup_client_impl.cc

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

#include <utility>

#include "chromeos/ash/services/multidevice_setup/public/cpp/multidevice_setup_client_impl.h"

#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/ranges/algorithm.h"
#include "base/time/time.h"
#include "chromeos/ash/components/multidevice/logging/logging.h"
#include "chromeos/ash/services/multidevice_setup/public/mojom/multidevice_setup.mojom.h"

namespace {

constexpr base::TimeDelta kInitialFeatureStateLoggingDelay = base::Seconds(15);
constexpr base::TimeDelta kRepeatingFeatureStateLoggingPeriod =
    base::Minutes(30);

// Log the feature states in |feature_states_map|. Called 1) on
// sign-in, 2) when at least one feature state changes, and 3) every 30
// minutes. The latter is necessary to capture users who stay logged in longer
// than UMA aggregation periods and don't change feature state.
void LogFeatureStates(
    const ash::multidevice_setup::MultiDeviceSetupClient::FeatureStatesMap&
        feature_states_map) {
  // There is a duplicate metric of
  // "MultiDevice.BetterTogetherSuite.MultiDeviceFeatureState" on different
  // ends of the mojo pipe to help us understand how frequent we get stuck
  // on this end of the pipe waiting for the client to be ready. See b/215469053
  // for more context.
  base::UmaHistogramEnumeration(
      "MultiDevice.BetterTogetherSuite.MultiDeviceFeatureState.MojoClient",
      feature_states_map
          .find(ash::multidevice_setup::mojom::Feature::kBetterTogetherSuite)
          ->second);
}

}  // namespace

namespace ash {

namespace multidevice_setup {

// static
MultiDeviceSetupClientImpl::Factory*
    MultiDeviceSetupClientImpl::Factory::test_factory_ = nullptr;

// static
std::unique_ptr<MultiDeviceSetupClient>
MultiDeviceSetupClientImpl::Factory::Create(
    mojo::PendingRemote<mojom::MultiDeviceSetup> remote_setup) {
  if (test_factory_)
    return test_factory_->CreateInstance(std::move(remote_setup));

  return base::WrapUnique(
      new MultiDeviceSetupClientImpl(std::move(remote_setup)));
}

// static
void MultiDeviceSetupClientImpl::Factory::SetFactoryForTesting(
    Factory* test_factory) {
  test_factory_ = test_factory;
}

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

MultiDeviceSetupClientImpl::MultiDeviceSetupClientImpl(
    mojo::PendingRemote<mojom::MultiDeviceSetup> remote_setup)
    : multidevice_setup_remote_(std::move(remote_setup)),
      remote_device_cache_(multidevice::RemoteDeviceCache::Factory::Create()),
      host_status_with_device_(GenerateDefaultHostStatusWithDevice()),
      feature_states_map_(GenerateDefaultFeatureStatesMap(
          mojom::FeatureState::kUnavailableNoVerifiedHost_ClientNotReady)) {
  multidevice_setup_remote_->AddHostStatusObserver(
      GenerateHostStatusObserverRemote());
  multidevice_setup_remote_->AddFeatureStateObserver(
      GenerateFeatureStatesObserverRemote());
  multidevice_setup_remote_->GetHostStatus(
      base::BindOnce(&MultiDeviceSetupClientImpl::OnHostStatusChanged,
                     base::Unretained(this)));
  multidevice_setup_remote_->GetFeatureStates(
      base::BindOnce(&MultiDeviceSetupClientImpl::OnFeatureStatesChanged,
                     base::Unretained(this)));

  // Delay the initial feature metric state logger in order to allow for
  // the client to become ready on initialization before we log, otherwise the
  // |kUnavailableNoVerifiedHost_ClientNotReady| will always be logged, which
  // does not help us understand the frequency of getting "stuck" waiting for
  // the client. The timer ends sooner than |kInitialFeatureStateLoggingDelay|
  // when |OnFeatureStatesChanged| is called (which signals the client is
  // ready and is logged).
  initial_feature_state_metric_logging_timer_.Start(
      FROM_HERE, kInitialFeatureStateLoggingDelay,
      base::BindRepeating(
          &MultiDeviceSetupClientImpl::OnFeatureStateMetricTimerFired,
          base::Unretained(this)));

  // Log the feature states every |kRepeatingFeatureStateLoggingPeriod| to
  // capture users who stay logged in longer than UMA aggregation periods and
  // don't change feature state. For more information on the frequency of
  // logging, see comments above `LogFeatureStates()`.
  feature_state_metric_timer_.Start(
      FROM_HERE, kRepeatingFeatureStateLoggingPeriod,
      base::BindRepeating(
          &MultiDeviceSetupClientImpl::OnFeatureStateMetricTimerFired,
          base::Unretained(this)));
}

MultiDeviceSetupClientImpl::~MultiDeviceSetupClientImpl() = default;

void MultiDeviceSetupClientImpl::OnFeatureStateMetricTimerFired() {
  LogFeatureStates(feature_states_map_);
}

void MultiDeviceSetupClientImpl::GetEligibleHostDevices(
    GetEligibleHostDevicesCallback callback) {
  multidevice_setup_remote_->GetEligibleHostDevices(base::BindOnce(
      &MultiDeviceSetupClientImpl::OnGetEligibleHostDevicesCompleted,
      base::Unretained(this), std::move(callback)));
}

void MultiDeviceSetupClientImpl::SetHostDevice(
    const std::string& host_instance_id_or_legacy_device_id,
    const std::string& auth_token,
    mojom::MultiDeviceSetup::SetHostDeviceCallback callback) {
  multidevice_setup_remote_->SetHostDevice(host_instance_id_or_legacy_device_id,
                                           auth_token, std::move(callback));
}

void MultiDeviceSetupClientImpl::RemoveHostDevice() {
  multidevice_setup_remote_->RemoveHostDevice();
}

const MultiDeviceSetupClient::HostStatusWithDevice&
MultiDeviceSetupClientImpl::GetHostStatus() const {
  PA_LOG(INFO) << "Responding to GetHostStatus() with the following host = "
               << HostStatusWithDeviceToString(host_status_with_device_);
  return host_status_with_device_;
}

void MultiDeviceSetupClientImpl::SetFeatureEnabledState(
    mojom::Feature feature,
    bool enabled,
    const std::optional<std::string>& auth_token,
    mojom::MultiDeviceSetup::SetFeatureEnabledStateCallback callback) {
  multidevice_setup_remote_->SetFeatureEnabledState(
      feature, enabled, auth_token, std::move(callback));
}

const MultiDeviceSetupClient::FeatureStatesMap&
MultiDeviceSetupClientImpl::GetFeatureStates() const {
  return feature_states_map_;
}

void MultiDeviceSetupClientImpl::RetrySetHostNow(
    mojom::MultiDeviceSetup::RetrySetHostNowCallback callback) {
  multidevice_setup_remote_->RetrySetHostNow(std::move(callback));
}

void MultiDeviceSetupClientImpl::TriggerEventForDebugging(
    mojom::EventTypeForDebugging type,
    mojom::MultiDeviceSetup::TriggerEventForDebuggingCallback callback) {
  multidevice_setup_remote_->TriggerEventForDebugging(type,
                                                      std::move(callback));
}

void MultiDeviceSetupClientImpl::SetQuickStartPhoneInstanceID(
    const std::string& qs_phone_instance_id) {
  multidevice_setup_remote_->SetQuickStartPhoneInstanceID(qs_phone_instance_id);
}

void MultiDeviceSetupClientImpl::OnHostStatusChanged(
    mojom::HostStatus host_status,
    const std::optional<multidevice::RemoteDevice>& host_device) {
  if (host_device) {
    remote_device_cache_->SetRemoteDevices({*host_device});
    host_status_with_device_ = std::make_pair(
        host_status, remote_device_cache_->GetRemoteDevice(
                         host_device->instance_id, host_device->GetDeviceId()));
  } else {
    host_status_with_device_ =
        std::make_pair(host_status, std::nullopt /* host_device */);
  }

  PA_LOG(INFO) << "Host status with device has changed. New status: "
               << HostStatusWithDeviceToString(host_status_with_device_);
  NotifyHostStatusChanged(host_status_with_device_);
}

void MultiDeviceSetupClientImpl::OnFeatureStatesChanged(
    const FeatureStatesMap& feature_states_map) {
  initial_feature_state_metric_logging_timer_.Stop();
  PA_LOG(INFO) << "Feature states have changed. New feature map: "
               << FeatureStatesMapToString(feature_states_map);
  feature_states_map_ = feature_states_map;
  NotifyFeatureStateChanged(feature_states_map_);
  LogFeatureStates(feature_states_map_);
}

void MultiDeviceSetupClientImpl::OnGetEligibleHostDevicesCompleted(
    GetEligibleHostDevicesCallback callback,
    const multidevice::RemoteDeviceList& eligible_host_devices) {
  remote_device_cache_->SetRemoteDevices(eligible_host_devices);

  multidevice::RemoteDeviceRefList eligible_host_device_refs;
  base::ranges::transform(eligible_host_devices,
                          std::back_inserter(eligible_host_device_refs),
                          [this](const auto& device) {
                            return *remote_device_cache_->GetRemoteDevice(
                                device.instance_id, device.GetDeviceId());
                          });

  std::move(callback).Run(eligible_host_device_refs);
}

mojo::PendingRemote<mojom::HostStatusObserver>
MultiDeviceSetupClientImpl::GenerateHostStatusObserverRemote() {
  return host_status_observer_receiver_.BindNewPipeAndPassRemote();
}

mojo::PendingRemote<mojom::FeatureStateObserver>
MultiDeviceSetupClientImpl::GenerateFeatureStatesObserverRemote() {
  return feature_state_observer_receiver_.BindNewPipeAndPassRemote();
}

void MultiDeviceSetupClientImpl::FlushForTesting() {
  multidevice_setup_remote_.FlushForTesting();
}

}  // namespace multidevice_setup

}  // namespace ash