// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/policy/status_collector/child_status_collector.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <cstdio>
#include <limits>
#include <optional>
#include <set>
#include <sstream>
#include <utility>
#include "ash/components/arc/mojom/enterprise_reporting.mojom.h"
#include "ash/components/arc/session/arc_bridge_service.h"
#include "ash/components/arc/session/arc_service_manager.h"
#include "base/base64.h"
#include "base/check.h"
#include "base/feature_list.h"
#include "base/format_macros.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/values.h"
#include "chrome/browser/ash/policy/status_collector/child_activity_storage.h"
#include "chrome/browser/ash/policy/status_collector/status_collector_state.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "chromeos/ash/components/login/login_state/login_state.h"
#include "chromeos/ash/components/settings/cros_settings.h"
#include "chromeos/ash/components/settings/cros_settings_names.h"
#include "chromeos/ash/components/settings/timezone_settings.h"
#include "chromeos/ash/components/system/statistics_provider.h"
#include "chromeos/version/version_loader.h"
#include "components/policy/core/common/cloud/cloud_policy_constants.h"
#include "components/policy/core/common/cloud/cloud_policy_util.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "content/public/browser/browser_thread.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
namespace policy {
namespace {
namespace em = ::enterprise_management;
// How much time in the past to store active periods for.
constexpr base::TimeDelta kMaxStoredPastActivityInterval = base::Days(30);
// How much time in the future to store active periods for.
constexpr base::TimeDelta kMaxStoredFutureActivityInterval = base::Days(2);
// How often the child's usage time is stored.
constexpr base::TimeDelta kUpdateChildActiveTimeInterval = base::Seconds(30);
const char kReportSizeHistogramName[] =
"ChromeOS.FamilyLink.ChildStatusReportRequest.Size";
const char kTimeSinceLastReportHistogramName[] =
"ChromeOS.FamilyLink.ChildStatusReportRequest.TimeSinceLastReport";
bool ReadAndroidStatus(ChildStatusCollector::AndroidStatusReceiver receiver) {
auto* const arc_service_manager = arc::ArcServiceManager::Get();
if (!arc_service_manager)
return false;
auto* const instance_holder =
arc_service_manager->arc_bridge_service()->enterprise_reporting();
if (!instance_holder)
return false;
auto* const instance =
ARC_GET_INSTANCE_FOR_METHOD(instance_holder, GetStatus);
if (!instance)
return false;
instance->GetStatus(std::move(receiver));
return true;
}
} // namespace
class ChildStatusCollectorState : public StatusCollectorState {
public:
explicit ChildStatusCollectorState(
const scoped_refptr<base::SequencedTaskRunner> task_runner,
StatusCollectorCallback response)
: StatusCollectorState(task_runner, std::move(response)) {}
bool FetchAndroidStatus(
const StatusCollector::AndroidStatusFetcher& android_status_fetcher) {
return android_status_fetcher.Run(base::BindOnce(
&ChildStatusCollectorState::OnAndroidInfoReceived, this));
}
private:
~ChildStatusCollectorState() override = default;
void OnAndroidInfoReceived(const std::string& status,
const std::string& droid_guard_info) {
em::AndroidStatus* const child_android_status =
response_params_.child_status->mutable_android_status();
child_android_status->set_status_payload(status);
child_android_status->set_droid_guard_info(droid_guard_info);
}
};
ChildStatusCollector::ChildStatusCollector(
PrefService* pref_service,
Profile* profile,
ash::system::StatisticsProvider* provider,
const AndroidStatusFetcher& android_status_fetcher,
base::TimeDelta activity_day_start)
: StatusCollector(provider, ash::CrosSettings::Get()),
pref_service_(pref_service),
profile_(profile),
android_status_fetcher_(android_status_fetcher) {
DCHECK(profile_);
// protected fields of `StatusCollector`.
max_stored_past_activity_interval_ = kMaxStoredPastActivityInterval;
max_stored_future_activity_interval_ = kMaxStoredFutureActivityInterval;
// Get the task runner of the current thread, so we can queue status responses
// on this thread.
CHECK(base::SequencedTaskRunner::HasCurrentDefault());
task_runner_ = base::SequencedTaskRunner::GetCurrentDefault();
if (android_status_fetcher_.is_null())
android_status_fetcher_ = base::BindRepeating(&ReadAndroidStatus);
update_child_usage_timer_.Start(FROM_HERE, kUpdateChildActiveTimeInterval,
this,
&ChildStatusCollector::UpdateChildUsageTime);
// Watch for changes to the individual policies that control what the status
// reports contain.
auto callback = base::BindRepeating(
&ChildStatusCollector::UpdateReportingSettings, base::Unretained(this));
version_info_subscription_ = cros_settings_->AddSettingsObserver(
ash::kReportDeviceVersionInfo, callback);
boot_mode_subscription_ =
cros_settings_->AddSettingsObserver(ash::kReportDeviceBootMode, callback);
// Watch for changes on the device state to calculate the child's active time.
ash::UsageTimeStateNotifier::GetInstance()->AddObserver(this);
// Fetch the current values of the policies.
UpdateReportingSettings();
// Get the OS version.
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
base::BindOnce(&chromeos::version_loader::GetVersion,
chromeos::version_loader::VERSION_FULL),
base::BindOnce(&ChildStatusCollector::OnOSVersion,
weak_factory_.GetWeakPtr()));
DCHECK(pref_service_->GetInitializationStatus() !=
PrefService::INITIALIZATION_STATUS_WAITING);
activity_storage_ = std::make_unique<ChildActivityStorage>(
pref_service_, prefs::kUserActivityTimes, activity_day_start);
}
ChildStatusCollector::~ChildStatusCollector() {
ash::UsageTimeStateNotifier::GetInstance()->RemoveObserver(this);
}
base::TimeDelta ChildStatusCollector::GetActiveChildScreenTime() {
UpdateChildUsageTime();
return base::Milliseconds(
pref_service_->GetInteger(prefs::kChildScreenTimeMilliseconds));
}
// static
const char* ChildStatusCollector::GetReportSizeHistogramNameForTest() {
return kReportSizeHistogramName;
}
const char* ChildStatusCollector::GetTimeSinceLastReportHistogramNameForTest() {
return kTimeSinceLastReportHistogramName;
}
void ChildStatusCollector::UpdateReportingSettings() {
// Attempt to fetch the current value of the reporting settings.
// If trusted values are not available, register this function to be called
// back when they are available.
if (ash::CrosSettingsProvider::TRUSTED !=
cros_settings_->PrepareTrustedValues(
base::BindOnce(&ChildStatusCollector::UpdateReportingSettings,
weak_factory_.GetWeakPtr()))) {
return;
}
// Settings related.
// Keep the default values in sync with DeviceReportingProto in
// chrome/browser/ash/policy/status_collector/child_status_collector.cc.
report_version_info_ = true;
cros_settings_->GetBoolean(ash::kReportDeviceVersionInfo,
&report_version_info_);
report_boot_mode_ = true;
cros_settings_->GetBoolean(ash::kReportDeviceBootMode, &report_boot_mode_);
}
void ChildStatusCollector::OnAppActivityReportSubmitted() {
DCHECK(last_report_params_);
if (last_report_params_->anything_reported) {
ash::app_time::AppActivityReportInterface* app_activity_reporting =
ash::app_time::AppActivityReportInterface::Get(profile_);
DCHECK(app_activity_reporting);
app_activity_reporting->AppActivityReportSubmitted(
last_report_params_->generation_time);
}
last_report_params_.reset();
}
void ChildStatusCollector::OnUsageTimeStateChange(
ash::UsageTimeStateNotifier::UsageTimeState state) {
UpdateChildUsageTime();
last_state_active_ =
state == ash::UsageTimeStateNotifier::UsageTimeState::ACTIVE;
}
void ChildStatusCollector::UpdateChildUsageTime() {
base::Time now = clock_->Now();
base::Time reset_time = activity_storage_->GetBeginningOfDay(now);
if (reset_time > now)
reset_time -= base::Days(1);
// Reset screen time if it has not been reset today.
if (reset_time > pref_service_->GetTime(prefs::kLastChildScreenTimeReset)) {
pref_service_->SetTime(prefs::kLastChildScreenTimeReset, now);
pref_service_->SetInteger(prefs::kChildScreenTimeMilliseconds, 0);
pref_service_->CommitPendingWrite();
}
if (!last_active_check_.is_null() && last_state_active_) {
// If it's been too long since the last report, or if the activity is
// negative (which can happen when the clock changes), assume a single
// interval of activity. This is the same strategy used to enterprise users.
base::TimeDelta active_seconds = now - last_active_check_;
if (active_seconds < base::Seconds(0) ||
active_seconds >= (2 * kUpdateChildActiveTimeInterval)) {
activity_storage_->AddActivityPeriod(now - kUpdateChildActiveTimeInterval,
now, now);
} else {
activity_storage_->AddActivityPeriod(last_active_check_, now, now);
}
activity_storage_->PruneActivityPeriods(
now, max_stored_past_activity_interval_,
max_stored_future_activity_interval_);
}
last_active_check_ = now;
}
bool ChildStatusCollector::GetActivityTimes(
em::ChildStatusReportRequest* status) {
UpdateChildUsageTime();
// Signed-in user is reported in child reporting.
auto activity_times = activity_storage_->GetStoredActivityPeriods();
bool anything_reported = false;
for (const auto& activity_period : activity_times) {
// This is correct even when there are leap seconds, because when a leap
// second occurs, two consecutive seconds have the same timestamp.
int64_t end_timestamp =
activity_period.start_timestamp() + base::Time::kMillisecondsPerDay;
em::ScreenTimeSpan* screen_time_span = status->add_screen_time_span();
em::TimePeriod* period = screen_time_span->mutable_time_period();
period->set_start_timestamp(activity_period.start_timestamp());
period->set_end_timestamp(end_timestamp);
screen_time_span->set_active_duration_ms(activity_period.end_timestamp() -
activity_period.start_timestamp());
if (last_reported_end_timestamp_ < end_timestamp) {
last_reported_end_timestamp_ = end_timestamp;
}
anything_reported = true;
}
return anything_reported;
}
bool ChildStatusCollector::GetAppActivity(
em::ChildStatusReportRequest* status) {
ash::app_time::AppActivityReportInterface* app_activity_reporting =
ash::app_time::AppActivityReportInterface::Get(profile_);
DCHECK(app_activity_reporting);
last_report_params_ =
app_activity_reporting->GenerateAppActivityReport(status);
if (last_report_params_->anything_reported) {
size_t size_in_bytes = status->ByteSizeLong();
// Logging report size for debugging purposes. Reports larger than 10,485 KB
// will trigger a hard limit. See CommonJobValidator.java.
base::UmaHistogramMemoryKB(kReportSizeHistogramName, size_in_bytes / 1024);
int64_t last_successful_report_time_int = pref_service_->GetInt64(
prefs::kPerAppTimeLimitsLastSuccessfulReportTime);
if (last_successful_report_time_int > 0) {
base::Time last_successful_report_time =
base::Time::FromDeltaSinceWindowsEpoch(
base::Microseconds(last_successful_report_time_int));
DCHECK_LT(last_successful_report_time,
last_report_params_->generation_time);
base::TimeDelta elapsed_time =
last_report_params_->generation_time - last_successful_report_time;
base::UmaHistogramCounts100000(kTimeSinceLastReportHistogramName,
elapsed_time.InMinutes());
}
}
return last_report_params_->anything_reported;
}
bool ChildStatusCollector::GetVersionInfo(
em::ChildStatusReportRequest* status) {
status->set_os_version(os_version_);
return true;
}
void ChildStatusCollector::GetStatusAsync(StatusCollectorCallback response) {
// Must be on creation thread since some stats are written to in that thread
// and accessing them from another thread would lead to race conditions.
DCHECK(thread_checker_.CalledOnValidThread());
// Some of the data we're collecting is gathered in background threads.
// This object keeps track of the state of each async request.
scoped_refptr<ChildStatusCollectorState> state(
new ChildStatusCollectorState(task_runner_, std::move(response)));
// Gather status data might queue some async queries.
FillChildStatusReportRequest(state);
// If there are no outstanding async queries, the destructor of |state| calls
// |response|. If there are async queries, the queries hold references to
// |state|, so that |state| is only destroyed when the last async query has
// finished.
}
bool ChildStatusCollector::FillUserSpecificFields(
scoped_refptr<ChildStatusCollectorState> state,
em::ChildStatusReportRequest* status) {
// Time zone.
const std::string current_timezone = base::UTF16ToUTF8(
ash::system::TimezoneSettings::GetInstance()->GetCurrentTimezoneID());
status->set_time_zone(current_timezone);
// Android status.
const bool report_android_status =
profile_->GetPrefs()->GetBoolean(prefs::kReportArcStatusEnabled);
if (report_android_status)
GetAndroidStatus(state);
status->set_user_dm_token(GetDMTokenForProfile(profile_));
// At least time zone is always reported.
return true;
}
bool ChildStatusCollector::GetAndroidStatus(
const scoped_refptr<ChildStatusCollectorState>& state) {
return state->FetchAndroidStatus(android_status_fetcher_);
}
void ChildStatusCollector::FillChildStatusReportRequest(
scoped_refptr<ChildStatusCollectorState> state) {
em::ChildStatusReportRequest* status =
state->response_params().child_status.get();
bool anything_reported = false;
anything_reported |= FillUserSpecificFields(state, status);
if (report_version_info_)
anything_reported |= GetVersionInfo(status);
anything_reported |= GetActivityTimes(status);
anything_reported |= GetAppActivity(status);
if (report_boot_mode_) {
std::optional<std::string> boot_mode =
StatusCollector::GetBootMode(statistics_provider_);
if (boot_mode) {
status->set_boot_mode(*boot_mode);
anything_reported = true;
}
}
// Wipe value if we didn't actually add any data.
if (!anything_reported)
state->response_params().child_status.reset();
}
void ChildStatusCollector::OnSubmittedSuccessfully() {
activity_storage_->TrimActivityPeriods(last_reported_end_timestamp_,
std::numeric_limits<int64_t>::max());
OnAppActivityReportSubmitted();
}
bool ChildStatusCollector::IsReportingActivityTimes() const {
return true;
}
bool ChildStatusCollector::IsReportingHardwareData() const {
return false;
}
bool ChildStatusCollector::IsReportingNetworkData() const {
return false;
}
bool ChildStatusCollector::IsReportingUsers() const {
return false;
}
bool ChildStatusCollector::IsReportingCrashReportInfo() const {
return false;
}
bool ChildStatusCollector::IsReportingAppInfoAndActivity() const {
return false;
}
// TODO(crbug.com/40239081)
// Make this function fallible when the optional passed in evaluated to
// nullptr, instead of returning a dummy string.
void ChildStatusCollector::OnOSVersion(
const std::optional<std::string>& version) {
os_version_ = version.value_or("0.0.0.0");
}
} // namespace policy