1// Copyright (c) 2012 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/session_length_limiter.h"
6
7#include <algorithm>
8
9#include "ash/shell.h"
10#include "base/bind.h"
11#include "base/bind_helpers.h"
12#include "base/location.h"
13#include "base/logging.h"
14#include "base/prefs/pref_registry_simple.h"
15#include "base/prefs/pref_service.h"
16#include "chrome/browser/browser_process.h"
17#include "chrome/browser/lifetime/application_lifetime.h"
18#include "chrome/common/pref_names.h"
19#include "ui/events/event.h"
20#include "ui/wm/core/user_activity_detector.h"
21
22namespace chromeos {
23
24namespace {
25
26// The minimum session time limit that can be set.
27const int kSessionLengthLimitMinMs = 30 * 1000; // 30 seconds.
28
29// The maximum session time limit that can be set.
30const int kSessionLengthLimitMaxMs = 24 * 60 * 60 * 1000; // 24 hours.
31
32// A default delegate implementation that returns the current time and does end
33// the current user's session when requested. This can be replaced with a mock
34// in tests.
35class SessionLengthLimiterDelegateImpl : public SessionLengthLimiter::Delegate {
36 public:
37  SessionLengthLimiterDelegateImpl();
38  virtual ~SessionLengthLimiterDelegateImpl();
39
40  virtual const base::TimeTicks GetCurrentTime() const OVERRIDE;
41  virtual void StopSession() OVERRIDE;
42
43 private:
44  DISALLOW_COPY_AND_ASSIGN(SessionLengthLimiterDelegateImpl);
45};
46
47SessionLengthLimiterDelegateImpl::SessionLengthLimiterDelegateImpl() {
48}
49
50SessionLengthLimiterDelegateImpl::~SessionLengthLimiterDelegateImpl() {
51}
52
53const base::TimeTicks SessionLengthLimiterDelegateImpl::GetCurrentTime() const {
54  return base::TimeTicks::Now();
55}
56
57void SessionLengthLimiterDelegateImpl::StopSession() {
58  chrome::AttemptUserExit();
59}
60
61}  // namespace
62
63SessionLengthLimiter::Delegate::~Delegate() {
64}
65
66// static
67void SessionLengthLimiter::RegisterPrefs(PrefRegistrySimple* registry) {
68  registry->RegisterBooleanPref(prefs::kSessionUserActivitySeen, false);
69  registry->RegisterInt64Pref(prefs::kSessionStartTime, 0);
70  registry->RegisterIntegerPref(prefs::kSessionLengthLimit, 0);
71  registry->RegisterBooleanPref(prefs::kSessionWaitForInitialUserActivity,
72                                false);
73}
74
75SessionLengthLimiter::SessionLengthLimiter(Delegate* delegate,
76                                           bool browser_restarted)
77    : delegate_(delegate ? delegate : new SessionLengthLimiterDelegateImpl),
78      user_activity_seen_(false) {
79  DCHECK(thread_checker_.CalledOnValidThread());
80
81  PrefService* local_state = g_browser_process->local_state();
82  pref_change_registrar_.Init(local_state);
83  pref_change_registrar_.Add(prefs::kSessionLengthLimit,
84                             base::Bind(&SessionLengthLimiter::UpdateLimit,
85                                        base::Unretained(this)));
86  pref_change_registrar_.Add(
87      prefs::kSessionWaitForInitialUserActivity,
88      base::Bind(&SessionLengthLimiter::UpdateSessionStartTime,
89                 base::Unretained(this)));
90
91  // If this is a browser restart after a crash, try to restore the session
92  // start time and the boolean indicating user activity from local state. If
93  // this is not a browser restart after a crash or the attempt to restore
94  // fails, set  the session start time to the current time and clear the
95  // boolean indicating user activity.
96  if (!browser_restarted || !RestoreStateAfterCrash()) {
97    local_state->ClearPref(prefs::kSessionUserActivitySeen);
98    UpdateSessionStartTime();
99  }
100
101  if (!user_activity_seen_ && ash::Shell::HasInstance())
102    ash::Shell::GetInstance()->user_activity_detector()->AddObserver(this);
103}
104
105SessionLengthLimiter::~SessionLengthLimiter() {
106  if (!user_activity_seen_ && ash::Shell::HasInstance())
107    ash::Shell::GetInstance()->user_activity_detector()->RemoveObserver(this);
108}
109
110void SessionLengthLimiter::OnUserActivity(const ui::Event* event) {
111  if (user_activity_seen_)
112    return;
113  if (ash::Shell::HasInstance())
114    ash::Shell::GetInstance()->user_activity_detector()->RemoveObserver(this);
115  user_activity_seen_ = true;
116
117  PrefService* local_state = g_browser_process->local_state();
118  local_state->SetBoolean(prefs::kSessionUserActivitySeen, true);
119  if (session_start_time_.is_null()) {
120    // If instructed to wait for initial user activity and this is the first
121    // activity in the session, set the session start time to the current time
122    // and persist it in local state.
123    session_start_time_ = delegate_->GetCurrentTime();
124    local_state->SetInt64(prefs::kSessionStartTime,
125                          session_start_time_.ToInternalValue());
126  }
127  local_state->CommitPendingWrite();
128
129  UpdateLimit();
130}
131
132bool SessionLengthLimiter::RestoreStateAfterCrash() {
133  PrefService* local_state = g_browser_process->local_state();
134  const base::TimeTicks session_start_time =
135      base::TimeTicks::FromInternalValue(
136          local_state->GetInt64(prefs::kSessionStartTime));
137  if (session_start_time.is_null() ||
138      session_start_time >= delegate_->GetCurrentTime()) {
139    return false;
140  }
141
142  session_start_time_ = session_start_time;
143  user_activity_seen_ =
144      local_state->GetBoolean(prefs::kSessionUserActivitySeen);
145
146  UpdateLimit();
147  return true;
148}
149
150void SessionLengthLimiter::UpdateSessionStartTime() {
151  DCHECK(thread_checker_.CalledOnValidThread());
152
153  if (user_activity_seen_)
154    return;
155
156  PrefService* local_state = g_browser_process->local_state();
157  if (local_state->GetBoolean(prefs::kSessionWaitForInitialUserActivity)) {
158    session_start_time_ = base::TimeTicks();
159    local_state->ClearPref(prefs::kSessionStartTime);
160  } else {
161    session_start_time_ = delegate_->GetCurrentTime();
162    local_state->SetInt64(prefs::kSessionStartTime,
163                          session_start_time_.ToInternalValue());
164  }
165  local_state->CommitPendingWrite();
166
167  UpdateLimit();
168}
169
170void SessionLengthLimiter::UpdateLimit() {
171  DCHECK(thread_checker_.CalledOnValidThread());
172
173  // Stop any currently running timer.
174  timer_.reset();
175
176  // If instructed to wait for initial user activity and no user activity has
177  // occurred yet, do not start a timer.
178  if (session_start_time_.is_null())
179    return;
180
181  // If no session length limit is set, do not start a timer.
182  int limit;
183  const PrefService::Preference* session_length_limit_pref =
184      pref_change_registrar_.prefs()->
185          FindPreference(prefs::kSessionLengthLimit);
186  if (session_length_limit_pref->IsDefaultValue() ||
187      !session_length_limit_pref->GetValue()->GetAsInteger(&limit)) {
188    return;
189  }
190
191  // Clamp the session length limit to the valid range.
192  const base::TimeDelta session_length_limit =
193      base::TimeDelta::FromMilliseconds(std::min(std::max(
194          limit, kSessionLengthLimitMinMs), kSessionLengthLimitMaxMs));
195
196  // Calculate the remaining session time.
197  const base::TimeDelta remaining = session_length_limit -
198      (delegate_->GetCurrentTime() - session_start_time_);
199
200  // Log out the user immediately if the session length limit has been reached
201  // or exceeded.
202  if (remaining <= base::TimeDelta()) {
203    delegate_->StopSession();
204    return;
205  }
206
207  // Set a timer to log out the user when the session length limit is reached.
208  timer_.reset(new base::OneShotTimer<SessionLengthLimiter::Delegate>);
209  timer_->Start(FROM_HERE, remaining, delegate_.get(),
210                &SessionLengthLimiter::Delegate::StopSession);
211}
212
213}  // namespace chromeos
214