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