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