// Copyright 2020 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/metrics/family_user_metrics_provider.h"
#include "base/check.h"
#include "base/metrics/histogram_functions.h"
#include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
#include "chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "components/session_manager/core/session_manager.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
namespace {
constexpr char kFamilyUserLogSegmentHistogramName[] =
"ChromeOS.FamilyUser.LogSegment2";
constexpr char kNumSecondaryAccountsHistogramName[] =
"ChromeOS.FamilyUser.NumSecondaryAccounts";
// Returns managed user log segment for metrics logging.
enterprise_management::PolicyData::MetricsLogSegment GetManagedUserLogSegment(
Profile* profile) {
const policy::UserCloudPolicyManagerAsh* user_cloud_policy_manager =
profile->GetUserCloudPolicyManagerAsh();
if (!user_cloud_policy_manager)
return enterprise_management::PolicyData::UNSPECIFIED;
const enterprise_management::PolicyData* policy =
user_cloud_policy_manager->core()->store()->policy();
if (!policy || !policy->has_metrics_log_segment())
return enterprise_management::PolicyData::UNSPECIFIED;
return policy->metrics_log_segment();
}
// Returns if the device is managed, independent of the user.
bool IsDeviceEnterpriseEnrolled() {
policy::BrowserPolicyConnectorAsh* connector =
g_browser_process->platform_part()->browser_policy_connector_ash();
return connector->IsDeviceEnterpriseManaged();
}
Profile* GetPrimaryUserProfile() {
const user_manager::User* primary_user =
user_manager::UserManager::Get()->GetPrimaryUser();
DCHECK(primary_user);
DCHECK(primary_user->is_profile_created());
Profile* profile = ash::ProfileHelper::Get()->GetProfileByUser(primary_user);
DCHECK(profile);
DCHECK(ash::ProfileHelper::IsUserProfile(profile));
return profile;
}
// Can return -1 for guest users, browser tests, and other edge cases. If -1,
// then no metrics uploaded.
int GetNumSecondaryAccounts(Profile* profile) {
// Check for incognito profiles.
if (profile->IsOffTheRecord())
return -1;
signin::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(profile);
DCHECK(identity_manager);
if (!identity_manager->AreRefreshTokensLoaded()) {
// IdentityManager hasn't finished loading accounts, return -1 to indicate
// that we don't know the number of secondary accounts yet.
return -1;
}
int num_accounts = identity_manager->GetAccountsWithRefreshTokens().size();
return num_accounts - 1;
}
} // namespace
FamilyUserMetricsProvider::FamilyUserMetricsProvider() {
session_manager::SessionManager* session_manager =
session_manager::SessionManager::Get();
// The |session_manager| is nullptr only for unit tests.
if (session_manager)
session_manager->AddObserver(this);
}
FamilyUserMetricsProvider::~FamilyUserMetricsProvider() {
session_manager::SessionManager* session_manager =
session_manager::SessionManager::Get();
// The |session_manager| is nullptr only for unit tests.
if (session_manager)
session_manager->RemoveObserver(this);
}
// This function is called at unpredictable intervals throughout the entire
// ChromeOS session, so guarantee it will never crash.
bool FamilyUserMetricsProvider::ProvideHistograms() {
if (!family_user_log_segment_)
return false;
base::UmaHistogramEnumeration(kFamilyUserLogSegmentHistogramName,
family_user_log_segment_.value());
if (num_secondary_accounts_ >= 0) {
base::UmaHistogramCounts100(kNumSecondaryAccountsHistogramName,
num_secondary_accounts_);
}
return true;
}
void FamilyUserMetricsProvider::OnUserSessionStarted(bool is_primary_user) {
if (!is_primary_user)
return;
Profile* profile = GetPrimaryUserProfile();
ObserveIdentityManager(profile);
num_secondary_accounts_ = GetNumSecondaryAccounts(profile);
if (IsSupervisedUser(profile)) {
family_user_log_segment_ = FamilyUserLogSegment::kSupervisedUser;
} else if (IsSupervisedStudent(profile)) {
family_user_log_segment_ = FamilyUserLogSegment::kSupervisedStudent;
} else if (!IsDeviceEnterpriseEnrolled() &&
GetManagedUserLogSegment(profile) ==
enterprise_management::PolicyData::K12) {
DCHECK(profile->GetProfilePolicyConnector()->IsManaged());
// This is a K-12 EDU user on an unmanaged ChromeOS device.
family_user_log_segment_ = FamilyUserLogSegment::kStudentAtHome;
} else if (profile->IsRegularProfile() &&
!profile->GetProfilePolicyConnector()->IsManaged()) {
DCHECK(!profile->IsChild());
DCHECK_EQ(GetManagedUserLogSegment(profile),
enterprise_management::PolicyData::UNSPECIFIED);
// This is a regular unmanaged user on any device.
family_user_log_segment_ = FamilyUserLogSegment::kRegularUser;
} else {
family_user_log_segment_ = FamilyUserLogSegment::kOther;
}
}
// Called when the user adds a secondary account. We're only interested in
// detecting when a supervised user adds an EDU secondary account.
void FamilyUserMetricsProvider::OnRefreshTokensLoaded() {
Profile* profile = GetPrimaryUserProfile();
num_secondary_accounts_ = GetNumSecondaryAccounts(profile);
// If a supervised user has a secondary account, then the secondary account
// must be EDU.
if (IsSupervisedStudent(profile))
family_user_log_segment_ = FamilyUserLogSegment::kSupervisedStudent;
}
void FamilyUserMetricsProvider::OnRefreshTokenUpdatedForAccount(
const CoreAccountInfo& account_info) {
// Call OnRefreshTokensLoaded to update `num_secondary_accounts_` and
// `family_user_log_segment_`.
OnRefreshTokensLoaded();
}
// Called when the user removes a secondary account. We're interested in
// detecting when a supervised user removes an EDU secondary account.
void FamilyUserMetricsProvider::OnRefreshTokenRemovedForAccount(
const CoreAccountId& account_id) {
// Call OnRefreshTokensLoaded to update `num_secondary_accounts_` and
// `family_user_log_segment_`.
OnRefreshTokensLoaded();
}
// static
const char*
FamilyUserMetricsProvider::GetFamilyUserLogSegmentHistogramNameForTesting() {
return kFamilyUserLogSegmentHistogramName;
}
const char*
FamilyUserMetricsProvider::GetNumSecondaryAccountsHistogramNameForTesting() {
return kNumSecondaryAccountsHistogramName;
}
void FamilyUserMetricsProvider::ObserveIdentityManager(Profile* profile) {
// Check for incognito profiles.
if (profile->IsOffTheRecord())
return;
signin::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(profile);
DCHECK(identity_manager);
if (!identity_manager_observations_.IsObservingSource(identity_manager))
identity_manager_observations_.AddObservation(identity_manager);
}
bool FamilyUserMetricsProvider::IsSupervisedUser(Profile* profile) {
if (!profile->IsChild())
return false;
return num_secondary_accounts_ == 0;
}
bool FamilyUserMetricsProvider::IsSupervisedStudent(Profile* profile) {
if (!profile->IsChild())
return false;
// If a supervised user has a secondary account, then the secondary
// account must be EDU.
return num_secondary_accounts_ > 0;
}