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