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