1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179

ash / system / federated / federated_service_controller_impl.cc [blame]

// Copyright 2022 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/federated/federated_service_controller_impl.h"

#include <string>

#include "ash/constants/ash_features.h"
#include "ash/login_status.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "base/containers/fixed_flat_map.h"
#include "base/containers/flat_map.h"
#include "base/feature_list.h"
#include "base/i18n/timezone.h"
#include "base/memory/raw_ptr.h"
#include "base/no_destructor.h"
#include "chromeos/ash/services/federated/public/cpp/federated_example_util.h"
#include "chromeos/ash/services/federated/public/cpp/service_connection.h"
#include "chromeos/ash/services/federated/public/mojom/federated_service.mojom.h"
#include "chromeos/ash/services/federated/public/mojom/tables.mojom.h"
#include "components/user_manager/user_type.h"

namespace ash::federated {
namespace {

using chromeos::federated::mojom::ClientScheduleConfig;
using chromeos::federated::mojom::ClientScheduleConfigPtr;
using chromeos::federated::mojom::FederatedExampleTableId;

// Local client config struct that can be converted to mojom
// ClientScheduleConfig.
struct LocalClientConfig {
  // A client uses its client_name and launch stage to make up the task group
  // identifier when checking in with the server.
  std::string_view client_name;
  // The table_id is defined in
  // chromeos/ash/services/federated/public/mojom/tables.mojom
  FederatedExampleTableId table_id;
  // The associated_feature is usually defined in
  // ash/constants/ash_features.h/cc
  raw_ptr<const base::Feature> associated_feature;
  // The hardcoded_stage is set when a client is fully launched and no longer
  // needs to be changed, in sucn cases the associated_featur should be nullptr,
  // e.g.
  // {"foo_client_name", FederatedExampleTableId::BAR_TABLE, nullptr, "prod"}
  std::optional<std::string_view> hardcoded_stage;
};

// Each federated client should have an entry in `kLocalClientConfigs` that
// contains its client name, example table id and a feature with launch stage as
// the associated parameter, or a hardcoded launch stage.
const std::array<LocalClientConfig, 4> kLocalClientConfigs = {{
    {"input_autocorrect_phh", FederatedExampleTableId::INPUT_AUTOCORRECT,
     &features::kAutocorrectFederatedPhh},
    {"launcher_query_analytics_v1", FederatedExampleTableId::LAUNCHER_QUERY,
     nullptr},
    {"launcher_query_analytics_v2", FederatedExampleTableId::LAUNCHER_QUERY_V2,
     &features::kFederatedLauncherQueryAnalyticsVersion2Task},
    {"timezone_code_phh", FederatedExampleTableId::TIMEZONE_CODE, nullptr},
}};

// Converts a LocalClientConfig to mojom ClientScheduleConfigPtr that can be
// used for scheduling tasks via mojom interface.
// The major logic here is to obtain a valid launch stage. If a client's
// associated_feature is not null, tries to get the launch_stage from the
// feature's parameter. Otherwise tries to use the hardcoded stage. If
// eventually no valid launch_stage, the client is ignored.
std::optional<ClientScheduleConfigPtr> ConvertLocalConfigToSchedulingConfig(
    const LocalClientConfig& local_config) {
  std::string launch_stage;
  if (local_config.associated_feature != nullptr) {
    base::FeatureParam<std::string> launch_stage_param{
        local_config.associated_feature, "launch_stage", ""};
    launch_stage = launch_stage_param.Get();
  } else if (local_config.hardcoded_stage.has_value()) {
    launch_stage = local_config.hardcoded_stage.value();
  }

  if (launch_stage.empty()) {
    DVLOG(1) << "client " << local_config.client_name
             << " has no valid launch stage, ignored.";
    return std::nullopt;
  }
  auto schedule_config = ClientScheduleConfig::New();
  schedule_config->client_name = local_config.client_name;
  schedule_config->example_storage_table_id = local_config.table_id;
  schedule_config->launch_stage = launch_stage;

  return schedule_config;
}

// Prepare client configs for scheduling tasks.
std::vector<ClientScheduleConfigPtr> PrepareClientScheduleConfigs() {
  std::vector<ClientScheduleConfigPtr> client_schedule_configs;
  for (const auto& local_config : kLocalClientConfigs) {
    auto maybe_schedule_config =
        ConvertLocalConfigToSchedulingConfig(local_config);
    if (maybe_schedule_config.has_value()) {
      client_schedule_configs.push_back(
          std::move(maybe_schedule_config.value()));
    }
  }

  return client_schedule_configs;
}

chromeos::federated::mojom::ExamplePtr CreateBrellaAnalyticsExamplePtr() {
  auto example = chromeos::federated::mojom::Example::New();
  example->features = chromeos::federated::mojom::Features::New();
  auto& feature_map = example->features->feature;
  feature_map["timezone_code"] =
      federated::CreateStringList({base::CountryCodeForCurrentTimezone()});
  return example;
}

// Returns whether federated can run for this type of logged-in user.
bool IsValidPrimaryUserType(const user_manager::UserType user_type) {
  // Primary user session must have user_type = regular or child (v.s. guest,
  // public account, kiosk app).
  return user_type == user_manager::UserType::kRegular ||
         user_type == user_manager::UserType::kChild;
}

}  // namespace

FederatedServiceControllerImpl::FederatedServiceControllerImpl() {
  SessionControllerImpl* session_controller =
      Shell::Get()->session_controller();
  DCHECK(session_controller);
  session_observation_.Observe(session_controller);
}

FederatedServiceControllerImpl::~FederatedServiceControllerImpl() = default;

void FederatedServiceControllerImpl::OnLoginStatusChanged(
    LoginStatus login_status) {
  // Federated service daemon uses cryptohome as example store and we only
  // treat it available when a proper primary user type has signed in.

  // Actually once `federated_service_` gets bound, even if availability is
  // set false because of subsequent LoginStatus changes, it keeps bound and
  // it's safe to call `federated_service_->ReportExampleToTable()`. But on the
  // ChromeOS daemon side it loses a valid cryptohome hence no valid example
  // storage, all reported examples are abandoned.

  auto* primary_user_session =
      Shell::Get()->session_controller()->GetPrimaryUserSession();

  service_available_ =
      primary_user_session != nullptr &&
      IsValidPrimaryUserType(primary_user_session->user_info.type);

  if (service_available_ && !federated_service_.is_bound()) {
    federated::ServiceConnection::GetInstance()->BindReceiver(
        federated_service_.BindNewPipeAndPassReceiver());

    if (features::IsFederatedServiceEnabled()) {
      federated_service_->StartSchedulingWithConfig(
          PrepareClientScheduleConfigs());
    }

    // On session first login, reports one example for "timezone_code_phh", a
    // trivial F.A. task for prove-out purpose.
    if (!reported_) {
      federated_service_->ReportExampleToTable(
          FederatedExampleTableId::TIMEZONE_CODE,
          CreateBrellaAnalyticsExamplePtr());
      reported_ = true;
    }
  }
}

bool FederatedServiceControllerImpl::IsServiceAvailable() const {
  return service_available_;
}

}  // namespace ash::federated