blob: 3f60942804d4b0cbafbd359b92eea96a8cc6b1f8 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/system/machine_learning/user_settings_event_logger.h"
#include "ash/app_list/app_list_controller_impl.h"
#include "ash/display/screen_orientation_controller.h"
#include "ash/public/cpp/app_list/app_list_client.h"
#include "ash/shell.h"
#include "ash/system/night_light/night_light_controller_impl.h"
#include "ash/system/power/power_status.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "base/check_op.h"
#include "base/metrics/histogram_macros.h"
#include "base/time/default_clock.h"
#include "base/time/time.h"
#include "chromeos/dbus/power_manager/backlight.pb.h"
#include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
namespace ash {
namespace ml {
using chromeos::network_config::mojom::NetworkStateProperties;
using chromeos::network_config::mojom::NetworkType;
using chromeos::network_config::mojom::SecurityType;
// static
UserSettingsEventLogger* UserSettingsEventLogger::instance_ = nullptr;
// static
void UserSettingsEventLogger::CreateInstance() {
DCHECK(!instance_);
instance_ = new UserSettingsEventLogger();
}
// static
void UserSettingsEventLogger::DeleteInstance() {
delete instance_;
instance_ = nullptr;
}
// static
UserSettingsEventLogger* UserSettingsEventLogger::Get() {
return instance_;
}
UserSettingsEventLogger::UserSettingsEventLogger()
: presenting_session_count_(0),
is_recently_presenting_(false),
is_recently_fullscreen_(false),
used_cellular_in_session_(false),
is_playing_audio_(false),
is_playing_video_(false),
clock_(base::DefaultClock::GetInstance()) {
chromeos::CrasAudioHandler* audio_handler = chromeos::CrasAudioHandler::Get();
DCHECK(audio_handler);
audio_handler->AddAudioObserver(this);
chromeos::PowerManagerClient::Get()->AddObserver(this);
Shell::Get()->AddShellObserver(this);
Shell::Get()->video_detector()->AddObserver(this);
volume_ = audio_handler->GetOutputVolumePercent();
}
UserSettingsEventLogger::~UserSettingsEventLogger() {
chromeos::CrasAudioHandler::Get()->RemoveAudioObserver(this);
chromeos::PowerManagerClient::Get()->RemoveObserver(this);
Shell::Get()->RemoveShellObserver(this);
Shell::Get()->video_detector()->RemoveObserver(this);
}
void UserSettingsEventLogger::LogNetworkUkmEvent(
const NetworkStateProperties& network) {
UserSettingsEvent settings_event;
auto* const event = settings_event.mutable_event();
auto* const features = settings_event.mutable_features();
if (network.type == NetworkType::kWiFi) {
event->set_setting_id(UserSettingsEvent::Event::WIFI);
const auto& wifi_state = network.type_state->get_wifi();
features->set_signal_strength(wifi_state->signal_strength);
features->set_has_wifi_security(wifi_state->security !=
SecurityType::kNone);
} else if (network.type == NetworkType::kCellular) {
event->set_setting_id(UserSettingsEvent::Event::CELLULAR);
features->set_signal_strength(
network.type_state->get_cellular()->signal_strength);
features->set_used_cellular_in_session(used_cellular_in_session_);
used_cellular_in_session_ = true;
} else {
// We are not interested in other types of networks.
return;
}
event->set_setting_type(UserSettingsEvent::Event::QUICK_SETTINGS);
PopulateSharedFeatures(&settings_event);
SendToUkmAndAppList(settings_event);
}
void UserSettingsEventLogger::LogBluetoothUkmEvent(
const BluetoothAddress& device_address) {
UserSettingsEvent settings_event;
auto* const event = settings_event.mutable_event();
event->set_setting_id(UserSettingsEvent::Event::BLUETOOTH);
event->set_setting_type(UserSettingsEvent::Event::QUICK_SETTINGS);
const auto& devices =
Shell::Get()->tray_bluetooth_helper()->GetAvailableBluetoothDevices();
UMA_HISTOGRAM_COUNTS_100("Ash.Shelf.UkmLogger.NumAvailableBluetoothDevices",
devices.size());
for (const auto& device : devices) {
if (device->address == device_address) {
settings_event.mutable_features()->set_is_paired_bluetooth_device(
device->is_paired);
break;
}
}
PopulateSharedFeatures(&settings_event);
SendToUkmAndAppList(settings_event);
}
void UserSettingsEventLogger::LogNightLightUkmEvent(const bool enabled) {
UserSettingsEvent settings_event;
auto* const event = settings_event.mutable_event();
auto* const features = settings_event.mutable_features();
event->set_setting_id(UserSettingsEvent::Event::NIGHT_LIGHT);
event->set_setting_type(UserSettingsEvent::Event::QUICK_SETTINGS);
// Convert the setting state to an int. Some settings have multiple states, so
// all setting states are stored as ints.
event->set_previous_value(!enabled ? 1 : 0);
event->set_current_value(enabled ? 1 : 0);
const auto* night_light_controller = Shell::Get()->night_light_controller();
const auto schedule_type = night_light_controller->GetScheduleType();
const bool has_night_light_schedule =
schedule_type != NightLightController::ScheduleType::kNone;
UMA_HISTOGRAM_BOOLEAN("Ash.Shelf.UkmLogger.HasNightLightSchedule",
has_night_light_schedule);
features->set_has_night_light_schedule(has_night_light_schedule);
features->set_is_after_sunset(
night_light_controller->IsNowWithinSunsetSunrise());
PopulateSharedFeatures(&settings_event);
SendToUkmAndAppList(settings_event);
}
void UserSettingsEventLogger::LogQuietModeUkmEvent(const bool enabled) {
UserSettingsEvent settings_event;
auto* const event = settings_event.mutable_event();
event->set_setting_id(UserSettingsEvent::Event::DO_NOT_DISTURB);
event->set_setting_type(UserSettingsEvent::Event::QUICK_SETTINGS);
// Convert the setting state to an int. Some settings have multiple states, so
// all setting states are stored as ints.
event->set_previous_value(!enabled ? 1 : 0);
event->set_current_value(enabled ? 1 : 0);
settings_event.mutable_features()->set_is_recently_presenting(
is_recently_presenting_);
PopulateSharedFeatures(&settings_event);
SendToUkmAndAppList(settings_event);
}
void UserSettingsEventLogger::LogAccessibilityUkmEvent(
UserSettingsEvent::Event::AccessibilityId id,
bool enabled) {
UserSettingsEvent settings_event;
auto* const event = settings_event.mutable_event();
event->set_setting_id(UserSettingsEvent::Event::ACCESSIBILITY);
event->set_setting_type(UserSettingsEvent::Event::QUICK_SETTINGS);
// Convert the setting state to an int. Some settings have multiple states, so
// all setting states are stored as ints.
event->set_previous_value(!enabled ? 1 : 0);
event->set_current_value(enabled ? 1 : 0);
event->set_accessibility_id(id);
PopulateSharedFeatures(&settings_event);
SendToUkmAndAppList(settings_event);
}
void UserSettingsEventLogger::OnOutputNodeVolumeChanged(uint64_t /*node*/,
const int volume) {
if (!volume_timer_.IsRunning()) {
volume_before_user_change_ = volume_;
}
volume_ = volume;
volume_before_mute_ = volume;
volume_timer_.Start(FROM_HERE, kSliderDelay, this,
&UserSettingsEventLogger::OnVolumeTimerEnded);
}
void UserSettingsEventLogger::OnOutputMuteChanged(const bool mute_on) {
if (!volume_timer_.IsRunning()) {
volume_before_user_change_ = volume_;
}
volume_ = mute_on ? 0 : volume_before_mute_;
volume_timer_.Start(FROM_HERE, kSliderDelay, this,
&UserSettingsEventLogger::OnVolumeTimerEnded);
}
void UserSettingsEventLogger::OnVolumeTimerEnded() {
UserSettingsEvent settings_event;
auto* const event = settings_event.mutable_event();
event->set_setting_id(UserSettingsEvent::Event::VOLUME);
event->set_setting_type(UserSettingsEvent::Event::QUICK_SETTINGS);
event->set_previous_value(volume_before_user_change_);
event->set_current_value(volume_);
PopulateSharedFeatures(&settings_event);
SendToUkmAndAppList(settings_event);
}
void UserSettingsEventLogger::OnOutputStarted() {
is_playing_audio_ = true;
}
void UserSettingsEventLogger::OnOutputStopped() {
is_playing_audio_ = false;
}
void UserSettingsEventLogger::ScreenBrightnessChanged(
const power_manager::BacklightBrightnessChange& change) {
const int new_brightness = std::floor(change.percent());
if (change.cause() ==
power_manager::BacklightBrightnessChange_Cause_USER_REQUEST) {
if (!brightness_timer_.IsRunning()) {
brightness_before_user_change_ = brightness_;
}
brightness_after_user_change_ = new_brightness;
// Keep starting the timer until there is a pause in brightness activity.
// Then only one event will be logged to summarise that activity.
brightness_timer_.Start(FROM_HERE, kSliderDelay, this,
&UserSettingsEventLogger::OnBrightnessTimerEnded);
}
brightness_ = new_brightness;
}
void UserSettingsEventLogger::OnBrightnessTimerEnded() {
UserSettingsEvent settings_event;
auto* const event = settings_event.mutable_event();
event->set_setting_id(UserSettingsEvent::Event::BRIGHTNESS);
event->set_setting_type(UserSettingsEvent::Event::QUICK_SETTINGS);
if (brightness_before_user_change_.has_value()) {
event->set_previous_value(brightness_before_user_change_.value());
}
event->set_current_value(brightness_after_user_change_);
settings_event.mutable_features()->set_is_recently_fullscreen(
is_recently_fullscreen_);
PopulateSharedFeatures(&settings_event);
SendToUkmAndAppList(settings_event);
}
void UserSettingsEventLogger::OnCastingSessionStartedOrStopped(
const bool started) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (started) {
++presenting_session_count_;
is_recently_presenting_ = true;
presenting_timer_.Stop();
} else {
--presenting_session_count_;
DCHECK_GE(presenting_session_count_, 0);
if (presenting_session_count_ == 0) {
presenting_timer_.Start(FROM_HERE, base::TimeDelta::FromMinutes(5), this,
&UserSettingsEventLogger::OnPresentingTimerEnded);
}
}
}
void UserSettingsEventLogger::OnPresentingTimerEnded() {
is_recently_presenting_ = false;
}
void UserSettingsEventLogger::OnFullscreenStateChanged(
const bool is_fullscreen,
aura::Window* /*container*/) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (is_fullscreen) {
is_recently_fullscreen_ = true;
fullscreen_timer_.Stop();
} else {
fullscreen_timer_.Start(FROM_HERE, base::TimeDelta::FromMinutes(5), this,
&UserSettingsEventLogger::OnFullscreenTimerEnded);
}
}
void UserSettingsEventLogger::OnFullscreenTimerEnded() {
is_recently_fullscreen_ = false;
}
void UserSettingsEventLogger::OnVideoStateChanged(
const VideoDetector::State state) {
is_playing_video_ = (state != VideoDetector::State::NOT_PLAYING);
}
void UserSettingsEventLogger::SetClockForTesting(const base::Clock* clock) {
clock_ = clock;
}
void UserSettingsEventLogger::PopulateSharedFeatures(
UserSettingsEvent* settings_event) {
auto* features = settings_event->mutable_features();
// Set time features.
base::Time::Exploded now;
clock_->Now().LocalExplode(&now);
features->set_hour_of_day(now.hour);
features->set_day_of_week(
static_cast<UserSettingsEvent::Features::DayOfWeek>(now.day_of_week));
// Set power features.
if (PowerStatus::IsInitialized()) {
const auto* power_status = PowerStatus::Get();
features->set_battery_percentage(power_status->GetRoundedBatteryPercent());
features->set_is_charging(power_status->IsLinePowerConnected() ||
power_status->IsMainsChargerConnected() ||
power_status->IsUsbChargerConnected());
}
// Set activity features.
features->set_is_playing_audio(is_playing_audio_);
features->set_is_playing_video(is_playing_video_);
// Set orientation features.
features->set_device_mode(
Shell::Get()->tablet_mode_controller()->InTabletMode()
? UserSettingsEvent::Features::TABLET_MODE
: UserSettingsEvent::Features::CLAMSHELL_MODE);
const auto orientation =
Shell::Get()->screen_orientation_controller()->GetCurrentOrientation();
if (IsLandscapeOrientation(orientation)) {
features->set_device_orientation(UserSettingsEvent::Features::LANDSCAPE);
} else if (IsPortraitOrientation(orientation)) {
features->set_device_orientation(UserSettingsEvent::Features::PORTRAIT);
}
}
void UserSettingsEventLogger::SendToUkmAndAppList(
const UserSettingsEvent& settings_event) {
const ukm::SourceId source_id = ukm::UkmRecorder::GetNewSourceID();
ukm::builders::UserSettingsEvent ukm_event(source_id);
const UserSettingsEvent::Event& event = settings_event.event();
const UserSettingsEvent::Features& features = settings_event.features();
if (event.has_event_id())
ukm_event.SetEventId(event.event_id());
if (event.has_setting_id())
ukm_event.SetSettingId(event.setting_id());
if (event.has_setting_type())
ukm_event.SetSettingType(event.setting_type());
if (event.has_previous_value())
ukm_event.SetPreviousValue(event.previous_value());
if (event.has_current_value())
ukm_event.SetCurrentValue(event.current_value());
if (event.has_accessibility_id())
ukm_event.SetAccessibilityId(event.accessibility_id());
if (features.has_hour_of_day())
ukm_event.SetHourOfDay(features.hour_of_day());
if (features.has_day_of_week())
ukm_event.SetDayOfWeek(features.day_of_week());
if (features.has_battery_percentage())
ukm_event.SetBatteryPercentage(features.battery_percentage());
if (features.has_is_charging())
ukm_event.SetIsCharging(features.is_charging());
if (features.has_is_playing_audio())
ukm_event.SetIsPlayingAudio(features.is_playing_audio());
if (features.has_is_playing_video())
ukm_event.SetIsPlayingVideo(features.is_playing_video());
if (features.has_device_mode())
ukm_event.SetDeviceMode(features.device_mode());
if (features.has_device_orientation())
ukm_event.SetDeviceOrientation(features.device_orientation());
if (features.has_is_recently_presenting())
ukm_event.SetIsRecentlyPresenting(features.is_recently_presenting());
if (features.has_is_recently_fullscreen())
ukm_event.SetIsRecentlyFullscreen(features.is_recently_fullscreen());
if (features.has_signal_strength())
ukm_event.SetSignalStrength(features.signal_strength());
if (features.has_has_wifi_security())
ukm_event.SetHasWifiSecurity(features.has_wifi_security());
if (features.has_used_cellular_in_session())
ukm_event.SetUsedCellularInSession(features.used_cellular_in_session());
if (features.has_is_paired_bluetooth_device())
ukm_event.SetIsPairedBluetoothDevice(features.is_paired_bluetooth_device());
if (features.has_has_night_light_schedule())
ukm_event.SetHasNightLightSchedule(features.has_night_light_schedule());
if (features.has_is_after_sunset())
ukm_event.SetIsAfterSunset(features.is_after_sunset());
ukm::UkmRecorder* const ukm_recorder = ukm::UkmRecorder::Get();
ukm_event.Record(ukm_recorder);
// Also log in browser side for other usage (CrOSActionRecorder for now).
AppListClient* app_list_client =
Shell::Get()->app_list_controller()->GetClient();
if (app_list_client) {
const std::string setting_name =
UserSettingsEvent_Event_SettingId_Name(event.setting_id());
app_list_client->OnQuickSettingsChanged(
setting_name, {{"SettingType", static_cast<int>(event.setting_type())},
{"PreviousValue", event.previous_value()},
{"CurrentValue", event.current_value()},
{"SettingId", static_cast<int>(event.setting_id())}});
}
}
} // namespace ml
} // namespace ash