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