// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/ui/webui/chromeos/mobile_setup_ui.h" #include <algorithm> #include <map> #include <string> #include "base/callback.h" #include "base/file_util.h" #include "base/json/json_reader.h" #include "base/json/json_writer.h" #include "base/logging.h" #include "base/memory/weak_ptr.h" #include "base/metrics/histogram.h" #include "base/string_piece.h" #include "base/string_util.h" #include "base/threading/thread_restrictions.h" #include "base/timer.h" #include "base/utf_string_conversions.h" #include "base/values.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/chromeos/cros/cros_library.h" #include "chrome/browser/chromeos/cros/network_library.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/webui/chrome_url_data_manager.h" #include "chrome/common/jstemplate_builder.h" #include "chrome/common/pref_names.h" #include "chrome/common/url_constants.h" #include "content/browser/browser_thread.h" #include "content/browser/tab_contents/tab_contents.h" #include "googleurl/src/gurl.h" #include "grit/browser_resources.h" #include "grit/chromium_strings.h" #include "grit/generated_resources.h" #include "grit/locale_settings.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" namespace { // Host page JS API function names. const char kJsApiStartActivation[] = "startActivation"; const char kJsApiSetTransactionStatus[] = "setTransactionStatus"; const char kJsDeviceStatusChangedHandler[] = "mobile.MobileSetup.deviceStateChanged"; // Error codes matching codes defined in the cellular config file. const char kErrorDefault[] = "default"; const char kErrorBadConnectionPartial[] = "bad_connection_partial"; const char kErrorBadConnectionActivated[] = "bad_connection_activated"; const char kErrorRoamingOnConnection[] = "roaming_connection"; const char kErrorNoEVDO[] = "no_evdo"; const char kErrorRoamingActivation[] = "roaming_activation"; const char kErrorRoamingPartiallyActivated[] = "roaming_partially_activated"; const char kErrorNoService[] = "no_service"; const char kErrorDisabled[] = "disabled"; const char kErrorNoDevice[] = "no_device"; const char kFailedPaymentError[] = "failed_payment"; const char kFailedConnectivity[] = "connectivity"; const char kErrorAlreadyRunning[] = "already_running"; // Cellular configuration file path. const char kCellularConfigPath[] = "/usr/share/chromeos-assets/mobile/mobile_config.json"; // Cellular config file field names. const char kVersionField[] = "version"; const char kErrorsField[] = "errors"; // Number of times we will retry to restart the activation process in case // there is no connectivity in the restricted pool. const int kMaxActivationAttempt = 3; // Number of times we will retry to reconnect if connection fails. const int kMaxConnectionRetry = 10; // Number of times we will retry to reconnect if connection fails. const int kMaxConnectionRetryOTASP = 30; // Number of times we will retry to reconnect after payment is processed. const int kMaxReconnectAttemptOTASP = 30; // Reconnect retry delay (after payment is processed). const int kPostPaymentReconnectDelayMS = 30000; // 30s. // Connection timeout in seconds. const int kConnectionTimeoutSeconds = 45; // Reconnect delay. const int kReconnectDelayMS = 3000; // Reconnect timer delay. const int kReconnectTimerDelayMS = 5000; // Reconnect delay after previous failure. const int kFailedReconnectDelayMS = 10000; // Retry delay after failed OTASP attempt. const int kOTASPRetryDelay = 20000; chromeos::CellularNetwork* GetCellularNetwork() { chromeos::NetworkLibrary* lib = chromeos::CrosLibrary::Get()-> GetNetworkLibrary(); if (lib->cellular_networks().begin() != lib->cellular_networks().end()) { return *(lib->cellular_networks().begin()); } return NULL; } chromeos::CellularNetwork* GetCellularNetwork( const std::string& service_path) { return chromeos::CrosLibrary::Get()-> GetNetworkLibrary()->FindCellularNetworkByPath(service_path); } } // namespace class CellularConfigDocument { public: CellularConfigDocument() {} // Return error message for a given code. std::string GetErrorMessage(const std::string& code); const std::string& version() { return version_; } bool LoadFromFile(const FilePath& config_path); private: std::string version_; std::map<std::string, std::string> error_map_; DISALLOW_COPY_AND_ASSIGN(CellularConfigDocument); }; static std::map<std::string, std::string> error_messages_; class MobileSetupUIHTMLSource : public ChromeURLDataManager::DataSource { public: explicit MobileSetupUIHTMLSource(const std::string& service_path); // Called when the network layer has requested a resource underneath // the path we registered. virtual void StartDataRequest(const std::string& path, bool is_incognito, int request_id); virtual std::string GetMimeType(const std::string&) const { return "text/html"; } private: virtual ~MobileSetupUIHTMLSource() {} std::string service_path_; DISALLOW_COPY_AND_ASSIGN(MobileSetupUIHTMLSource); }; // The handler for Javascript messages related to the "register" view. class MobileSetupHandler : public WebUIMessageHandler, public chromeos::NetworkLibrary::NetworkManagerObserver, public chromeos::NetworkLibrary::NetworkObserver, public base::SupportsWeakPtr<MobileSetupHandler> { public: explicit MobileSetupHandler(const std::string& service_path); virtual ~MobileSetupHandler(); // Init work after Attach. void Init(TabContents* contents); // WebUIMessageHandler implementation. virtual WebUIMessageHandler* Attach(WebUI* web_ui); virtual void RegisterMessages(); // NetworkLibrary::NetworkManagerObserver implementation. virtual void OnNetworkManagerChanged(chromeos::NetworkLibrary* obj); // NetworkLibrary::NetworkObserver implementation. virtual void OnNetworkChanged(chromeos::NetworkLibrary* obj, const chromeos::Network* network); private: typedef enum PlanActivationState { PLAN_ACTIVATION_PAGE_LOADING = -1, PLAN_ACTIVATION_START = 0, PLAN_ACTIVATION_TRYING_OTASP = 1, PLAN_ACTIVATION_RECONNECTING_OTASP_TRY = 2, PLAN_ACTIVATION_INITIATING_ACTIVATION = 3, PLAN_ACTIVATION_RECONNECTING = 4, PLAN_ACTIVATION_SHOWING_PAYMENT = 5, PLAN_ACTIVATION_DELAY_OTASP = 6, PLAN_ACTIVATION_START_OTASP = 7, PLAN_ACTIVATION_OTASP = 8, PLAN_ACTIVATION_RECONNECTING_OTASP = 9, PLAN_ACTIVATION_DONE = 10, PLAN_ACTIVATION_ERROR = 0xFF, } PlanActivationState; class TaskProxy : public base::RefCountedThreadSafe<TaskProxy> { public: TaskProxy(const base::WeakPtr<MobileSetupHandler>& handler, int delay) : handler_(handler), delay_(delay) { } TaskProxy(const base::WeakPtr<MobileSetupHandler>& handler, const std::string& status) : handler_(handler), status_(status) { } void HandleStartActivation() { if (handler_) handler_->StartActivation(); } void HandleSetTransactionStatus() { if (handler_) handler_->SetTransactionStatus(status_); } void ContinueConnecting() { if (handler_) handler_->ContinueConnecting(delay_); } void RetryOTASP() { if (handler_) handler_->RetryOTASP(); } private: base::WeakPtr<MobileSetupHandler> handler_; std::string status_; int delay_; DISALLOW_COPY_AND_ASSIGN(TaskProxy); }; // Handlers for JS WebUI messages. void HandleSetTransactionStatus(const ListValue* args); void HandleStartActivation(const ListValue* args); void SetTransactionStatus(const std::string& status); // Schedules activation process via task proxy. void InitiateActivation(); // Starts activation. void StartActivation(); // Retried OTASP. void RetryOTASP(); // Continues activation process. This method is called after we disconnect // due to detected connectivity issue to kick off reconnection. void ContinueConnecting(int delay); // Sends message to host registration page with system/user info data. void SendDeviceInfo(); // Callback for when |reconnect_timer_| fires. void ReconnectTimerFired(); // Starts OTASP process. void StartOTASP(); // Checks if we need to reconnect due to failed connection attempt. bool NeedsReconnecting(chromeos::CellularNetwork* network, PlanActivationState* new_state, std::string* error_description); // Disconnect from network. void DisconnectFromNetwork(chromeos::CellularNetwork* network); // Connects to cellular network, resets connection timer. bool ConnectToNetwork(chromeos::CellularNetwork* network, int delay); // Forces disconnect / reconnect when we detect portal connectivity issues. void ForceReconnect(chromeos::CellularNetwork* network, int delay); // Reports connection timeout. bool ConnectionTimeout(); // Verify the state of cellular network and modify internal state. void EvaluateCellularNetwork(chromeos::CellularNetwork* network); // Check the current cellular network for error conditions. bool GotActivationError(chromeos::CellularNetwork* network, std::string* error); // Sends status updates to WebUI page. void UpdatePage(chromeos::CellularNetwork* network, const std::string& error_description); // Changes internal state. void ChangeState(chromeos::CellularNetwork* network, PlanActivationState new_state, const std::string& error_description); // Prepares network devices for cellular activation process. void SetupActivationProcess(chromeos::CellularNetwork* network); // Disables ethernet and wifi newtorks since they interefere with // detection of restricted pool on cellular side. void DisableOtherNetworks(); // Resets network devices after cellular activation process. // |network| should be NULL if the activation process failed. void CompleteActivation(chromeos::CellularNetwork* network); // Control routines for handling other types of connections during // cellular activation. void ReEnableOtherConnections(); // Converts the currently active CellularNetwork device into a JS object. static void GetDeviceInfo(chromeos::CellularNetwork* network, DictionaryValue* value); static bool ShouldReportDeviceState(std::string* state, std::string* error); // Performs activation state cellular device evaluation. // Returns false if device activation failed. In this case |error| // will contain error message to be reported to Web UI. static bool EvaluateCellularDeviceState(bool* report_status, std::string* state, std::string* error); // Return error message for a given code. static std::string GetErrorMessage(const std::string& code); static void LoadCellularConfig(); // Returns next reconnection state based on the current activation phase. static PlanActivationState GetNextReconnectState(PlanActivationState state); static const char* GetStateDescription(PlanActivationState state); static scoped_ptr<CellularConfigDocument> cellular_config_; TabContents* tab_contents_; // Internal handler state. PlanActivationState state_; std::string service_path_; // Flags that control if wifi and ethernet connection needs to be restored // after the activation of cellular network. bool reenable_wifi_; bool reenable_ethernet_; bool reenable_cert_check_; bool evaluating_; // True if we think that another tab is already running activation. bool already_running_; // Connection retry counter. int connection_retry_count_; // Post payment reconnect wait counters. int reconnect_wait_count_; // Activation retry attempt count; int activation_attempt_; // Connection start time. base::Time connection_start_time_; // Timer that monitors reconnection timeouts. base::RepeatingTimer<MobileSetupHandler> reconnect_timer_; DISALLOW_COPY_AND_ASSIGN(MobileSetupHandler); }; scoped_ptr<CellularConfigDocument> MobileSetupHandler::cellular_config_; //////////////////////////////////////////////////////////////////////////////// // // CellularConfigDocument // //////////////////////////////////////////////////////////////////////////////// std::string CellularConfigDocument::GetErrorMessage(const std::string& code) { std::map<std::string, std::string>::iterator iter = error_map_.find(code); if (iter == error_map_.end()) return code; return iter->second; } bool CellularConfigDocument::LoadFromFile(const FilePath& config_path) { error_map_.clear(); std::string config; { // Reading config file causes us to do blocking IO on UI thread. // Temporarily allow it until we fix http://crosbug.com/11535 base::ThreadRestrictions::ScopedAllowIO allow_io; if (!file_util::ReadFileToString(config_path, &config)) return false; } scoped_ptr<Value> root(base::JSONReader::Read(config, true)); DCHECK(root.get() != NULL); if (!root.get() || root->GetType() != Value::TYPE_DICTIONARY) { LOG(WARNING) << "Bad cellular config file"; return false; } DictionaryValue* root_dict = static_cast<DictionaryValue*>(root.get()); if (!root_dict->GetString(kVersionField, &version_)) { LOG(WARNING) << "Cellular config file missing version"; return false; } DictionaryValue* errors = NULL; if (!root_dict->GetDictionary(kErrorsField, &errors)) return false; for (DictionaryValue::key_iterator keys = errors->begin_keys(); keys != errors->end_keys(); ++keys) { std::string value; if (!errors->GetString(*keys, &value)) { LOG(WARNING) << "Bad cellular config error value"; error_map_.clear(); return false; } error_map_.insert(std::pair<std::string, std::string>(*keys, value)); } return true; } //////////////////////////////////////////////////////////////////////////////// // // MobileSetupUIHTMLSource // //////////////////////////////////////////////////////////////////////////////// MobileSetupUIHTMLSource::MobileSetupUIHTMLSource( const std::string& service_path) : DataSource(chrome::kChromeUIMobileSetupHost, MessageLoop::current()), service_path_(service_path) { } void MobileSetupUIHTMLSource::StartDataRequest(const std::string& path, bool is_incognito, int request_id) { chromeos::CellularNetwork* network = GetCellularNetwork(service_path_); DCHECK(network); DictionaryValue strings; strings.SetString("title", l10n_util::GetStringUTF16(IDS_MOBILE_SETUP_TITLE)); strings.SetString("connecting_header", l10n_util::GetStringFUTF16(IDS_MOBILE_CONNECTING_HEADER, network ? UTF8ToUTF16(network->name()) : string16())); strings.SetString("error_header", l10n_util::GetStringUTF16(IDS_MOBILE_ERROR_HEADER)); strings.SetString("activating_header", l10n_util::GetStringUTF16(IDS_MOBILE_ACTIVATING_HEADER)); strings.SetString("completed_header", l10n_util::GetStringUTF16(IDS_MOBILE_COMPLETED_HEADER)); strings.SetString("please_wait", l10n_util::GetStringUTF16(IDS_MOBILE_PLEASE_WAIT)); strings.SetString("completed_text", l10n_util::GetStringUTF16(IDS_MOBILE_COMPLETED_TEXT)); strings.SetString("close_button", l10n_util::GetStringUTF16(IDS_CLOSE)); SetFontAndTextDirection(&strings); static const base::StringPiece html( ResourceBundle::GetSharedInstance().GetRawDataResource( IDR_MOBILE_SETUP_PAGE_HTML)); const std::string full_html = jstemplate_builder::GetI18nTemplateHtml( html, &strings); scoped_refptr<RefCountedBytes> html_bytes(new RefCountedBytes); html_bytes->data.resize(full_html.size()); std::copy(full_html.begin(), full_html.end(), html_bytes->data.begin()); SendResponse(request_id, html_bytes); } //////////////////////////////////////////////////////////////////////////////// // // MobileSetupHandler // //////////////////////////////////////////////////////////////////////////////// MobileSetupHandler::MobileSetupHandler(const std::string& service_path) : tab_contents_(NULL), state_(PLAN_ACTIVATION_PAGE_LOADING), service_path_(service_path), reenable_wifi_(false), reenable_ethernet_(false), reenable_cert_check_(false), evaluating_(false), already_running_(false), connection_retry_count_(0), reconnect_wait_count_(0), activation_attempt_(0) { } MobileSetupHandler::~MobileSetupHandler() { reconnect_timer_.Stop(); chromeos::NetworkLibrary* lib = chromeos::CrosLibrary::Get()->GetNetworkLibrary(); lib->RemoveNetworkManagerObserver(this); lib->RemoveObserverForAllNetworks(this); if (lib->IsLocked()) lib->Unlock(); ReEnableOtherConnections(); } WebUIMessageHandler* MobileSetupHandler::Attach(WebUI* web_ui) { return WebUIMessageHandler::Attach(web_ui); } void MobileSetupHandler::Init(TabContents* contents) { tab_contents_ = contents; LoadCellularConfig(); if (!chromeos::CrosLibrary::Get()->GetNetworkLibrary()->IsLocked()) SetupActivationProcess(GetCellularNetwork(service_path_)); else already_running_ = true; } void MobileSetupHandler::RegisterMessages() { web_ui_->RegisterMessageCallback(kJsApiStartActivation, NewCallback(this, &MobileSetupHandler::HandleStartActivation)); web_ui_->RegisterMessageCallback(kJsApiSetTransactionStatus, NewCallback(this, &MobileSetupHandler::HandleSetTransactionStatus)); } void MobileSetupHandler::OnNetworkManagerChanged( chromeos::NetworkLibrary* cros) { if (state_ == PLAN_ACTIVATION_PAGE_LOADING) return; // Note that even though we get here when the service has // reappeared after disappearing earlier in the activation // process, there's no need to re-establish the NetworkObserver, // because the service path remains the same. EvaluateCellularNetwork(GetCellularNetwork(service_path_)); } void MobileSetupHandler::OnNetworkChanged(chromeos::NetworkLibrary* cros, const chromeos::Network* network) { if (state_ == PLAN_ACTIVATION_PAGE_LOADING) return; DCHECK(network && network->type() == chromeos::TYPE_CELLULAR); EvaluateCellularNetwork(GetCellularNetwork(network->service_path())); } void MobileSetupHandler::HandleStartActivation(const ListValue* args) { InitiateActivation(); UMA_HISTOGRAM_COUNTS("Cellular.MobileSetupStart", 1); } void MobileSetupHandler::HandleSetTransactionStatus(const ListValue* args) { const size_t kSetTransactionStatusParamCount = 1; if (args->GetSize() != kSetTransactionStatusParamCount) return; // Get change callback function name. std::string status; if (!args->GetString(0, &status)) return; scoped_refptr<TaskProxy> task = new TaskProxy(AsWeakPtr(), status); BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, NewRunnableMethod(task.get(), &TaskProxy::HandleSetTransactionStatus)); } void MobileSetupHandler::InitiateActivation() { scoped_refptr<TaskProxy> task = new TaskProxy(AsWeakPtr(), 0); BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, NewRunnableMethod(task.get(), &TaskProxy::HandleStartActivation)); } void MobileSetupHandler::StartActivation() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); chromeos::NetworkLibrary* lib = chromeos::CrosLibrary::Get()->GetNetworkLibrary(); chromeos::CellularNetwork* network = GetCellularNetwork(service_path_); // Check if we can start activation process. if (!network || already_running_) { std::string error; if (already_running_) error = kErrorAlreadyRunning; else if (!lib->cellular_available()) error = kErrorNoDevice; else if (!lib->cellular_enabled()) error = kErrorDisabled; else error = kErrorNoService; ChangeState(NULL, PLAN_ACTIVATION_ERROR, GetErrorMessage(error)); return; } // Start monitoring network property changes. lib->AddNetworkManagerObserver(this); lib->RemoveObserverForAllNetworks(this); lib->AddNetworkObserver(network->service_path(), this); // Try to start with OTASP immediately if we have received payment recently. state_ = lib->HasRecentCellularPlanPayment() ? PLAN_ACTIVATION_START_OTASP : PLAN_ACTIVATION_START; EvaluateCellularNetwork(network); } void MobileSetupHandler::RetryOTASP() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(state_ == PLAN_ACTIVATION_DELAY_OTASP); StartOTASP(); } void MobileSetupHandler::ContinueConnecting(int delay) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); chromeos::CellularNetwork* network = GetCellularNetwork(service_path_); if (network && network->connecting_or_connected()) { EvaluateCellularNetwork(network); } else { ConnectToNetwork(network, delay); } } void MobileSetupHandler::SetTransactionStatus(const std::string& status) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); // The payment is received, try to reconnect and check the status all over // again. if (LowerCaseEqualsASCII(status, "ok") && state_ == PLAN_ACTIVATION_SHOWING_PAYMENT) { chromeos::NetworkLibrary* lib = chromeos::CrosLibrary::Get()->GetNetworkLibrary(); lib->SignalCellularPlanPayment(); UMA_HISTOGRAM_COUNTS("Cellular.PaymentReceived", 1); StartOTASP(); } else { UMA_HISTOGRAM_COUNTS("Cellular.PaymentFailed", 1); } } void MobileSetupHandler::StartOTASP() { state_ = PLAN_ACTIVATION_START_OTASP; chromeos::CellularNetwork* network = GetCellularNetwork(); if (network && network->connected() && network->activation_state() == chromeos::ACTIVATION_STATE_ACTIVATED) { chromeos::CrosLibrary::Get()->GetNetworkLibrary()-> DisconnectFromNetwork(network); } else { EvaluateCellularNetwork(network); } } void MobileSetupHandler::ReconnectTimerFired() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); // Permit network connection changes only in reconnecting states. if (state_ != PLAN_ACTIVATION_RECONNECTING_OTASP_TRY && state_ != PLAN_ACTIVATION_RECONNECTING && state_ != PLAN_ACTIVATION_RECONNECTING_OTASP) return; chromeos::CellularNetwork* network = GetCellularNetwork(service_path_); if (!network) { // No service, try again since this is probably just transient condition. LOG(WARNING) << "Service not present at reconnect attempt."; } EvaluateCellularNetwork(network); } void MobileSetupHandler::DisconnectFromNetwork( chromeos::CellularNetwork* network) { DCHECK(network); LOG(INFO) << "Disconnecting from: " << network->service_path(); chromeos::CrosLibrary::Get()->GetNetworkLibrary()-> DisconnectFromNetwork(network); // Disconnect will force networks to be reevaluated, so // we don't want to continue processing on this path anymore. evaluating_ = false; } bool MobileSetupHandler::NeedsReconnecting( chromeos::CellularNetwork* network, PlanActivationState* new_state, std::string* error_description) { DCHECK(network); if (!network->failed() && !ConnectionTimeout()) return false; // Try to reconnect again if reconnect failed, or if for some // reasons we are still not connected after 45 seconds. int max_retries = (state_ == PLAN_ACTIVATION_RECONNECTING_OTASP) ? kMaxConnectionRetryOTASP : kMaxConnectionRetry; if (connection_retry_count_ < max_retries) { UMA_HISTOGRAM_COUNTS("Cellular.ConnectionRetry", 1); ConnectToNetwork(network, kFailedReconnectDelayMS); return true; } // We simply can't connect anymore after all these tries. UMA_HISTOGRAM_COUNTS("Cellular.ConnectionFailed", 1); *new_state = PLAN_ACTIVATION_ERROR; *error_description = GetErrorMessage(kFailedConnectivity); return false; } bool MobileSetupHandler::ConnectToNetwork( chromeos::CellularNetwork* network, int delay) { if (network && network->connecting_or_connected()) return true; // Permit network connection changes only in reconnecting states. if (state_ != PLAN_ACTIVATION_RECONNECTING_OTASP_TRY && state_ != PLAN_ACTIVATION_RECONNECTING && state_ != PLAN_ACTIVATION_RECONNECTING_OTASP) return false; if (network) LOG(INFO) << "Connecting to: " << network->service_path(); connection_retry_count_++; connection_start_time_ = base::Time::Now(); if (!network) { LOG(WARNING) << "Connect failed." << (network ? network->service_path().c_str() : "no service"); // If we coudn't connect during reconnection phase, try to reconnect // with a delay (and try to reconnect if needed). scoped_refptr<TaskProxy> task = new TaskProxy(AsWeakPtr(), delay); BrowserThread::PostDelayedTask(BrowserThread::UI, FROM_HERE, NewRunnableMethod(task.get(), &TaskProxy::ContinueConnecting), delay); return false; } chromeos::CrosLibrary::Get()->GetNetworkLibrary()-> ConnectToCellularNetwork(network); return true; } void MobileSetupHandler::ForceReconnect( chromeos::CellularNetwork* network, int delay) { DCHECK(network); UMA_HISTOGRAM_COUNTS("Cellular.ActivationRetry", 1); // Reset reconnect metrics. connection_retry_count_ = 0; connection_start_time_ = base::Time(); // First, disconnect... LOG(INFO) << "Disconnecting from " << network->service_path(); chromeos::CrosLibrary::Get()->GetNetworkLibrary()-> DisconnectFromNetwork(network); // Check the network state 3s after we disconnect to make sure. scoped_refptr<TaskProxy> task = new TaskProxy(AsWeakPtr(), delay); BrowserThread::PostDelayedTask(BrowserThread::UI, FROM_HERE, NewRunnableMethod(task.get(), &TaskProxy::ContinueConnecting), delay); } bool MobileSetupHandler::ConnectionTimeout() { return (base::Time::Now() - connection_start_time_).InSeconds() > kConnectionTimeoutSeconds; } void MobileSetupHandler::EvaluateCellularNetwork( chromeos::CellularNetwork* network) { if (!web_ui_) return; PlanActivationState new_state = state_; if (!network) { LOG(WARNING) << "Cellular service lost"; if (state_ == PLAN_ACTIVATION_RECONNECTING_OTASP_TRY || state_ == PLAN_ACTIVATION_RECONNECTING || state_ == PLAN_ACTIVATION_RECONNECTING_OTASP) { // This might be the legit case when service is lost after activation. // We need to make sure we force reconnection as soon as it shows up. LOG(INFO) << "Force service reconnection"; connection_start_time_ = base::Time(); } return; } // Prevent this method from being called if it is already on the stack. // This might happen on some state transitions (ie. connect, disconnect). if (evaluating_) return; evaluating_ = true; std::string error_description; LOG(WARNING) << "Cellular:\n service=" << network->GetStateString().c_str() << "\n ui=" << GetStateDescription(state_) << "\n activation=" << network->GetActivationStateString().c_str() << "\n connectivity=" << network->GetConnectivityStateString().c_str() << "\n error=" << network->GetErrorString().c_str() << "\n setvice_path=" << network->service_path().c_str(); switch (state_) { case PLAN_ACTIVATION_START: { switch (network->activation_state()) { case chromeos::ACTIVATION_STATE_ACTIVATED: { if (network->failed_or_disconnected()) { new_state = PLAN_ACTIVATION_RECONNECTING; } else if (network->connected()) { if (network->restricted_pool()) { new_state = PLAN_ACTIVATION_SHOWING_PAYMENT; } else { new_state = PLAN_ACTIVATION_DONE; } } break; } default: { if (network->failed_or_disconnected() || network->state() == chromeos::STATE_ACTIVATION_FAILURE) { new_state = (network->activation_state() == chromeos::ACTIVATION_STATE_PARTIALLY_ACTIVATED) ? PLAN_ACTIVATION_TRYING_OTASP : PLAN_ACTIVATION_INITIATING_ACTIVATION; } else if (network->connected()) { DisconnectFromNetwork(network); return; } break; } } break; } case PLAN_ACTIVATION_START_OTASP: { switch (network->activation_state()) { case chromeos::ACTIVATION_STATE_PARTIALLY_ACTIVATED: { if (network->failed_or_disconnected()) { new_state = PLAN_ACTIVATION_OTASP; } else if (network->connected()) { DisconnectFromNetwork(network); return; } break; } case chromeos::ACTIVATION_STATE_ACTIVATED: new_state = PLAN_ACTIVATION_RECONNECTING_OTASP; break; default: { LOG(WARNING) << "Unexpected activation state for device " << network->service_path().c_str(); break; } } break; } case PLAN_ACTIVATION_DELAY_OTASP: // Just ignore any changes until the OTASP retry timer kicks in. evaluating_ = false; return; case PLAN_ACTIVATION_INITIATING_ACTIVATION: case PLAN_ACTIVATION_OTASP: case PLAN_ACTIVATION_TRYING_OTASP: { switch (network->activation_state()) { case chromeos::ACTIVATION_STATE_ACTIVATED: if (network->failed_or_disconnected()) { new_state = GetNextReconnectState(state_); } else if (network->connected()) { if (network->restricted_pool()) { new_state = PLAN_ACTIVATION_SHOWING_PAYMENT; } else { new_state = PLAN_ACTIVATION_DONE; } } break; case chromeos::ACTIVATION_STATE_PARTIALLY_ACTIVATED: if (network->connected()) { if (network->restricted_pool()) new_state = PLAN_ACTIVATION_SHOWING_PAYMENT; } else { new_state = GetNextReconnectState(state_); } break; case chromeos::ACTIVATION_STATE_NOT_ACTIVATED: case chromeos::ACTIVATION_STATE_ACTIVATING: // Wait in this state until activation state changes. break; default: break; } break; } case PLAN_ACTIVATION_RECONNECTING_OTASP_TRY: case PLAN_ACTIVATION_RECONNECTING: { if (network->connected()) { // Make sure other networks are not interfering with our detection of // restricted pool. DisableOtherNetworks(); // Wait until the service shows up and gets activated. switch (network->activation_state()) { case chromeos::ACTIVATION_STATE_PARTIALLY_ACTIVATED: case chromeos::ACTIVATION_STATE_ACTIVATED: if (network->connectivity_state() == chromeos::CONN_STATE_NONE) { LOG(WARNING) << "No connectivity for device " << network->service_path().c_str(); // If we are connected but there is no connectivity at all, // restart the whole process again. if (activation_attempt_ < kMaxActivationAttempt) { activation_attempt_++; LOG(WARNING) << "Reconnect attempt #" << activation_attempt_; ForceReconnect(network, kFailedReconnectDelayMS); evaluating_ = false; return; } else { new_state = PLAN_ACTIVATION_ERROR; UMA_HISTOGRAM_COUNTS("Cellular.ActivationRetryFailure", 1); error_description = GetErrorMessage(kFailedConnectivity); } } else if (network->connectivity_state() == chromeos::CONN_STATE_RESTRICTED) { // If we have already received payment, don't show the payment // page again. We should try to reconnect after some time instead. new_state = PLAN_ACTIVATION_SHOWING_PAYMENT; } else if (network->activation_state() == chromeos::ACTIVATION_STATE_ACTIVATED) { new_state = PLAN_ACTIVATION_DONE; } break; default: break; } } else if (NeedsReconnecting(network, &new_state, &error_description)) { evaluating_ = false; return; } break; } case PLAN_ACTIVATION_RECONNECTING_OTASP: { if (network->connected()) { // Make sure other networks are not interfering with our detection of // restricted pool. DisableOtherNetworks(); // Wait until the service shows up and gets activated. switch (network->activation_state()) { case chromeos::ACTIVATION_STATE_PARTIALLY_ACTIVATED: case chromeos::ACTIVATION_STATE_ACTIVATED: if (network->connectivity_state() == chromeos::CONN_STATE_NONE || network->connectivity_state() == chromeos::CONN_STATE_RESTRICTED) { LOG(WARNING) << "Still no connectivity after OTASP for device " << network->service_path().c_str(); // If we have already received payment, don't show the payment // page again. We should try to reconnect after some time instead. if (reconnect_wait_count_ < kMaxReconnectAttemptOTASP) { reconnect_wait_count_++; LOG(WARNING) << "OTASP reconnect attempt #" << reconnect_wait_count_; ForceReconnect(network, kPostPaymentReconnectDelayMS); evaluating_ = false; return; } else { new_state = PLAN_ACTIVATION_ERROR; UMA_HISTOGRAM_COUNTS("Cellular.PostPaymentConnectFailure", 1); error_description = GetErrorMessage(kFailedConnectivity); } } else if (network->connectivity_state() == chromeos::CONN_STATE_UNRESTRICTED) { new_state = PLAN_ACTIVATION_DONE; } break; default: break; } } else if (NeedsReconnecting(network, &new_state, &error_description)) { evaluating_ = false; return; } break; } case PLAN_ACTIVATION_PAGE_LOADING: break; // Just ignore all signals until the site confirms payment. case PLAN_ACTIVATION_SHOWING_PAYMENT: // Activation completed/failed, ignore network changes. case PLAN_ACTIVATION_DONE: case PLAN_ACTIVATION_ERROR: break; } if (new_state != PLAN_ACTIVATION_ERROR && GotActivationError(network, &error_description)) { // Check for this special case when we try to do activate partially // activated device. If that attempt failed, try to disconnect to clear the // state and reconnect again. if ((network->activation_state() == chromeos::ACTIVATION_STATE_PARTIALLY_ACTIVATED || network->activation_state() == chromeos::ACTIVATION_STATE_ACTIVATING) && (network->error() == chromeos::ERROR_NO_ERROR || network->error() == chromeos::ERROR_OTASP_FAILED) && network->state() == chromeos::STATE_ACTIVATION_FAILURE) { LOG(WARNING) << "Activation failure detected " << network->service_path().c_str(); switch (state_) { case PLAN_ACTIVATION_OTASP: case PLAN_ACTIVATION_RECONNECTING_OTASP: new_state = PLAN_ACTIVATION_DELAY_OTASP; break; case PLAN_ACTIVATION_TRYING_OTASP: new_state = PLAN_ACTIVATION_RECONNECTING_OTASP_TRY; break; case PLAN_ACTIVATION_INITIATING_ACTIVATION: new_state = PLAN_ACTIVATION_RECONNECTING; break; case PLAN_ACTIVATION_START: // We are just starting, so this must be previous activation attempt // failure. new_state = PLAN_ACTIVATION_TRYING_OTASP; break; case PLAN_ACTIVATION_DELAY_OTASP: case PLAN_ACTIVATION_RECONNECTING_OTASP_TRY: case PLAN_ACTIVATION_RECONNECTING: new_state = state_; break; default: new_state = PLAN_ACTIVATION_ERROR; break; } } else { LOG(WARNING) << "Unexpected activation failure for " << network->service_path().c_str(); new_state = PLAN_ACTIVATION_ERROR; } } if (new_state == PLAN_ACTIVATION_ERROR && !error_description.length()) error_description = GetErrorMessage(kErrorDefault); ChangeState(network, new_state, error_description); evaluating_ = false; } MobileSetupHandler::PlanActivationState MobileSetupHandler::GetNextReconnectState( MobileSetupHandler::PlanActivationState state) { switch (state) { case PLAN_ACTIVATION_INITIATING_ACTIVATION: return PLAN_ACTIVATION_RECONNECTING; case PLAN_ACTIVATION_OTASP: return PLAN_ACTIVATION_RECONNECTING_OTASP; case PLAN_ACTIVATION_TRYING_OTASP: return PLAN_ACTIVATION_RECONNECTING_OTASP_TRY; default: return PLAN_ACTIVATION_RECONNECTING; } } // Debugging helper function, will take it out at the end. const char* MobileSetupHandler::GetStateDescription( PlanActivationState state) { switch (state) { case PLAN_ACTIVATION_PAGE_LOADING: return "PAGE_LOADING"; case PLAN_ACTIVATION_START: return "ACTIVATION_START"; case PLAN_ACTIVATION_TRYING_OTASP: return "TRYING_OTASP"; case PLAN_ACTIVATION_RECONNECTING_OTASP_TRY: return "RECONNECTING_OTASP_TRY"; case PLAN_ACTIVATION_INITIATING_ACTIVATION: return "INITIATING_ACTIVATION"; case PLAN_ACTIVATION_RECONNECTING: return "RECONNECTING"; case PLAN_ACTIVATION_SHOWING_PAYMENT: return "SHOWING_PAYMENT"; case PLAN_ACTIVATION_START_OTASP: return "START_OTASP"; case PLAN_ACTIVATION_DELAY_OTASP: return "DELAY_OTASP"; case PLAN_ACTIVATION_OTASP: return "OTASP"; case PLAN_ACTIVATION_RECONNECTING_OTASP: return "RECONNECTING_OTASP"; case PLAN_ACTIVATION_DONE: return "DONE"; case PLAN_ACTIVATION_ERROR: return "ERROR"; } return "UNKNOWN"; } void MobileSetupHandler::CompleteActivation( chromeos::CellularNetwork* network) { // Remove observers, we are done with this page. chromeos::NetworkLibrary* lib = chromeos::CrosLibrary::Get()-> GetNetworkLibrary(); lib->RemoveNetworkManagerObserver(this); lib->RemoveObserverForAllNetworks(this); if (lib->IsLocked()) lib->Unlock(); // If we have successfully activated the connection, set autoconnect flag. if (network) network->SetAutoConnect(true); // Reactivate other types of connections if we have // shut them down previously. ReEnableOtherConnections(); } void MobileSetupHandler::UpdatePage( chromeos::CellularNetwork* network, const std::string& error_description) { DictionaryValue device_dict; if (network) GetDeviceInfo(network, &device_dict); device_dict.SetInteger("state", state_); if (error_description.length()) device_dict.SetString("error", error_description); web_ui_->CallJavascriptFunction( kJsDeviceStatusChangedHandler, device_dict); } void MobileSetupHandler::ChangeState(chromeos::CellularNetwork* network, PlanActivationState new_state, const std::string& error_description) { static bool first_time = true; if (state_ == new_state && !first_time) return; LOG(WARNING) << "Activation state flip old = " << GetStateDescription(state_) << ", new = " << GetStateDescription(new_state); first_time = false; // Pick action that should happen on leaving the old state. switch (state_) { case PLAN_ACTIVATION_RECONNECTING_OTASP_TRY: case PLAN_ACTIVATION_RECONNECTING: case PLAN_ACTIVATION_RECONNECTING_OTASP: if (reconnect_timer_.IsRunning()) { reconnect_timer_.Stop(); } break; default: break; } state_ = new_state; // Signal to JS layer that the state is changing. UpdatePage(network, error_description); // Pick action that should happen on entering the new state. switch (new_state) { case PLAN_ACTIVATION_START: break; case PLAN_ACTIVATION_DELAY_OTASP: { UMA_HISTOGRAM_COUNTS("Cellular.RetryOTASP", 1); scoped_refptr<TaskProxy> task = new TaskProxy(AsWeakPtr(), 0); BrowserThread::PostDelayedTask(BrowserThread::UI, FROM_HERE, NewRunnableMethod(task.get(), &TaskProxy::RetryOTASP), kOTASPRetryDelay); break; } case PLAN_ACTIVATION_INITIATING_ACTIVATION: case PLAN_ACTIVATION_TRYING_OTASP: case PLAN_ACTIVATION_OTASP: DCHECK(network); LOG(WARNING) << "Activating service " << network->service_path().c_str(); UMA_HISTOGRAM_COUNTS("Cellular.ActivationTry", 1); if (!network->StartActivation()) { UMA_HISTOGRAM_COUNTS("Cellular.ActivationFailure", 1); if (new_state == PLAN_ACTIVATION_OTASP) { ChangeState(network, PLAN_ACTIVATION_DELAY_OTASP, std::string()); } else { ChangeState(network, PLAN_ACTIVATION_ERROR, GetErrorMessage(kFailedConnectivity)); } } break; case PLAN_ACTIVATION_RECONNECTING_OTASP_TRY: case PLAN_ACTIVATION_RECONNECTING: case PLAN_ACTIVATION_RECONNECTING_OTASP: { // Start reconnect timer. This will ensure that we are not left in // limbo by the network library. if (!reconnect_timer_.IsRunning()) { reconnect_timer_.Start( base::TimeDelta::FromMilliseconds(kReconnectTimerDelayMS), this, &MobileSetupHandler::ReconnectTimerFired); } // Reset connection metrics and try to connect. reconnect_wait_count_ = 0; connection_retry_count_ = 0; connection_start_time_ = base::Time::Now(); ConnectToNetwork(network, kReconnectDelayMS); break; } case PLAN_ACTIVATION_PAGE_LOADING: return; case PLAN_ACTIVATION_SHOWING_PAYMENT: // Fix for fix SSL for the walled gardens where cert chain verification // might not work. break; case PLAN_ACTIVATION_DONE: DCHECK(network); CompleteActivation(network); UMA_HISTOGRAM_COUNTS("Cellular.MobileSetupSucceeded", 1); break; case PLAN_ACTIVATION_ERROR: CompleteActivation(NULL); UMA_HISTOGRAM_COUNTS("Cellular.PlanFailed", 1); break; default: break; } } void MobileSetupHandler::ReEnableOtherConnections() { chromeos::NetworkLibrary* lib = chromeos::CrosLibrary::Get()-> GetNetworkLibrary(); if (reenable_ethernet_) { reenable_ethernet_ = false; lib->EnableEthernetNetworkDevice(true); } if (reenable_wifi_) { reenable_wifi_ = false; lib->EnableWifiNetworkDevice(true); } PrefService* prefs = g_browser_process->local_state(); if (reenable_cert_check_) { prefs->SetBoolean(prefs::kCertRevocationCheckingEnabled, true); reenable_cert_check_ = false; } } void MobileSetupHandler::SetupActivationProcess( chromeos::CellularNetwork* network) { if (!network) return; // Disable SSL cert checks since we will be doing this in // restricted pool. PrefService* prefs = g_browser_process->local_state(); if (!reenable_cert_check_ && prefs->GetBoolean( prefs::kCertRevocationCheckingEnabled)) { reenable_cert_check_ = true; prefs->SetBoolean(prefs::kCertRevocationCheckingEnabled, false); } chromeos::NetworkLibrary* lib = chromeos::CrosLibrary::Get()-> GetNetworkLibrary(); // Disable autoconnect to cellular network. network->SetAutoConnect(false); // Prevent any other network interference. DisableOtherNetworks(); lib->Lock(); } void MobileSetupHandler::DisableOtherNetworks() { chromeos::NetworkLibrary* lib = chromeos::CrosLibrary::Get()-> GetNetworkLibrary(); // Disable ethernet and wifi. if (lib->ethernet_enabled()) { reenable_ethernet_ = true; lib->EnableEthernetNetworkDevice(false); } if (lib->wifi_enabled()) { reenable_wifi_ = true; lib->EnableWifiNetworkDevice(false); } } bool MobileSetupHandler::GotActivationError( chromeos::CellularNetwork* network, std::string* error) { DCHECK(network); bool got_error = false; const char* error_code = kErrorDefault; // This is the magic for detection of errors in during activation process. if (network->state() == chromeos::STATE_FAILURE && network->error() == chromeos::ERROR_AAA_FAILED) { if (network->activation_state() == chromeos::ACTIVATION_STATE_PARTIALLY_ACTIVATED) { error_code = kErrorBadConnectionPartial; } else if (network->activation_state() == chromeos::ACTIVATION_STATE_ACTIVATED) { if (network->roaming_state() == chromeos::ROAMING_STATE_HOME) { error_code = kErrorBadConnectionActivated; } else if (network->roaming_state() == chromeos::ROAMING_STATE_ROAMING) { error_code = kErrorRoamingOnConnection; } } got_error = true; } else if (network->state() == chromeos::STATE_ACTIVATION_FAILURE) { if (network->error() == chromeos::ERROR_NEED_EVDO) { if (network->activation_state() == chromeos::ACTIVATION_STATE_PARTIALLY_ACTIVATED) error_code = kErrorNoEVDO; } else if (network->error() == chromeos::ERROR_NEED_HOME_NETWORK) { if (network->activation_state() == chromeos::ACTIVATION_STATE_NOT_ACTIVATED) { error_code = kErrorRoamingActivation; } else if (network->activation_state() == chromeos::ACTIVATION_STATE_PARTIALLY_ACTIVATED) { error_code = kErrorRoamingPartiallyActivated; } } got_error = true; } if (got_error) *error = GetErrorMessage(error_code); return got_error; } void MobileSetupHandler::GetDeviceInfo(chromeos::CellularNetwork* network, DictionaryValue* value) { DCHECK(network); chromeos::NetworkLibrary* cros = chromeos::CrosLibrary::Get()->GetNetworkLibrary(); if (!cros) return; value->SetString("carrier", network->name()); value->SetString("payment_url", network->payment_url()); const chromeos::NetworkDevice* device = cros->FindNetworkDeviceByPath(network->device_path()); if (device) { value->SetString("MEID", device->meid()); value->SetString("IMEI", device->imei()); value->SetString("MDN", device->mdn()); } } std::string MobileSetupHandler::GetErrorMessage(const std::string& code) { if (!cellular_config_.get()) return ""; return cellular_config_->GetErrorMessage(code); } void MobileSetupHandler::LoadCellularConfig() { static bool config_loaded = false; if (config_loaded) return; config_loaded = true; // Load partner customization startup manifest if it is available. FilePath config_path(kCellularConfigPath); bool config_exists = false; { // Reading config file causes us to do blocking IO on UI thread. // Temporarily allow it until we fix http://crosbug.com/11535 base::ThreadRestrictions::ScopedAllowIO allow_io; config_exists = file_util::PathExists(config_path); } if (config_exists) { scoped_ptr<CellularConfigDocument> config(new CellularConfigDocument()); bool config_loaded = config->LoadFromFile(config_path); if (config_loaded) { LOG(INFO) << "Cellular config file loaded: " << kCellularConfigPath; // lock cellular_config_.reset(config.release()); } else { LOG(ERROR) << "Error loading cellular config file: " << kCellularConfigPath; } } } //////////////////////////////////////////////////////////////////////////////// // // MobileSetupUI // //////////////////////////////////////////////////////////////////////////////// MobileSetupUI::MobileSetupUI(TabContents* contents) : WebUI(contents) { chromeos::CellularNetwork* network = GetCellularNetwork(); std::string service_path = network ? network->service_path() : std::string(); MobileSetupHandler* handler = new MobileSetupHandler(service_path); AddMessageHandler((handler)->Attach(this)); handler->Init(contents); MobileSetupUIHTMLSource* html_source = new MobileSetupUIHTMLSource(service_path); // Set up the chrome://mobilesetup/ source. contents->profile()->GetChromeURLDataManager()->AddDataSource(html_source); }