network_portal_detector_impl.cc revision 5f1c94371a64b3196d4be9466099bb892df9b88e
1// Copyright (c) 2013 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/net/network_portal_detector_impl.h"
6
7#include <algorithm>
8
9#include "base/bind.h"
10#include "base/command_line.h"
11#include "base/logging.h"
12#include "base/message_loop/message_loop.h"
13#include "base/metrics/histogram.h"
14#include "chrome/browser/chrome_notification_types.h"
15#include "chromeos/dbus/dbus_thread_manager.h"
16#include "chromeos/dbus/shill_profile_client.h"
17#include "chromeos/login/login_state.h"
18#include "chromeos/network/network_state.h"
19#include "chromeos/network/network_state_handler.h"
20#include "content/public/browser/notification_service.h"
21#include "content/public/common/content_switches.h"
22#include "grit/generated_resources.h"
23#include "net/http/http_status_code.h"
24#include "third_party/cros_system_api/dbus/service_constants.h"
25#include "ui/base/l10n/l10n_util.h"
26
27using captive_portal::CaptivePortalDetector;
28
29namespace chromeos {
30
31namespace {
32
33// Delay before portal detection caused by changes in proxy settings.
34const int kProxyChangeDelaySec = 1;
35
36// Maximum number of reports from captive portal detector about
37// offline state in a row before notification is sent to observers.
38const int kMaxOfflineResultsBeforeReport = 3;
39
40// Delay before portal detection attempt after !ONLINE -> !ONLINE
41// transition.
42const int kShortInitialDelayBetweenAttemptsMs = 600;
43
44// Maximum timeout before portal detection attempts after !ONLINE ->
45// !ONLINE transition.
46const int kShortMaximumDelayBetweenAttemptsMs = 2 * 60 * 1000;
47
48// Delay before portal detection attempt after !ONLINE -> ONLINE
49// transition.
50const int kLongInitialDelayBetweenAttemptsMs = 30 * 1000;
51
52// Maximum timeout before portal detection attempts after !ONLINE ->
53// ONLINE transition.
54const int kLongMaximumDelayBetweenAttemptsMs = 5 * 60 * 1000;
55
56const NetworkState* DefaultNetwork() {
57  return NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
58}
59
60bool InSession() {
61  return LoginState::IsInitialized() && LoginState::Get()->IsUserLoggedIn();
62}
63
64void RecordDetectionResult(NetworkPortalDetector::CaptivePortalStatus status) {
65  if (InSession()) {
66    UMA_HISTOGRAM_ENUMERATION(
67        NetworkPortalDetectorImpl::kSessionDetectionResultHistogram,
68        status,
69        NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
70  } else {
71    UMA_HISTOGRAM_ENUMERATION(
72        NetworkPortalDetectorImpl::kOobeDetectionResultHistogram,
73        status,
74        NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
75  }
76}
77
78void RecordDetectionDuration(const base::TimeDelta& duration) {
79  if (InSession()) {
80    UMA_HISTOGRAM_MEDIUM_TIMES(
81        NetworkPortalDetectorImpl::kSessionDetectionDurationHistogram,
82        duration);
83  } else {
84    UMA_HISTOGRAM_MEDIUM_TIMES(
85        NetworkPortalDetectorImpl::kOobeDetectionDurationHistogram, duration);
86  }
87}
88
89void RecordDiscrepancyWithShill(
90    const NetworkState* network,
91    const NetworkPortalDetector::CaptivePortalStatus status) {
92  if (InSession()) {
93    if (network->connection_state() == shill::kStateOnline) {
94      UMA_HISTOGRAM_ENUMERATION(
95          NetworkPortalDetectorImpl::kSessionShillOnlineHistogram,
96          status,
97          NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
98    } else if (network->connection_state() == shill::kStatePortal) {
99      UMA_HISTOGRAM_ENUMERATION(
100          NetworkPortalDetectorImpl::kSessionShillPortalHistogram,
101          status,
102          NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
103    } else if (network->connection_state() == shill::kStateOffline) {
104      UMA_HISTOGRAM_ENUMERATION(
105          NetworkPortalDetectorImpl::kSessionShillOfflineHistogram,
106          status,
107          NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
108    }
109  } else {
110    if (network->connection_state() == shill::kStateOnline) {
111      UMA_HISTOGRAM_ENUMERATION(
112          NetworkPortalDetectorImpl::kOobeShillOnlineHistogram,
113          status,
114          NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
115    } else if (network->connection_state() == shill::kStatePortal) {
116      UMA_HISTOGRAM_ENUMERATION(
117          NetworkPortalDetectorImpl::kOobeShillPortalHistogram,
118          status,
119          NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
120    } else if (network->connection_state() == shill::kStateOffline) {
121      UMA_HISTOGRAM_ENUMERATION(
122          NetworkPortalDetectorImpl::kOobeShillOfflineHistogram,
123          status,
124          NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
125    }
126  }
127}
128
129void RecordPortalToOnlineTransition(const base::TimeDelta& duration) {
130  if (InSession()) {
131    UMA_HISTOGRAM_LONG_TIMES(
132        NetworkPortalDetectorImpl::kSessionPortalToOnlineHistogram,
133        duration);
134  } else {
135    UMA_HISTOGRAM_LONG_TIMES(
136        NetworkPortalDetectorImpl::kOobePortalToOnlineHistogram,
137        duration);
138  }
139}
140
141}  // namespace
142
143////////////////////////////////////////////////////////////////////////////////
144// NetworkPortalDetectorImpl::DetectionAttemptCompletedLogState
145
146NetworkPortalDetectorImpl::DetectionAttemptCompletedReport::
147    DetectionAttemptCompletedReport()
148    : result(captive_portal::RESULT_COUNT), response_code(-1) {
149}
150
151NetworkPortalDetectorImpl::DetectionAttemptCompletedReport::
152    DetectionAttemptCompletedReport(const std::string network_name,
153                                    const std::string network_id,
154                                    captive_portal::CaptivePortalResult result,
155                                    int response_code)
156    : network_name(network_name),
157      network_id(network_id),
158      result(result),
159      response_code(response_code) {
160}
161
162void NetworkPortalDetectorImpl::DetectionAttemptCompletedReport::Report()
163    const {
164  VLOG(1) << "Detection attempt completed: "
165          << "name=" << network_name << ", "
166          << "id=" << network_id << ", "
167          << "result=" << captive_portal::CaptivePortalResultToString(result)
168          << ", "
169          << "response_code=" << response_code;
170}
171
172bool NetworkPortalDetectorImpl::DetectionAttemptCompletedReport::Equals(
173    const DetectionAttemptCompletedReport& o) const {
174  return network_name == o.network_name && network_id == o.network_id &&
175         result == o.result && response_code == o.response_code;
176}
177
178////////////////////////////////////////////////////////////////////////////////
179// NetworkPortalDetectorImpl, public:
180
181const char NetworkPortalDetectorImpl::kOobeDetectionResultHistogram[] =
182    "CaptivePortal.OOBE.DetectionResult";
183const char NetworkPortalDetectorImpl::kOobeDetectionDurationHistogram[] =
184    "CaptivePortal.OOBE.DetectionDuration";
185const char NetworkPortalDetectorImpl::kOobeShillOnlineHistogram[] =
186    "CaptivePortal.OOBE.DiscrepancyWithShill_Online";
187const char NetworkPortalDetectorImpl::kOobeShillPortalHistogram[] =
188    "CaptivePortal.OOBE.DiscrepancyWithShill_RestrictedPool";
189const char NetworkPortalDetectorImpl::kOobeShillOfflineHistogram[] =
190    "CaptivePortal.OOBE.DiscrepancyWithShill_Offline";
191const char NetworkPortalDetectorImpl::kOobePortalToOnlineHistogram[] =
192    "CaptivePortal.OOBE.PortalToOnlineTransition";
193
194const char NetworkPortalDetectorImpl::kSessionDetectionResultHistogram[] =
195    "CaptivePortal.Session.DetectionResult";
196const char NetworkPortalDetectorImpl::kSessionDetectionDurationHistogram[] =
197    "CaptivePortal.Session.DetectionDuration";
198const char NetworkPortalDetectorImpl::kSessionShillOnlineHistogram[] =
199    "CaptivePortal.Session.DiscrepancyWithShill_Online";
200const char NetworkPortalDetectorImpl::kSessionShillPortalHistogram[] =
201    "CaptivePortal.Session.DiscrepancyWithShill_RestrictedPool";
202const char NetworkPortalDetectorImpl::kSessionShillOfflineHistogram[] =
203    "CaptivePortal.Session.DiscrepancyWithShill_Offline";
204const char NetworkPortalDetectorImpl::kSessionPortalToOnlineHistogram[] =
205    "CaptivePortal.Session.PortalToOnlineTransition";
206
207// static
208void NetworkPortalDetectorImpl::Initialize(
209    net::URLRequestContextGetter* url_context) {
210  if (NetworkPortalDetector::set_for_testing())
211    return;
212  CHECK(!NetworkPortalDetector::network_portal_detector())
213      << "NetworkPortalDetector was initialized twice.";
214  if (CommandLine::ForCurrentProcess()->HasSwitch(::switches::kTestType))
215    set_network_portal_detector(new NetworkPortalDetectorStubImpl());
216  else
217    set_network_portal_detector(new NetworkPortalDetectorImpl(url_context));
218}
219
220NetworkPortalDetectorImpl::NetworkPortalDetectorImpl(
221    const scoped_refptr<net::URLRequestContextGetter>& request_context)
222    : state_(STATE_IDLE),
223      test_url_(CaptivePortalDetector::kDefaultURL),
224      enabled_(false),
225      strategy_(PortalDetectorStrategy::CreateById(
226          PortalDetectorStrategy::STRATEGY_ID_LOGIN_SCREEN, this)),
227      last_detection_result_(CAPTIVE_PORTAL_STATUS_UNKNOWN),
228      same_detection_result_count_(0),
229      no_response_result_count_(0),
230      weak_factory_(this) {
231  captive_portal_detector_.reset(new CaptivePortalDetector(request_context));
232
233  registrar_.Add(this,
234                 chrome::NOTIFICATION_LOGIN_PROXY_CHANGED,
235                 content::NotificationService::AllSources());
236  registrar_.Add(this,
237                 chrome::NOTIFICATION_AUTH_SUPPLIED,
238                 content::NotificationService::AllSources());
239  registrar_.Add(this,
240                 chrome::NOTIFICATION_AUTH_CANCELLED,
241                 content::NotificationService::AllSources());
242
243  NetworkHandler::Get()->network_state_handler()->AddObserver(this, FROM_HERE);
244  StartDetectionIfIdle();
245}
246
247NetworkPortalDetectorImpl::~NetworkPortalDetectorImpl() {
248  DCHECK(CalledOnValidThread());
249
250  attempt_task_.Cancel();
251  attempt_timeout_.Cancel();
252
253  captive_portal_detector_->Cancel();
254  captive_portal_detector_.reset();
255  observers_.Clear();
256  if (NetworkHandler::IsInitialized()) {
257    NetworkHandler::Get()->network_state_handler()->RemoveObserver(this,
258                                                                   FROM_HERE);
259  }
260}
261
262void NetworkPortalDetectorImpl::AddObserver(Observer* observer) {
263  DCHECK(CalledOnValidThread());
264  if (observer && !observers_.HasObserver(observer))
265    observers_.AddObserver(observer);
266}
267
268void NetworkPortalDetectorImpl::AddAndFireObserver(Observer* observer) {
269  DCHECK(CalledOnValidThread());
270  if (!observer)
271    return;
272  AddObserver(observer);
273  CaptivePortalState portal_state;
274  const NetworkState* network = DefaultNetwork();
275  if (network)
276    portal_state = GetCaptivePortalState(network->guid());
277  observer->OnPortalDetectionCompleted(network, portal_state);
278}
279
280void NetworkPortalDetectorImpl::RemoveObserver(Observer* observer) {
281  DCHECK(CalledOnValidThread());
282  if (observer)
283    observers_.RemoveObserver(observer);
284}
285
286bool NetworkPortalDetectorImpl::IsEnabled() { return enabled_; }
287
288void NetworkPortalDetectorImpl::Enable(bool start_detection) {
289  DCHECK(CalledOnValidThread());
290  if (enabled_)
291    return;
292
293  DCHECK(is_idle());
294  enabled_ = true;
295
296  const NetworkState* network = DefaultNetwork();
297  if (!start_detection || !network)
298    return;
299  VLOG(1) << "Starting detection for: "
300          << "name=" << network->name() << ", id=" << network->guid();
301  portal_state_map_.erase(network->guid());
302  StartDetection();
303}
304
305NetworkPortalDetectorImpl::CaptivePortalState
306NetworkPortalDetectorImpl::GetCaptivePortalState(const std::string& guid) {
307  DCHECK(CalledOnValidThread());
308  CaptivePortalStateMap::const_iterator it = portal_state_map_.find(guid);
309  if (it == portal_state_map_.end())
310    return CaptivePortalState();
311  return it->second;
312}
313
314bool NetworkPortalDetectorImpl::StartDetectionIfIdle() {
315  if (!is_idle())
316    return false;
317  StartDetection();
318  return true;
319}
320
321void NetworkPortalDetectorImpl::SetStrategy(
322    PortalDetectorStrategy::StrategyId id) {
323  if (id == strategy_->Id())
324    return;
325  strategy_ = PortalDetectorStrategy::CreateById(id, this).Pass();
326  StopDetection();
327  StartDetectionIfIdle();
328}
329
330void NetworkPortalDetectorImpl::DefaultNetworkChanged(
331    const NetworkState* default_network) {
332  DCHECK(CalledOnValidThread());
333
334  if (!default_network) {
335    VLOG(1) << "DefaultNetworkChanged: None.";
336    default_network_name_.clear();
337
338    StopDetection();
339
340    CaptivePortalState state;
341    state.status = CAPTIVE_PORTAL_STATUS_OFFLINE;
342    OnDetectionCompleted(NULL, state);
343    return;
344  }
345
346  default_network_name_ = default_network->name();
347
348  bool network_changed = (default_network_id_ != default_network->guid());
349  default_network_id_ = default_network->guid();
350
351  bool connection_state_changed =
352      (default_connection_state_ != default_network->connection_state());
353  default_connection_state_ = default_network->connection_state();
354
355  VLOG(1) << "DefaultNetworkChanged: "
356          << "name=" << default_network_name_ << ", "
357          << "id=" << default_network_id_ << ", "
358          << "state=" << default_connection_state_ << ", "
359          << "changed=" << network_changed << ", "
360          << "state_changed=" << connection_state_changed;
361
362  if (network_changed || connection_state_changed)
363    StopDetection();
364
365  if (is_idle() && NetworkState::StateIsConnected(default_connection_state_)) {
366    // Initiate Captive Portal detection if network's captive
367    // portal state is unknown (e.g. for freshly created networks),
368    // offline or if network connection state was changed.
369    CaptivePortalState state = GetCaptivePortalState(default_network->guid());
370    if (state.status == CAPTIVE_PORTAL_STATUS_UNKNOWN ||
371        state.status == CAPTIVE_PORTAL_STATUS_OFFLINE ||
372        (!network_changed && connection_state_changed)) {
373      ScheduleAttempt(base::TimeDelta());
374    }
375  }
376}
377
378int NetworkPortalDetectorImpl::NoResponseResultCount() {
379  return no_response_result_count_;
380}
381
382base::TimeTicks NetworkPortalDetectorImpl::AttemptStartTime() {
383  return attempt_start_time_;
384}
385
386base::TimeTicks NetworkPortalDetectorImpl::GetCurrentTimeTicks() {
387  if (time_ticks_for_testing_.is_null())
388    return base::TimeTicks::Now();
389  return time_ticks_for_testing_;
390}
391
392
393////////////////////////////////////////////////////////////////////////////////
394// NetworkPortalDetectorImpl, private:
395
396void NetworkPortalDetectorImpl::StartDetection() {
397  DCHECK(is_idle());
398
399  ResetStrategyAndCounters();
400  detection_start_time_ = GetCurrentTimeTicks();
401  ScheduleAttempt(base::TimeDelta());
402}
403
404void NetworkPortalDetectorImpl::StopDetection() {
405  attempt_task_.Cancel();
406  attempt_timeout_.Cancel();
407  captive_portal_detector_->Cancel();
408  state_ = STATE_IDLE;
409  ResetStrategyAndCounters();
410}
411
412void NetworkPortalDetectorImpl::ScheduleAttempt(const base::TimeDelta& delay) {
413  DCHECK(is_idle());
414
415  if (!IsEnabled())
416    return;
417
418  attempt_task_.Cancel();
419  attempt_timeout_.Cancel();
420  state_ = STATE_PORTAL_CHECK_PENDING;
421
422  next_attempt_delay_ = std::max(delay, strategy_->GetDelayTillNextAttempt());
423  attempt_task_.Reset(base::Bind(&NetworkPortalDetectorImpl::StartAttempt,
424                                 weak_factory_.GetWeakPtr()));
425  base::MessageLoop::current()->PostDelayedTask(
426      FROM_HERE, attempt_task_.callback(), next_attempt_delay_);
427}
428
429void NetworkPortalDetectorImpl::StartAttempt() {
430  DCHECK(is_portal_check_pending());
431
432  state_ = STATE_CHECKING_FOR_PORTAL;
433  attempt_start_time_ = GetCurrentTimeTicks();
434
435  captive_portal_detector_->DetectCaptivePortal(
436      test_url_,
437      base::Bind(&NetworkPortalDetectorImpl::OnAttemptCompleted,
438                 weak_factory_.GetWeakPtr()));
439  attempt_timeout_.Reset(
440      base::Bind(&NetworkPortalDetectorImpl::OnAttemptTimeout,
441                 weak_factory_.GetWeakPtr()));
442
443  base::MessageLoop::current()->PostDelayedTask(
444      FROM_HERE,
445      attempt_timeout_.callback(),
446      strategy_->GetNextAttemptTimeout());
447}
448
449void NetworkPortalDetectorImpl::OnAttemptTimeout() {
450  DCHECK(CalledOnValidThread());
451  DCHECK(is_checking_for_portal());
452
453  VLOG(1) << "Portal detection timeout: name=" << default_network_name_ << ", "
454          << "id=" << default_network_id_;
455
456  captive_portal_detector_->Cancel();
457  CaptivePortalDetector::Results results;
458  results.result = captive_portal::RESULT_NO_RESPONSE;
459  OnAttemptCompleted(results);
460}
461
462void NetworkPortalDetectorImpl::OnAttemptCompleted(
463    const CaptivePortalDetector::Results& results) {
464  DCHECK(CalledOnValidThread());
465  DCHECK(is_checking_for_portal());
466
467  captive_portal::CaptivePortalResult result = results.result;
468  int response_code = results.response_code;
469
470  const NetworkState* network = DefaultNetwork();
471
472  // If using a fake profile client, also fake being behind a captive portal
473  // if the default network is in portal state.
474  if (result != captive_portal::RESULT_NO_RESPONSE &&
475      DBusThreadManager::Get()->GetShillProfileClient()->GetTestInterface() &&
476      network && network->connection_state() == shill::kStatePortal) {
477    result = captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL;
478    response_code = 200;
479  }
480
481  DetectionAttemptCompletedReport attempt_completed_report(
482      default_network_name_, default_network_id_, result, response_code);
483  if (!attempt_completed_report_.Equals(attempt_completed_report)) {
484    attempt_completed_report_ = attempt_completed_report;
485    attempt_completed_report_.Report();
486  }
487
488  state_ = STATE_IDLE;
489  attempt_timeout_.Cancel();
490
491  CaptivePortalState state;
492  state.response_code = response_code;
493  state.time = GetCurrentTimeTicks();
494  switch (result) {
495    case captive_portal::RESULT_NO_RESPONSE:
496      if (state.response_code == net::HTTP_PROXY_AUTHENTICATION_REQUIRED) {
497        state.status = CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED;
498      } else if (network &&
499                 (network->connection_state() == shill::kStatePortal)) {
500        // Take into account shill's detection results.
501        state.status = CAPTIVE_PORTAL_STATUS_PORTAL;
502      } else {
503        state.status = CAPTIVE_PORTAL_STATUS_OFFLINE;
504      }
505      break;
506    case captive_portal::RESULT_INTERNET_CONNECTED:
507      state.status = CAPTIVE_PORTAL_STATUS_ONLINE;
508      break;
509    case captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL:
510      state.status = CAPTIVE_PORTAL_STATUS_PORTAL;
511      break;
512    default:
513      break;
514  }
515
516  if (last_detection_result_ != state.status) {
517    last_detection_result_ = state.status;
518    same_detection_result_count_ = 1;
519    net::BackoffEntry::Policy policy = strategy_->policy();
520    if (state.status == CAPTIVE_PORTAL_STATUS_ONLINE) {
521      policy.initial_delay_ms = kLongInitialDelayBetweenAttemptsMs;
522      policy.maximum_backoff_ms = kLongMaximumDelayBetweenAttemptsMs;
523    } else {
524      policy.initial_delay_ms = kShortInitialDelayBetweenAttemptsMs;
525      policy.maximum_backoff_ms = kShortMaximumDelayBetweenAttemptsMs;
526    }
527    strategy_->SetPolicyAndReset(policy);
528  } else {
529    ++same_detection_result_count_;
530  }
531  strategy_->OnDetectionCompleted();
532
533  if (result == captive_portal::RESULT_NO_RESPONSE)
534    ++no_response_result_count_;
535  else
536    no_response_result_count_ = 0;
537
538  if (state.status != CAPTIVE_PORTAL_STATUS_OFFLINE ||
539      same_detection_result_count_ >= kMaxOfflineResultsBeforeReport) {
540    OnDetectionCompleted(network, state);
541  }
542  ScheduleAttempt(results.retry_after_delta);
543}
544
545void NetworkPortalDetectorImpl::Observe(
546    int type,
547    const content::NotificationSource& source,
548    const content::NotificationDetails& details) {
549  if (type == chrome::NOTIFICATION_LOGIN_PROXY_CHANGED ||
550      type == chrome::NOTIFICATION_AUTH_SUPPLIED ||
551      type == chrome::NOTIFICATION_AUTH_CANCELLED) {
552    VLOG(1) << "Restarting portal detection due to proxy change.";
553    StopDetection();
554    ScheduleAttempt(base::TimeDelta::FromSeconds(kProxyChangeDelaySec));
555  }
556}
557
558void NetworkPortalDetectorImpl::OnDetectionCompleted(
559    const NetworkState* network,
560    const CaptivePortalState& state) {
561  if (!network) {
562    NotifyDetectionCompleted(network, state);
563    return;
564  }
565
566  CaptivePortalStateMap::const_iterator it =
567      portal_state_map_.find(network->guid());
568  if (it == portal_state_map_.end() || it->second.status != state.status ||
569      it->second.response_code != state.response_code) {
570    // Record detection duration iff detection result differs from the
571    // previous one for this network. The reason is to record all stats
572    // only when network changes it's state.
573    RecordDetectionStats(network, state.status);
574    if (it != portal_state_map_.end() &&
575        it->second.status == CAPTIVE_PORTAL_STATUS_PORTAL &&
576        state.status == CAPTIVE_PORTAL_STATUS_ONLINE) {
577      RecordPortalToOnlineTransition(state.time - it->second.time);
578    }
579
580    portal_state_map_[network->guid()] = state;
581  }
582  NotifyDetectionCompleted(network, state);
583}
584
585void NetworkPortalDetectorImpl::NotifyDetectionCompleted(
586    const NetworkState* network,
587    const CaptivePortalState& state) {
588  FOR_EACH_OBSERVER(
589      Observer, observers_, OnPortalDetectionCompleted(network, state));
590  notification_controller_.OnPortalDetectionCompleted(network, state);
591}
592
593bool NetworkPortalDetectorImpl::AttemptTimeoutIsCancelledForTesting() const {
594  return attempt_timeout_.IsCancelled();
595}
596
597void NetworkPortalDetectorImpl::RecordDetectionStats(
598    const NetworkState* network,
599    CaptivePortalStatus status) {
600  // Don't record stats for offline state.
601  if (!network)
602    return;
603
604  if (!detection_start_time_.is_null())
605    RecordDetectionDuration(GetCurrentTimeTicks() - detection_start_time_);
606  RecordDetectionResult(status);
607
608  switch (status) {
609    case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_UNKNOWN:
610      NOTREACHED();
611      break;
612    case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_OFFLINE:
613      if (network->connection_state() == shill::kStateOnline ||
614          network->connection_state() == shill::kStatePortal) {
615        RecordDiscrepancyWithShill(network, status);
616      }
617      break;
618    case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE:
619      if (network->connection_state() != shill::kStateOnline)
620        RecordDiscrepancyWithShill(network, status);
621      break;
622    case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PORTAL:
623      if (network->connection_state() != shill::kStatePortal)
624        RecordDiscrepancyWithShill(network, status);
625      break;
626    case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED:
627      if (network->connection_state() != shill::kStateOnline)
628        RecordDiscrepancyWithShill(network, status);
629      break;
630    case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT:
631      NOTREACHED();
632      break;
633  }
634}
635
636void NetworkPortalDetectorImpl::ResetStrategyAndCounters() {
637  last_detection_result_ = CAPTIVE_PORTAL_STATUS_UNKNOWN;
638  same_detection_result_count_ = 0;
639  no_response_result_count_ = 0;
640  strategy_->Reset();
641}
642
643}  // namespace chromeos
644