summaryrefslogtreecommitdiffstats
path: root/chrome/browser/chromeos/session_length_limiter.cc
blob: 136db6083a764dc74babf18af504b1f8e6c8c659 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
// Copyright (c) 2012 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/session_length_limiter.h"

#include <algorithm>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/prefs/pref_registry_simple.h"
#include "base/prefs/pref_service.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/common/pref_names.h"
#include "ui/base/user_activity/user_activity_detector.h"
#include "ui/events/event.h"

namespace chromeos {

namespace {

// The minimum session time limit that can be set.
const int kSessionLengthLimitMinMs = 30 * 1000; // 30 seconds.

// The maximum session time limit that can be set.
const int kSessionLengthLimitMaxMs = 24 * 60 * 60 * 1000; // 24 hours.

// A default delegate implementation that returns the current time and does end
// the current user's session when requested. This can be replaced with a mock
// in tests.
class SessionLengthLimiterDelegateImpl : public SessionLengthLimiter::Delegate {
 public:
  SessionLengthLimiterDelegateImpl();
  virtual ~SessionLengthLimiterDelegateImpl();

  virtual const base::TimeTicks GetCurrentTime() const override;
  virtual void StopSession() override;

 private:
  DISALLOW_COPY_AND_ASSIGN(SessionLengthLimiterDelegateImpl);
};

SessionLengthLimiterDelegateImpl::SessionLengthLimiterDelegateImpl() {
}

SessionLengthLimiterDelegateImpl::~SessionLengthLimiterDelegateImpl() {
}

const base::TimeTicks SessionLengthLimiterDelegateImpl::GetCurrentTime() const {
  return base::TimeTicks::Now();
}

void SessionLengthLimiterDelegateImpl::StopSession() {
  chrome::AttemptUserExit();
}

}  // namespace

SessionLengthLimiter::Delegate::~Delegate() {
}

// static
void SessionLengthLimiter::RegisterPrefs(PrefRegistrySimple* registry) {
  registry->RegisterBooleanPref(prefs::kSessionUserActivitySeen, false);
  registry->RegisterInt64Pref(prefs::kSessionStartTime, 0);
  registry->RegisterIntegerPref(prefs::kSessionLengthLimit, 0);
  registry->RegisterBooleanPref(prefs::kSessionWaitForInitialUserActivity,
                                false);
}

SessionLengthLimiter::SessionLengthLimiter(Delegate* delegate,
                                           bool browser_restarted)
    : delegate_(delegate ? delegate : new SessionLengthLimiterDelegateImpl),
      user_activity_seen_(false) {
  DCHECK(thread_checker_.CalledOnValidThread());

  PrefService* local_state = g_browser_process->local_state();
  pref_change_registrar_.Init(local_state);
  pref_change_registrar_.Add(prefs::kSessionLengthLimit,
                             base::Bind(&SessionLengthLimiter::UpdateLimit,
                                        base::Unretained(this)));
  pref_change_registrar_.Add(
      prefs::kSessionWaitForInitialUserActivity,
      base::Bind(&SessionLengthLimiter::UpdateSessionStartTime,
                 base::Unretained(this)));

  // If this is a browser restart after a crash, try to restore the session
  // start time and the boolean indicating user activity from local state. If
  // this is not a browser restart after a crash or the attempt to restore
  // fails, set  the session start time to the current time and clear the
  // boolean indicating user activity.
  if (!browser_restarted || !RestoreStateAfterCrash()) {
    local_state->ClearPref(prefs::kSessionUserActivitySeen);
    UpdateSessionStartTime();
  }

  if (!user_activity_seen_ && ui::UserActivityDetector::Get())
    ui::UserActivityDetector::Get()->AddObserver(this);
}

SessionLengthLimiter::~SessionLengthLimiter() {
  if (!user_activity_seen_ && ui::UserActivityDetector::Get())
    ui::UserActivityDetector::Get()->RemoveObserver(this);
}

void SessionLengthLimiter::OnUserActivity(const ui::Event* event) {
  if (user_activity_seen_)
    return;
  if (ui::UserActivityDetector::Get())
    ui::UserActivityDetector::Get()->RemoveObserver(this);
  user_activity_seen_ = true;

  PrefService* local_state = g_browser_process->local_state();
  local_state->SetBoolean(prefs::kSessionUserActivitySeen, true);
  if (session_start_time_.is_null()) {
    // If instructed to wait for initial user activity and this is the first
    // activity in the session, set the session start time to the current time
    // and persist it in local state.
    session_start_time_ = delegate_->GetCurrentTime();
    local_state->SetInt64(prefs::kSessionStartTime,
                          session_start_time_.ToInternalValue());
  }
  local_state->CommitPendingWrite();

  UpdateLimit();
}

bool SessionLengthLimiter::RestoreStateAfterCrash() {
  PrefService* local_state = g_browser_process->local_state();
  const base::TimeTicks session_start_time =
      base::TimeTicks::FromInternalValue(
          local_state->GetInt64(prefs::kSessionStartTime));
  if (session_start_time.is_null() ||
      session_start_time >= delegate_->GetCurrentTime()) {
    return false;
  }

  session_start_time_ = session_start_time;
  user_activity_seen_ =
      local_state->GetBoolean(prefs::kSessionUserActivitySeen);

  UpdateLimit();
  return true;
}

void SessionLengthLimiter::UpdateSessionStartTime() {
  DCHECK(thread_checker_.CalledOnValidThread());

  if (user_activity_seen_)
    return;

  PrefService* local_state = g_browser_process->local_state();
  if (local_state->GetBoolean(prefs::kSessionWaitForInitialUserActivity)) {
    session_start_time_ = base::TimeTicks();
    local_state->ClearPref(prefs::kSessionStartTime);
  } else {
    session_start_time_ = delegate_->GetCurrentTime();
    local_state->SetInt64(prefs::kSessionStartTime,
                          session_start_time_.ToInternalValue());
  }
  local_state->CommitPendingWrite();

  UpdateLimit();
}

void SessionLengthLimiter::UpdateLimit() {
  DCHECK(thread_checker_.CalledOnValidThread());

  // Stop any currently running timer.
  timer_.reset();

  // If instructed to wait for initial user activity and no user activity has
  // occurred yet, do not start a timer.
  if (session_start_time_.is_null())
    return;

  // If no session length limit is set, do not start a timer.
  int limit;
  const PrefService::Preference* session_length_limit_pref =
      pref_change_registrar_.prefs()->
          FindPreference(prefs::kSessionLengthLimit);
  if (session_length_limit_pref->IsDefaultValue() ||
      !session_length_limit_pref->GetValue()->GetAsInteger(&limit)) {
    return;
  }

  // Clamp the session length limit to the valid range.
  const base::TimeDelta session_length_limit =
      base::TimeDelta::FromMilliseconds(std::min(std::max(
          limit, kSessionLengthLimitMinMs), kSessionLengthLimitMaxMs));

  // Calculate the remaining session time.
  const base::TimeDelta remaining = session_length_limit -
      (delegate_->GetCurrentTime() - session_start_time_);

  // Log out the user immediately if the session length limit has been reached
  // or exceeded.
  if (remaining <= base::TimeDelta()) {
    delegate_->StopSession();
    return;
  }

  // Set a timer to log out the user when the session length limit is reached.
  timer_.reset(new base::OneShotTimer<SessionLengthLimiter::Delegate>);
  timer_->Start(FROM_HERE, remaining, delegate_.get(),
                &SessionLengthLimiter::Delegate::StopSession);
}

}  // namespace chromeos