1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/chromeos/power/ml/idle_event_notifier.h"
6
7 #include "base/check.h"
8 #include "base/notreached.h"
9 #include "chrome/browser/chromeos/power/ml/recent_events_counter.h"
10 #include "chromeos/dbus/dbus_thread_manager.h"
11 #include "chromeos/dbus/power_manager/idle.pb.h"
12 #include "chromeos/dbus/power_manager/suspend.pb.h"
13 #include "ui/events/event.h"
14 #include "ui/events/event_constants.h"
15
16 namespace chromeos {
17 namespace power {
18 namespace ml {
19
20 using TimeSinceBoot = base::TimeDelta;
21
22 // static
23 constexpr base::TimeDelta IdleEventNotifier::kIdleDelay;
24 constexpr base::TimeDelta IdleEventNotifier::kUserInputEventsDuration;
25 constexpr int IdleEventNotifier::kNumUserInputEventsBuckets;
26
27 struct IdleEventNotifier::ActivityDataInternal {
28 // Use base::Time here because we later need to convert them to local time
29 // since midnight.
30 base::Time last_activity_time;
31 // Zero if there's no user activity before idle event.
32 base::Time last_user_activity_time;
33
34 TimeSinceBoot last_activity_since_boot;
35 base::Optional<TimeSinceBoot> earliest_activity_since_boot;
36 base::Optional<TimeSinceBoot> last_key_since_boot;
37 base::Optional<TimeSinceBoot> last_mouse_since_boot;
38 base::Optional<TimeSinceBoot> last_touch_since_boot;
39 base::Optional<TimeSinceBoot> video_start_time;
40 base::Optional<TimeSinceBoot> video_end_time;
41 };
42
ActivityData()43 IdleEventNotifier::ActivityData::ActivityData() {}
44
ActivityData(const ActivityData & input_data)45 IdleEventNotifier::ActivityData::ActivityData(const ActivityData& input_data) {
46 last_activity_day = input_data.last_activity_day;
47 last_activity_time_of_day = input_data.last_activity_time_of_day;
48 last_user_activity_time_of_day = input_data.last_user_activity_time_of_day;
49 recent_time_active = input_data.recent_time_active;
50 time_since_last_key = input_data.time_since_last_key;
51 time_since_last_mouse = input_data.time_since_last_mouse;
52 time_since_last_touch = input_data.time_since_last_touch;
53 video_playing_time = input_data.video_playing_time;
54 time_since_video_ended = input_data.time_since_video_ended;
55 key_events_in_last_hour = input_data.key_events_in_last_hour;
56 mouse_events_in_last_hour = input_data.mouse_events_in_last_hour;
57 touch_events_in_last_hour = input_data.touch_events_in_last_hour;
58 }
59
IdleEventNotifier(PowerManagerClient * power_manager_client,ui::UserActivityDetector * detector,mojo::PendingReceiver<viz::mojom::VideoDetectorObserver> receiver)60 IdleEventNotifier::IdleEventNotifier(
61 PowerManagerClient* power_manager_client,
62 ui::UserActivityDetector* detector,
63 mojo::PendingReceiver<viz::mojom::VideoDetectorObserver> receiver)
64 : power_manager_client_observer_(this),
65 user_activity_observer_(this),
66 internal_data_(std::make_unique<ActivityDataInternal>()),
67 receiver_(this, std::move(receiver)),
68 key_counter_(
69 std::make_unique<RecentEventsCounter>(kUserInputEventsDuration,
70 kNumUserInputEventsBuckets)),
71 mouse_counter_(
72 std::make_unique<RecentEventsCounter>(kUserInputEventsDuration,
73 kNumUserInputEventsBuckets)),
74 touch_counter_(
75 std::make_unique<RecentEventsCounter>(kUserInputEventsDuration,
76 kNumUserInputEventsBuckets)) {
77 DCHECK(power_manager_client);
78 power_manager_client_observer_.Add(power_manager_client);
79 DCHECK(detector);
80 user_activity_observer_.Add(detector);
81 }
82
83 IdleEventNotifier::~IdleEventNotifier() = default;
84
LidEventReceived(chromeos::PowerManagerClient::LidState state,const base::TimeTicks &)85 void IdleEventNotifier::LidEventReceived(
86 chromeos::PowerManagerClient::LidState state,
87 const base::TimeTicks& /* timestamp */) {
88 // Ignore lid-close event, as we will observe suspend signal.
89 if (state == chromeos::PowerManagerClient::LidState::OPEN) {
90 UpdateActivityData(ActivityType::USER_OTHER);
91 }
92 }
93
PowerChanged(const power_manager::PowerSupplyProperties & proto)94 void IdleEventNotifier::PowerChanged(
95 const power_manager::PowerSupplyProperties& proto) {
96 if (external_power_ != proto.external_power()) {
97 external_power_ = proto.external_power();
98 UpdateActivityData(ActivityType::USER_OTHER);
99 }
100 }
101
102
SuspendDone(const base::TimeDelta & sleep_duration)103 void IdleEventNotifier::SuspendDone(const base::TimeDelta& sleep_duration) {
104 // SuspendDone is triggered by user opening the lid (or other user
105 // activities).
106 // A suspend and subsequent SuspendDone signal could occur with or without a
107 // preceding screen dim. If ScreenDimImminent is received before suspend,
108 // ResetTimestampsForRecentActivity would have been called so it wouldn't
109 // matter whether to reset the timestamps again. If there is no preceding
110 // ScreenDimImminent, then we need to call ResetTimestampsForRecentActivity
111 // if the |sleep_duration| is long enough, so that we can reset recent
112 // activity duration. We consider a |sleep_duration| is long if it is at least
113 // kIdleDelay.
114 if (sleep_duration >= kIdleDelay) {
115 ResetTimestampsForRecentActivity();
116 if (video_playing_) {
117 // This could happen when user closes the lid while video is playing.
118 // If OnVideoActivityEnded is not received before system is suspended, we
119 // could have |video_playing_| = true. If |sleep_duration| < kIdleDelay,
120 // we consider video never stopped. Otherwise, we treat it as a new video
121 // playing session.
122 internal_data_->video_start_time = boot_clock_.GetTimeSinceBoot();
123 }
124 }
125
126 UpdateActivityData(ActivityType::USER_OTHER);
127 }
128
OnUserActivity(const ui::Event * event)129 void IdleEventNotifier::OnUserActivity(const ui::Event* event) {
130 if (!event)
131 return;
132 // Get the type of activity first then reset timer.
133 ActivityType type = ActivityType::USER_OTHER;
134 if (event->IsKeyEvent()) {
135 type = ActivityType::KEY;
136 } else if (event->IsMouseEvent()) {
137 type = ActivityType::MOUSE;
138 } else if (event->IsTouchEvent()) {
139 type = ActivityType::TOUCH;
140 }
141 UpdateActivityData(type);
142 }
143
OnVideoActivityStarted()144 void IdleEventNotifier::OnVideoActivityStarted() {
145 if (video_playing_) {
146 NOTREACHED() << "Duplicate start of video activity";
147 return;
148 }
149 video_playing_ = true;
150 UpdateActivityData(ActivityType::VIDEO);
151 }
152
OnVideoActivityEnded()153 void IdleEventNotifier::OnVideoActivityEnded() {
154 if (!video_playing_) {
155 NOTREACHED() << "Duplicate end of video activity";
156 return;
157 }
158 video_playing_ = false;
159 UpdateActivityData(ActivityType::VIDEO);
160 }
161
GetActivityDataAndReset()162 IdleEventNotifier::ActivityData IdleEventNotifier::GetActivityDataAndReset() {
163 const ActivityData data = ConvertActivityData(*internal_data_);
164 ResetTimestampsForRecentActivity();
165 return data;
166 }
167
GetActivityData() const168 IdleEventNotifier::ActivityData IdleEventNotifier::GetActivityData() const {
169 return ConvertActivityData(*internal_data_);
170 }
171
ConvertActivityData(const ActivityDataInternal & internal_data) const172 IdleEventNotifier::ActivityData IdleEventNotifier::ConvertActivityData(
173 const ActivityDataInternal& internal_data) const {
174 const base::TimeDelta time_since_boot = boot_clock_.GetTimeSinceBoot();
175 ActivityData data;
176
177 base::Time::Exploded exploded;
178 internal_data.last_activity_time.LocalExplode(&exploded);
179 data.last_activity_day =
180 static_cast<UserActivityEvent_Features_DayOfWeek>(exploded.day_of_week);
181
182 data.last_activity_time_of_day =
183 internal_data.last_activity_time -
184 internal_data.last_activity_time.LocalMidnight();
185
186 if (!internal_data.last_user_activity_time.is_null()) {
187 data.last_user_activity_time_of_day =
188 internal_data.last_user_activity_time -
189 internal_data.last_user_activity_time.LocalMidnight();
190 }
191
192 if (internal_data.earliest_activity_since_boot) {
193 data.recent_time_active =
194 internal_data.last_activity_since_boot -
195 internal_data.earliest_activity_since_boot.value();
196 } else {
197 data.recent_time_active = base::TimeDelta();
198 }
199
200 if (internal_data.last_key_since_boot) {
201 data.time_since_last_key =
202 time_since_boot - internal_data.last_key_since_boot.value();
203 }
204
205 if (internal_data.last_mouse_since_boot) {
206 data.time_since_last_mouse =
207 time_since_boot - internal_data.last_mouse_since_boot.value();
208 }
209
210 if (internal_data.last_touch_since_boot) {
211 data.time_since_last_touch =
212 time_since_boot - internal_data.last_touch_since_boot.value();
213 }
214
215 if (internal_data_->video_start_time && internal_data_->video_end_time) {
216 DCHECK(!video_playing_);
217 data.video_playing_time = internal_data_->video_end_time.value() -
218 internal_data_->video_start_time.value();
219 data.time_since_video_ended =
220 time_since_boot - internal_data_->video_end_time.value();
221 }
222
223 data.key_events_in_last_hour = key_counter_->GetTotal(time_since_boot);
224 data.mouse_events_in_last_hour = mouse_counter_->GetTotal(time_since_boot);
225 data.touch_events_in_last_hour = touch_counter_->GetTotal(time_since_boot);
226
227 return data;
228 }
229
UpdateActivityData(ActivityType type)230 void IdleEventNotifier::UpdateActivityData(ActivityType type) {
231 const base::Time now = base::Time::Now();
232 DCHECK(internal_data_);
233 internal_data_->last_activity_time = now;
234
235 const base::TimeDelta time_since_boot = boot_clock_.GetTimeSinceBoot();
236
237 internal_data_->last_activity_since_boot = time_since_boot;
238 if (!internal_data_->earliest_activity_since_boot) {
239 internal_data_->earliest_activity_since_boot = time_since_boot;
240 }
241
242 if (type == ActivityType::VIDEO) {
243 if (video_playing_) {
244 if (!internal_data_->video_start_time ||
245 (internal_data_->video_end_time &&
246 (time_since_boot - internal_data_->video_end_time.value() >=
247 kIdleDelay))) {
248 internal_data_->video_start_time = time_since_boot;
249 }
250 } else {
251 internal_data_->video_end_time = time_since_boot;
252 }
253 return;
254 }
255
256 // All other activity is user-initiated.
257 internal_data_->last_user_activity_time = now;
258
259 switch (type) {
260 case ActivityType::KEY:
261 internal_data_->last_key_since_boot = time_since_boot;
262 key_counter_->Log(time_since_boot);
263 break;
264 case ActivityType::MOUSE:
265 internal_data_->last_mouse_since_boot = time_since_boot;
266 mouse_counter_->Log(time_since_boot);
267 break;
268 case ActivityType::TOUCH:
269 internal_data_->last_touch_since_boot = time_since_boot;
270 touch_counter_->Log(time_since_boot);
271 break;
272 default:
273 // We don't track other activity types.
274 return;
275 }
276 }
277
278 // Only clears out |last_activity_since_boot| and
279 // |earliest_activity_since_boot| because they are used to calculate recent
280 // time active, which should be reset between idle events.
ResetTimestampsForRecentActivity()281 void IdleEventNotifier::ResetTimestampsForRecentActivity() {
282 internal_data_->last_activity_since_boot = base::TimeDelta();
283 internal_data_->earliest_activity_since_boot = base::nullopt;
284 internal_data_->video_start_time = base::nullopt;
285 internal_data_->video_end_time = base::nullopt;
286 }
287
288 } // namespace ml
289 } // namespace power
290 } // namespace chromeos
291