blob: c68f78b3202ceadb664888846c4f75b6dd430b22 [file] [log] [blame]
// Copyright 2017 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 "chrome/browser/chromeos/power/ml/idle_event_notifier.h"
#include "base/check.h"
#include "base/notreached.h"
#include "chrome/browser/chromeos/power/ml/recent_events_counter.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/power_manager/idle.pb.h"
#include "chromeos/dbus/power_manager/suspend.pb.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
namespace chromeos {
namespace power {
namespace ml {
using TimeSinceBoot = base::TimeDelta;
// static
constexpr base::TimeDelta IdleEventNotifier::kIdleDelay;
constexpr base::TimeDelta IdleEventNotifier::kUserInputEventsDuration;
constexpr int IdleEventNotifier::kNumUserInputEventsBuckets;
struct IdleEventNotifier::ActivityDataInternal {
// Use base::Time here because we later need to convert them to local time
// since midnight.
base::Time last_activity_time;
// Zero if there's no user activity before idle event.
base::Time last_user_activity_time;
TimeSinceBoot last_activity_since_boot;
base::Optional<TimeSinceBoot> earliest_activity_since_boot;
base::Optional<TimeSinceBoot> last_key_since_boot;
base::Optional<TimeSinceBoot> last_mouse_since_boot;
base::Optional<TimeSinceBoot> last_touch_since_boot;
base::Optional<TimeSinceBoot> video_start_time;
base::Optional<TimeSinceBoot> video_end_time;
};
IdleEventNotifier::ActivityData::ActivityData() {}
IdleEventNotifier::ActivityData::ActivityData(const ActivityData& input_data) {
last_activity_day = input_data.last_activity_day;
last_activity_time_of_day = input_data.last_activity_time_of_day;
last_user_activity_time_of_day = input_data.last_user_activity_time_of_day;
recent_time_active = input_data.recent_time_active;
time_since_last_key = input_data.time_since_last_key;
time_since_last_mouse = input_data.time_since_last_mouse;
time_since_last_touch = input_data.time_since_last_touch;
video_playing_time = input_data.video_playing_time;
time_since_video_ended = input_data.time_since_video_ended;
key_events_in_last_hour = input_data.key_events_in_last_hour;
mouse_events_in_last_hour = input_data.mouse_events_in_last_hour;
touch_events_in_last_hour = input_data.touch_events_in_last_hour;
}
IdleEventNotifier::IdleEventNotifier(
PowerManagerClient* power_manager_client,
ui::UserActivityDetector* detector,
mojo::PendingReceiver<viz::mojom::VideoDetectorObserver> receiver)
: power_manager_client_observer_(this),
user_activity_observer_(this),
internal_data_(std::make_unique<ActivityDataInternal>()),
receiver_(this, std::move(receiver)),
key_counter_(
std::make_unique<RecentEventsCounter>(kUserInputEventsDuration,
kNumUserInputEventsBuckets)),
mouse_counter_(
std::make_unique<RecentEventsCounter>(kUserInputEventsDuration,
kNumUserInputEventsBuckets)),
touch_counter_(
std::make_unique<RecentEventsCounter>(kUserInputEventsDuration,
kNumUserInputEventsBuckets)) {
DCHECK(power_manager_client);
power_manager_client_observer_.Add(power_manager_client);
DCHECK(detector);
user_activity_observer_.Add(detector);
}
IdleEventNotifier::~IdleEventNotifier() = default;
void IdleEventNotifier::LidEventReceived(
chromeos::PowerManagerClient::LidState state,
const base::TimeTicks& /* timestamp */) {
// Ignore lid-close event, as we will observe suspend signal.
if (state == chromeos::PowerManagerClient::LidState::OPEN) {
UpdateActivityData(ActivityType::USER_OTHER);
}
}
void IdleEventNotifier::PowerChanged(
const power_manager::PowerSupplyProperties& proto) {
if (external_power_ != proto.external_power()) {
external_power_ = proto.external_power();
UpdateActivityData(ActivityType::USER_OTHER);
}
}
void IdleEventNotifier::SuspendDone(const base::TimeDelta& sleep_duration) {
// SuspendDone is triggered by user opening the lid (or other user
// activities).
// A suspend and subsequent SuspendDone signal could occur with or without a
// preceding screen dim. If ScreenDimImminent is received before suspend,
// ResetTimestampsForRecentActivity would have been called so it wouldn't
// matter whether to reset the timestamps again. If there is no preceding
// ScreenDimImminent, then we need to call ResetTimestampsForRecentActivity
// if the |sleep_duration| is long enough, so that we can reset recent
// activity duration. We consider a |sleep_duration| is long if it is at least
// kIdleDelay.
if (sleep_duration >= kIdleDelay) {
ResetTimestampsForRecentActivity();
if (video_playing_) {
// This could happen when user closes the lid while video is playing.
// If OnVideoActivityEnded is not received before system is suspended, we
// could have |video_playing_| = true. If |sleep_duration| < kIdleDelay,
// we consider video never stopped. Otherwise, we treat it as a new video
// playing session.
internal_data_->video_start_time = boot_clock_.GetTimeSinceBoot();
}
}
UpdateActivityData(ActivityType::USER_OTHER);
}
void IdleEventNotifier::OnUserActivity(const ui::Event* event) {
if (!event)
return;
// Get the type of activity first then reset timer.
ActivityType type = ActivityType::USER_OTHER;
if (event->IsKeyEvent()) {
type = ActivityType::KEY;
} else if (event->IsMouseEvent()) {
type = ActivityType::MOUSE;
} else if (event->IsTouchEvent()) {
type = ActivityType::TOUCH;
}
UpdateActivityData(type);
}
void IdleEventNotifier::OnVideoActivityStarted() {
if (video_playing_) {
NOTREACHED() << "Duplicate start of video activity";
return;
}
video_playing_ = true;
UpdateActivityData(ActivityType::VIDEO);
}
void IdleEventNotifier::OnVideoActivityEnded() {
if (!video_playing_) {
NOTREACHED() << "Duplicate end of video activity";
return;
}
video_playing_ = false;
UpdateActivityData(ActivityType::VIDEO);
}
IdleEventNotifier::ActivityData IdleEventNotifier::GetActivityDataAndReset() {
const ActivityData data = ConvertActivityData(*internal_data_);
ResetTimestampsForRecentActivity();
return data;
}
IdleEventNotifier::ActivityData IdleEventNotifier::GetActivityData() const {
return ConvertActivityData(*internal_data_);
}
IdleEventNotifier::ActivityData IdleEventNotifier::ConvertActivityData(
const ActivityDataInternal& internal_data) const {
const base::TimeDelta time_since_boot = boot_clock_.GetTimeSinceBoot();
ActivityData data;
base::Time::Exploded exploded;
internal_data.last_activity_time.LocalExplode(&exploded);
data.last_activity_day =
static_cast<UserActivityEvent_Features_DayOfWeek>(exploded.day_of_week);
data.last_activity_time_of_day =
internal_data.last_activity_time -
internal_data.last_activity_time.LocalMidnight();
if (!internal_data.last_user_activity_time.is_null()) {
data.last_user_activity_time_of_day =
internal_data.last_user_activity_time -
internal_data.last_user_activity_time.LocalMidnight();
}
if (internal_data.earliest_activity_since_boot) {
data.recent_time_active =
internal_data.last_activity_since_boot -
internal_data.earliest_activity_since_boot.value();
} else {
data.recent_time_active = base::TimeDelta();
}
if (internal_data.last_key_since_boot) {
data.time_since_last_key =
time_since_boot - internal_data.last_key_since_boot.value();
}
if (internal_data.last_mouse_since_boot) {
data.time_since_last_mouse =
time_since_boot - internal_data.last_mouse_since_boot.value();
}
if (internal_data.last_touch_since_boot) {
data.time_since_last_touch =
time_since_boot - internal_data.last_touch_since_boot.value();
}
if (internal_data_->video_start_time && internal_data_->video_end_time) {
DCHECK(!video_playing_);
data.video_playing_time = internal_data_->video_end_time.value() -
internal_data_->video_start_time.value();
data.time_since_video_ended =
time_since_boot - internal_data_->video_end_time.value();
}
data.key_events_in_last_hour = key_counter_->GetTotal(time_since_boot);
data.mouse_events_in_last_hour = mouse_counter_->GetTotal(time_since_boot);
data.touch_events_in_last_hour = touch_counter_->GetTotal(time_since_boot);
return data;
}
void IdleEventNotifier::UpdateActivityData(ActivityType type) {
const base::Time now = base::Time::Now();
DCHECK(internal_data_);
internal_data_->last_activity_time = now;
const base::TimeDelta time_since_boot = boot_clock_.GetTimeSinceBoot();
internal_data_->last_activity_since_boot = time_since_boot;
if (!internal_data_->earliest_activity_since_boot) {
internal_data_->earliest_activity_since_boot = time_since_boot;
}
if (type == ActivityType::VIDEO) {
if (video_playing_) {
if (!internal_data_->video_start_time ||
(internal_data_->video_end_time &&
(time_since_boot - internal_data_->video_end_time.value() >=
kIdleDelay))) {
internal_data_->video_start_time = time_since_boot;
}
} else {
internal_data_->video_end_time = time_since_boot;
}
return;
}
// All other activity is user-initiated.
internal_data_->last_user_activity_time = now;
switch (type) {
case ActivityType::KEY:
internal_data_->last_key_since_boot = time_since_boot;
key_counter_->Log(time_since_boot);
break;
case ActivityType::MOUSE:
internal_data_->last_mouse_since_boot = time_since_boot;
mouse_counter_->Log(time_since_boot);
break;
case ActivityType::TOUCH:
internal_data_->last_touch_since_boot = time_since_boot;
touch_counter_->Log(time_since_boot);
break;
default:
// We don't track other activity types.
return;
}
}
// Only clears out |last_activity_since_boot| and
// |earliest_activity_since_boot| because they are used to calculate recent
// time active, which should be reset between idle events.
void IdleEventNotifier::ResetTimestampsForRecentActivity() {
internal_data_->last_activity_since_boot = base::TimeDelta();
internal_data_->earliest_activity_since_boot = base::nullopt;
internal_data_->video_start_time = base::nullopt;
internal_data_->video_end_time = base::nullopt;
}
} // namespace ml
} // namespace power
} // namespace chromeos