| // Copyright 2011 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/profiles/profile_metrics.h" |
| |
| #include <string> |
| #include <vector> |
| |
| #include "base/check_op.h" |
| #include "base/containers/flat_map.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/notreached.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_attributes_entry.h" |
| #include "chrome/browser/profiles/profile_attributes_storage.h" |
| #include "chrome/common/chrome_constants.h" |
| #include "components/profile_metrics/counts.h" |
| |
| namespace { |
| |
| // Enum for getting net counts for adding and deleting users. |
| enum class ProfileNetUserCounts { |
| ADD_NEW_USER = 0, // Total count of add new user |
| PROFILE_DELETED, // User deleted a profile |
| kMaxValue = PROFILE_DELETED |
| }; |
| |
| // Count of profiles sharing a particular name. |
| struct ProfileCountByName { |
| explicit ProfileCountByName(bool is_managed) |
| : managed_count(is_managed ? 1 : 0), |
| non_managed_count(is_managed ? 0 : 1) {} |
| |
| ProfileCountByName(const ProfileCountByName&) = default; |
| ProfileCountByName& operator=(const ProfileCountByName&) = default; |
| |
| int managed_count = 0; |
| int non_managed_count = 0; |
| }; |
| |
| base::flat_map<std::u16string, ProfileCountByName> GetProfilesByGaiaName( |
| const ProfileAttributesStorage& storage) { |
| base::flat_map<std::u16string, ProfileCountByName> profile_counts_by_name; |
| for (const auto* entry : storage.GetAllProfilesAttributes()) { |
| const std::u16string gaia_name = entry->GetGAIAGivenName(); |
| if (gaia_name.empty()) { |
| continue; |
| } |
| |
| const bool is_managed = entry->UserAcceptedAccountManagement(); |
| auto it = profile_counts_by_name.find(gaia_name); |
| if (it == profile_counts_by_name.end()) { |
| profile_counts_by_name.emplace(gaia_name, is_managed); |
| continue; |
| } |
| |
| ProfileCountByName& count = it->second; |
| if (is_managed) { |
| ++count.managed_count; |
| } else { |
| ++count.non_managed_count; |
| } |
| } |
| return profile_counts_by_name; |
| } |
| |
| // Count and return summary information about the profiles currently in the |
| // `storage`. |
| profile_metrics::Counts CountProfileInformation( |
| ProfileAttributesStorage* storage, |
| profile_metrics::ProfileActivityThreshold activity_threshold) { |
| profile_metrics::Counts counts; |
| |
| size_t number_of_profiles = storage->GetNumberOfProfiles(); |
| counts.total = number_of_profiles; |
| // Ignore other metrics if we have no profiles. |
| if (!number_of_profiles) { |
| return counts; |
| } |
| |
| std::vector<ProfileAttributesEntry*> entries = |
| storage->GetAllProfilesAttributes(); |
| for (ProfileAttributesEntry* entry : entries) { |
| if (!ProfileMetrics::IsProfileActive(entry, activity_threshold)) { |
| counts.unused++; |
| } else { |
| counts.active++; |
| if (entry->IsSupervised()) { |
| counts.supervised++; |
| } |
| if (entry->IsAuthenticated()) { |
| counts.signedin++; |
| } |
| } |
| } |
| |
| return counts; |
| } |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| base::TimeDelta GetActivityThresholdDelta( |
| profile_metrics::ProfileActivityThreshold activity_threshold) { |
| switch (activity_threshold) { |
| case profile_metrics::ProfileActivityThreshold::kDuration1Day: |
| return base::Days(1); |
| case profile_metrics::ProfileActivityThreshold::kDuration7Days: |
| return base::Days(7); |
| case profile_metrics::ProfileActivityThreshold::kDuration28Days: |
| return base::Days(28); |
| } |
| } |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| } // namespace |
| |
| // This enum is used for histograms. Do not change existing values. Append new |
| // values at the end. |
| enum ProfileAvatar { |
| AVATAR_GENERIC = 0, // The names for avatar icons |
| AVATAR_GENERIC_AQUA = 1, |
| AVATAR_GENERIC_BLUE = 2, |
| AVATAR_GENERIC_GREEN = 3, |
| AVATAR_GENERIC_ORANGE = 4, |
| AVATAR_GENERIC_PURPLE = 5, |
| AVATAR_GENERIC_RED = 6, |
| AVATAR_GENERIC_YELLOW = 7, |
| AVATAR_SECRET_AGENT = 8, |
| AVATAR_SUPERHERO = 9, |
| AVATAR_VOLLEYBALL = 10, |
| AVATAR_BUSINESSMAN = 11, |
| AVATAR_NINJA = 12, |
| AVATAR_ALIEN = 13, |
| AVATAR_AWESOME = 14, |
| AVATAR_FLOWER = 15, |
| AVATAR_PIZZA = 16, |
| AVATAR_SOCCER = 17, |
| AVATAR_BURGER = 18, |
| AVATAR_CAT = 19, |
| AVATAR_CUPCAKE = 20, |
| AVATAR_DOG = 21, |
| AVATAR_HORSE = 22, |
| AVATAR_MARGARITA = 23, |
| AVATAR_NOTE = 24, |
| AVATAR_SUN_CLOUD = 25, |
| AVATAR_PLACEHOLDER = 26, |
| AVATAR_UNKNOWN = 27, |
| AVATAR_GAIA = 28, |
| // Modern avatars: |
| AVATAR_ORIGAMI_CAT = 29, |
| AVATAR_ORIGAMI_CORGI = 30, |
| AVATAR_ORIGAMI_DRAGON = 31, |
| AVATAR_ORIGAMI_ELEPHANT = 32, |
| AVATAR_ORIGAMI_FOX = 33, |
| AVATAR_ORIGAMI_MONKEY = 34, |
| AVATAR_ORIGAMI_PANDA = 35, |
| AVATAR_ORIGAMI_PENGUIN = 36, |
| AVATAR_ORIGAMI_PINKBUTTERFLY = 37, |
| AVATAR_ORIGAMI_RABBIT = 38, |
| AVATAR_ORIGAMI_UNICORN = 39, |
| AVATAR_ILLUSTRATION_BASKETBALL = 40, |
| AVATAR_ILLUSTRATION_BIKE = 41, |
| AVATAR_ILLUSTRATION_BIRD = 42, |
| AVATAR_ILLUSTRATION_CHEESE = 43, |
| AVATAR_ILLUSTRATION_FOOTBALL = 44, |
| AVATAR_ILLUSTRATION_RAMEN = 45, |
| AVATAR_ILLUSTRATION_SUNGLASSES = 46, |
| AVATAR_ILLUSTRATION_SUSHI = 47, |
| AVATAR_ILLUSTRATION_TAMAGOTCHI = 48, |
| AVATAR_ILLUSTRATION_VINYL = 49, |
| AVATAR_ABSTRACT_AVOCADO = 50, |
| AVATAR_ABSTRACT_CAPPUCCINO = 51, |
| AVATAR_ABSTRACT_ICECREAM = 52, |
| AVATAR_ABSTRACT_ICEWATER = 53, |
| AVATAR_ABSTRACT_MELON = 54, |
| AVATAR_ABSTRACT_ONIGIRI = 55, |
| AVATAR_ABSTRACT_PIZZA = 56, |
| AVATAR_ABSTRACT_SANDWICH = 57, |
| NUM_PROFILE_AVATAR_METRICS |
| }; |
| |
| // static |
| bool ProfileMetrics::IsProfileActive( |
| const ProfileAttributesEntry* entry, |
| profile_metrics::ProfileActivityThreshold activity_threshold) { |
| #if !BUILDFLAG(IS_ANDROID) |
| // TODO(mlerman): iOS and Android should set an ActiveTime in the |
| // ProfileAttributesStorage. (see ProfileManager::OnBrowserSetLastActive) |
| if (base::Time::Now() - entry->GetActiveTime() > |
| GetActivityThresholdDelta(activity_threshold)) { |
| return false; |
| } |
| #endif |
| return true; |
| } |
| |
| void ProfileMetrics::LogNumberOfProfiles(ProfileAttributesStorage* storage) { |
| CHECK(storage); |
| profile_metrics::LogTotalNumberOfProfiles(storage->GetNumberOfProfiles()); |
| |
| for (profile_metrics::ProfileActivityThreshold activity_threshold : |
| {profile_metrics::ProfileActivityThreshold::kDuration1Day, |
| profile_metrics::ProfileActivityThreshold::kDuration7Days, |
| profile_metrics::ProfileActivityThreshold::kDuration28Days}) { |
| profile_metrics::Counts counts = |
| CountProfileInformation(storage, activity_threshold); |
| profile_metrics::LogProfileMetricsCounts(counts, activity_threshold); |
| } |
| |
| // Records whether some profiles have primary accounts with the same first |
| // name. |
| base::flat_map<std::u16string, ProfileCountByName> profile_counts_by_name = |
| GetProfilesByGaiaName(*storage); |
| for (const auto& [name, count] : profile_counts_by_name) { |
| GaiaNameShareStatus name_shared = GaiaNameShareStatus::kNotShared; |
| if (count.non_managed_count > 1) { |
| name_shared = GaiaNameShareStatus::kSharedNonManaged; |
| } else if (count.non_managed_count + count.managed_count > 1) { |
| name_shared = GaiaNameShareStatus::kSharedManaged; |
| } |
| base::UmaHistogramEnumeration("Profile.GaiaNameShareStatus", name_shared); |
| } |
| } |
| |
| void ProfileMetrics::LogProfileAddNewUser(ProfileAdd metric) { |
| base::UmaHistogramEnumeration("Profile.AddNewUser", metric); |
| base::UmaHistogramEnumeration("Profile.NetUserCount", |
| ProfileNetUserCounts::ADD_NEW_USER); |
| } |
| |
| // static |
| void ProfileMetrics::LogProfileAddSignInFlowOutcome( |
| ProfileSignedInFlowOutcome outcome) { |
| base::UmaHistogramEnumeration("Profile.AddSignInFlowOutcome", outcome); |
| } |
| |
| void ProfileMetrics::LogProfileAvatarSelection(size_t icon_index) { |
| ProfileAvatar icon_name = AVATAR_UNKNOWN; |
| switch (icon_index) { |
| case 0: |
| icon_name = AVATAR_GENERIC; |
| break; |
| case 1: |
| icon_name = AVATAR_GENERIC_AQUA; |
| break; |
| case 2: |
| icon_name = AVATAR_GENERIC_BLUE; |
| break; |
| case 3: |
| icon_name = AVATAR_GENERIC_GREEN; |
| break; |
| case 4: |
| icon_name = AVATAR_GENERIC_ORANGE; |
| break; |
| case 5: |
| icon_name = AVATAR_GENERIC_PURPLE; |
| break; |
| case 6: |
| icon_name = AVATAR_GENERIC_RED; |
| break; |
| case 7: |
| icon_name = AVATAR_GENERIC_YELLOW; |
| break; |
| case 8: |
| icon_name = AVATAR_SECRET_AGENT; |
| break; |
| case 9: |
| icon_name = AVATAR_SUPERHERO; |
| break; |
| case 10: |
| icon_name = AVATAR_VOLLEYBALL; |
| break; |
| case 11: |
| icon_name = AVATAR_BUSINESSMAN; |
| break; |
| case 12: |
| icon_name = AVATAR_NINJA; |
| break; |
| case 13: |
| icon_name = AVATAR_ALIEN; |
| break; |
| case 14: |
| icon_name = AVATAR_AWESOME; |
| break; |
| case 15: |
| icon_name = AVATAR_FLOWER; |
| break; |
| case 16: |
| icon_name = AVATAR_PIZZA; |
| break; |
| case 17: |
| icon_name = AVATAR_SOCCER; |
| break; |
| case 18: |
| icon_name = AVATAR_BURGER; |
| break; |
| case 19: |
| icon_name = AVATAR_CAT; |
| break; |
| case 20: |
| icon_name = AVATAR_CUPCAKE; |
| break; |
| case 21: |
| icon_name = AVATAR_DOG; |
| break; |
| case 22: |
| icon_name = AVATAR_HORSE; |
| break; |
| case 23: |
| icon_name = AVATAR_MARGARITA; |
| break; |
| case 24: |
| icon_name = AVATAR_NOTE; |
| break; |
| case 25: |
| icon_name = AVATAR_SUN_CLOUD; |
| break; |
| case 26: |
| icon_name = AVATAR_PLACEHOLDER; |
| break; |
| // Modern avatars: |
| case 27: |
| icon_name = AVATAR_ORIGAMI_CAT; |
| break; |
| case 28: |
| icon_name = AVATAR_ORIGAMI_CORGI; |
| break; |
| case 29: |
| icon_name = AVATAR_ORIGAMI_DRAGON; |
| break; |
| case 30: |
| icon_name = AVATAR_ORIGAMI_ELEPHANT; |
| break; |
| case 31: |
| icon_name = AVATAR_ORIGAMI_FOX; |
| break; |
| case 32: |
| icon_name = AVATAR_ORIGAMI_MONKEY; |
| break; |
| case 33: |
| icon_name = AVATAR_ORIGAMI_PANDA; |
| break; |
| case 34: |
| icon_name = AVATAR_ORIGAMI_PENGUIN; |
| break; |
| case 35: |
| icon_name = AVATAR_ORIGAMI_PINKBUTTERFLY; |
| break; |
| case 36: |
| icon_name = AVATAR_ORIGAMI_RABBIT; |
| break; |
| case 37: |
| icon_name = AVATAR_ORIGAMI_UNICORN; |
| break; |
| case 38: |
| icon_name = AVATAR_ILLUSTRATION_BASKETBALL; |
| break; |
| case 39: |
| icon_name = AVATAR_ILLUSTRATION_BIKE; |
| break; |
| case 40: |
| icon_name = AVATAR_ILLUSTRATION_BIRD; |
| break; |
| case 41: |
| icon_name = AVATAR_ILLUSTRATION_CHEESE; |
| break; |
| case 42: |
| icon_name = AVATAR_ILLUSTRATION_FOOTBALL; |
| break; |
| case 43: |
| icon_name = AVATAR_ILLUSTRATION_RAMEN; |
| break; |
| case 44: |
| icon_name = AVATAR_ILLUSTRATION_SUNGLASSES; |
| break; |
| case 45: |
| icon_name = AVATAR_ILLUSTRATION_SUSHI; |
| break; |
| case 46: |
| icon_name = AVATAR_ILLUSTRATION_TAMAGOTCHI; |
| break; |
| case 47: |
| icon_name = AVATAR_ILLUSTRATION_VINYL; |
| break; |
| case 48: |
| icon_name = AVATAR_ABSTRACT_AVOCADO; |
| break; |
| case 49: |
| icon_name = AVATAR_ABSTRACT_CAPPUCCINO; |
| break; |
| case 50: |
| icon_name = AVATAR_ABSTRACT_ICECREAM; |
| break; |
| case 51: |
| icon_name = AVATAR_ABSTRACT_ICEWATER; |
| break; |
| case 52: |
| icon_name = AVATAR_ABSTRACT_MELON; |
| break; |
| case 53: |
| icon_name = AVATAR_ABSTRACT_ONIGIRI; |
| break; |
| case 54: |
| icon_name = AVATAR_ABSTRACT_PIZZA; |
| break; |
| case 55: |
| icon_name = AVATAR_ABSTRACT_SANDWICH; |
| break; |
| case SIZE_MAX: |
| icon_name = AVATAR_GAIA; |
| break; |
| default: |
| NOTREACHED(); |
| } |
| base::UmaHistogramEnumeration("Profile.Avatar", icon_name, |
| NUM_PROFILE_AVATAR_METRICS); |
| } |
| |
| void ProfileMetrics::LogProfileDeleteUser(ProfileDelete metric) { |
| DCHECK(metric < NUM_DELETE_PROFILE_METRICS); |
| base::UmaHistogramEnumeration("Profile.DeleteProfileAction", metric, |
| NUM_DELETE_PROFILE_METRICS); |
| if (metric != DELETE_PROFILE_USER_MANAGER_SHOW_WARNING && |
| metric != DELETE_PROFILE_SETTINGS_SHOW_WARNING && |
| metric != DELETE_PROFILE_ABORTED) { |
| // If a user was actually deleted, update the net user count. |
| base::UmaHistogramEnumeration("Profile.NetUserCount", |
| ProfileNetUserCounts::PROFILE_DELETED); |
| } |
| } |
| |
| void ProfileMetrics::LogProfileLaunch(Profile* profile) { |
| if (profile->IsChild()) { |
| base::RecordAction( |
| base::UserMetricsAction("ManagedMode_NewManagedUserWindow")); |
| } |
| } |