blob: 8c0b5f028fc349f625b8349acc35506b2c3d5134 [file] [log] [blame]
// Copyright (c) 2012 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 <stddef.h>
#include <memory>
#include <algorithm>
#include <map>
#include <string>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ref_counted_memory.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/mobile/mobile_activator.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/browser_resources.h"
#include "chrome/grit/generated_resources.h"
#include "chromeos/network/device_state.h"
#include "chromeos/network/network_configuration_handler.h"
#include "chromeos/network/network_event_log.h"
#include "chromeos/network/network_state.h"
#include "chromeos/network/network_state_handler.h"
#include "chromeos/network/network_state_handler_observer.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/url_data_source.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "content/public/browser/web_ui_message_handler.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/webui/jstemplate_builder.h"
#include "ui/base/webui/web_ui_util.h"
#include "url/gurl.h"
using chromeos::MobileActivator;
using chromeos::NetworkHandler;
using chromeos::NetworkState;
using content::BrowserThread;
using content::RenderViewHost;
using content::WebContents;
using content::WebUIMessageHandler;
namespace {
// Host page JS API function names.
const char kJsApiStartActivation[] = "startActivation";
const char kJsApiSetTransactionStatus[] = "setTransactionStatus";
const char kJsApiPaymentPortalLoad[] = "paymentPortalLoad";
const char kJsGetDeviceInfo[] = "getDeviceInfo";
const char kJsApiResultOK[] = "ok";
const char kJsDeviceStatusChangedCallback[] =
"mobile.MobileSetup.deviceStateChanged";
const char kJsPortalFrameLoadFailedCallback[] =
"mobile.MobileSetup.portalFrameLoadError";
const char kJsPortalFrameLoadCompletedCallback[] =
"mobile.MobileSetup.portalFrameLoadCompleted";
const char kJsGetDeviceInfoCallback[] =
"mobile.MobileSetupPortal.onGotDeviceInfo";
const char kJsConnectivityChangedCallback[] =
"mobile.MobileSetupPortal.onConnectivityChanged";
void DataRequestFailed(
const std::string& service_path,
const content::URLDataSource::GotDataCallback& callback) {
NET_LOG_ERROR("Data Request Failed for Mobile Setup", service_path);
scoped_refptr<base::RefCountedBytes> html_bytes(new base::RefCountedBytes);
callback.Run(html_bytes.get());
}
// Converts the network properties into a JS object.
void GetDeviceInfo(const base::DictionaryValue& properties,
base::DictionaryValue* value) {
std::string name;
properties.GetStringWithoutPathExpansion(shill::kNameProperty, &name);
std::string activation_type;
properties.GetStringWithoutPathExpansion(
shill::kActivationTypeProperty,
&activation_type);
const base::DictionaryValue* payment_dict;
std::string payment_url, post_method, post_data;
if (properties.GetDictionaryWithoutPathExpansion(
shill::kPaymentPortalProperty, &payment_dict)) {
payment_dict->GetStringWithoutPathExpansion(
shill::kPaymentPortalURL, &payment_url);
payment_dict->GetStringWithoutPathExpansion(
shill::kPaymentPortalMethod, &post_method);
payment_dict->GetStringWithoutPathExpansion(
shill::kPaymentPortalPostData, &post_data);
}
value->SetString("activation_type", activation_type);
value->SetString("carrier", name);
value->SetString("payment_url", payment_url);
if (base::LowerCaseEqualsASCII(post_method, "post") && !post_data.empty())
value->SetString("post_data", post_data);
// Use the cached DeviceState properties.
std::string device_path;
if (!properties.GetStringWithoutPathExpansion(
shill::kDeviceProperty, &device_path) ||
device_path.empty()) {
return;
}
const chromeos::DeviceState* device =
NetworkHandler::Get()->network_state_handler()->GetDeviceState(
device_path);
if (!device)
return;
value->SetString("MEID", device->meid());
value->SetString("IMEI", device->imei());
value->SetString("MDN", device->mdn());
}
void SetActivationStateAndError(MobileActivator::PlanActivationState state,
const std::string& error_description,
base::DictionaryValue* value) {
value->SetInteger("state", state);
if (!error_description.empty())
value->SetString("error", error_description);
}
} // namespace
class MobileSetupUIHTMLSource : public content::URLDataSource {
public:
MobileSetupUIHTMLSource();
// content::URLDataSource implementation.
std::string GetSource() const override;
void StartDataRequest(
const std::string& path,
const content::ResourceRequestInfo::WebContentsGetter& wc_getter,
const content::URLDataSource::GotDataCallback& callback) override;
std::string GetMimeType(const std::string&) const override {
return "text/html";
}
bool ShouldAddContentSecurityPolicy() const override { return false; }
bool AllowCaching() const override {
// Should not be cached to reflect dynamically-generated contents that may
// depend on current settings.
return false;
}
private:
~MobileSetupUIHTMLSource() override {}
void GetPropertiesAndStartDataRequest(
const content::URLDataSource::GotDataCallback& callback,
const std::string& service_path,
const base::DictionaryValue& properties);
void GetPropertiesFailure(
const content::URLDataSource::GotDataCallback& callback,
const std::string& service_path,
const std::string& error_name,
std::unique_ptr<base::DictionaryValue> error_data);
base::WeakPtrFactory<MobileSetupUIHTMLSource> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(MobileSetupUIHTMLSource);
};
// The handler for Javascript messages related to the "register" view.
class MobileSetupHandler
: public WebUIMessageHandler,
public MobileActivator::Observer,
public chromeos::NetworkStateHandlerObserver,
public base::SupportsWeakPtr<MobileSetupHandler> {
public:
MobileSetupHandler();
~MobileSetupHandler() override;
// WebUIMessageHandler implementation.
void RegisterMessages() override;
private:
enum Type {
TYPE_UNDETERMINED,
// The network is not yet activated, and the webui is in activation flow.
TYPE_ACTIVATION,
// The network is activated, the webui displays network portal.
TYPE_PORTAL,
// Same as TYPE_PORTAL, but the network technology is LTE. The webui is
// additionally aware of network manager state and whether the portal can be
// reached.
TYPE_PORTAL_LTE
};
// MobileActivator::Observer.
void OnActivationStateChanged(const NetworkState* network,
MobileActivator::PlanActivationState new_state,
const std::string& error_description) override;
// Callbacks for NetworkConfigurationHandler::GetProperties.
void GetPropertiesAndCallStatusChanged(
MobileActivator::PlanActivationState state,
const std::string& error_description,
const std::string& service_path,
const base::DictionaryValue& properties);
void GetPropertiesAndCallGetDeviceInfo(
const std::string& service_path,
const base::DictionaryValue& properties);
void GetPropertiesFailure(const std::string& service_path,
const std::string& callback_name,
const std::string& error_name,
std::unique_ptr<base::DictionaryValue> error_data);
// Handlers for JS WebUI messages.
void HandleSetTransactionStatus(const base::ListValue* args);
void HandleStartActivation(const base::ListValue* args);
void HandlePaymentPortalLoad(const base::ListValue* args);
void HandleGetDeviceInfo(const base::ListValue* args);
// NetworkStateHandlerObserver implementation.
void NetworkConnectionStateChanged(const NetworkState* network) override;
void DefaultNetworkChanged(const NetworkState* default_network) override;
// Updates |lte_portal_reachable_| for lte network |network| and notifies
// webui of the new state if the reachability changed or |force_notification|
// is set.
void UpdatePortalReachability(const NetworkState* network,
bool force_notification);
// Sends message to host registration page with system/user info data.
void SendDeviceInfo();
// Type of the mobilesetup webui deduced from received messages.
Type type_;
// Whether portal page for lte networks can be reached in current network
// connection state. This value is reflected in portal webui for lte networks.
// Initial value is true.
bool lte_portal_reachable_;
base::WeakPtrFactory<MobileSetupHandler> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(MobileSetupHandler);
};
////////////////////////////////////////////////////////////////////////////////
//
// MobileSetupUIHTMLSource
//
////////////////////////////////////////////////////////////////////////////////
MobileSetupUIHTMLSource::MobileSetupUIHTMLSource()
: weak_ptr_factory_(this) {
}
std::string MobileSetupUIHTMLSource::GetSource() const {
return chrome::kChromeUIMobileSetupHost;
}
void MobileSetupUIHTMLSource::StartDataRequest(
const std::string& path,
const content::ResourceRequestInfo::WebContentsGetter& wc_getter,
const content::URLDataSource::GotDataCallback& callback) {
NetworkHandler::Get()->network_configuration_handler()->GetShillProperties(
path,
base::Bind(&MobileSetupUIHTMLSource::GetPropertiesAndStartDataRequest,
weak_ptr_factory_.GetWeakPtr(), callback),
base::Bind(&MobileSetupUIHTMLSource::GetPropertiesFailure,
weak_ptr_factory_.GetWeakPtr(), callback, path));
}
void MobileSetupUIHTMLSource::GetPropertiesAndStartDataRequest(
const content::URLDataSource::GotDataCallback& callback,
const std::string& service_path,
const base::DictionaryValue& properties) {
const base::DictionaryValue* payment_dict;
std::string name, usage_url, activation_state, payment_url;
if (!properties.GetStringWithoutPathExpansion(
shill::kNameProperty, &name) ||
!properties.GetStringWithoutPathExpansion(
shill::kUsageURLProperty, &usage_url) ||
!properties.GetStringWithoutPathExpansion(
shill::kActivationStateProperty, &activation_state) ||
!properties.GetDictionaryWithoutPathExpansion(
shill::kPaymentPortalProperty, &payment_dict) ||
!payment_dict->GetStringWithoutPathExpansion(
shill::kPaymentPortalURL, &payment_url)) {
DataRequestFailed(service_path, callback);
return;
}
if (payment_url.empty() && usage_url.empty() &&
activation_state != shill::kActivationStateActivated) {
DataRequestFailed(service_path, callback);
return;
}
NET_LOG_EVENT("Starting mobile setup", service_path);
base::DictionaryValue strings;
strings.SetString("connecting_header",
l10n_util::GetStringFUTF16(IDS_MOBILE_CONNECTING_HEADER,
base::UTF8ToUTF16(name)));
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("portal_unreachable_header",
l10n_util::GetStringUTF16(IDS_MOBILE_NO_CONNECTION_HEADER));
strings.SetString("invalid_device_info_header",
l10n_util::GetStringUTF16(IDS_MOBILE_INVALID_DEVICE_INFO_HEADER));
strings.SetString("title", l10n_util::GetStringUTF16(IDS_MOBILE_SETUP_TITLE));
strings.SetString("close_button",
l10n_util::GetStringUTF16(IDS_CLOSE));
strings.SetString("cancel_button",
l10n_util::GetStringUTF16(IDS_CANCEL));
strings.SetString("ok_button",
l10n_util::GetStringUTF16(IDS_OK));
const std::string& app_locale = g_browser_process->GetApplicationLocale();
webui::SetLoadTimeDataDefaults(app_locale, &strings);
// The webui differs based on whether the network is activated or not. If the
// network is activated, the webui goes straight to portal. Otherwise the
// webui is used for activation flow.
std::string full_html;
if (activation_state == shill::kActivationStateActivated) {
static const base::StringPiece html_for_activated(
ui::ResourceBundle::GetSharedInstance().GetRawDataResource(
IDR_MOBILE_SETUP_PORTAL_PAGE_HTML));
full_html = webui::GetI18nTemplateHtml(html_for_activated, &strings);
} else {
static const base::StringPiece html_for_non_activated(
ui::ResourceBundle::GetSharedInstance().GetRawDataResource(
IDR_MOBILE_SETUP_PAGE_HTML));
full_html = webui::GetI18nTemplateHtml(html_for_non_activated, &strings);
}
callback.Run(base::RefCountedString::TakeString(&full_html));
}
void MobileSetupUIHTMLSource::GetPropertiesFailure(
const content::URLDataSource::GotDataCallback& callback,
const std::string& service_path,
const std::string& error_name,
std::unique_ptr<base::DictionaryValue> error_data) {
DataRequestFailed(service_path, callback);
}
////////////////////////////////////////////////////////////////////////////////
//
// MobileSetupHandler
//
////////////////////////////////////////////////////////////////////////////////
MobileSetupHandler::MobileSetupHandler()
: type_(TYPE_UNDETERMINED),
lte_portal_reachable_(true),
weak_ptr_factory_(this) {
}
MobileSetupHandler::~MobileSetupHandler() {
if (type_ == TYPE_ACTIVATION) {
MobileActivator::GetInstance()->RemoveObserver(this);
MobileActivator::GetInstance()->TerminateActivation();
} else if (type_ == TYPE_PORTAL_LTE) {
NetworkHandler::Get()->network_state_handler()->RemoveObserver(this,
FROM_HERE);
}
}
void MobileSetupHandler::OnActivationStateChanged(
const NetworkState* network,
MobileActivator::PlanActivationState state,
const std::string& error_description) {
DCHECK_EQ(TYPE_ACTIVATION, type_);
if (!web_ui())
return;
if (!network) {
base::DictionaryValue device_dict;
SetActivationStateAndError(state, error_description, &device_dict);
web_ui()->CallJavascriptFunctionUnsafe(kJsDeviceStatusChangedCallback,
device_dict);
return;
}
NetworkHandler::Get()->network_configuration_handler()->GetShillProperties(
network->path(),
base::Bind(&MobileSetupHandler::GetPropertiesAndCallStatusChanged,
weak_ptr_factory_.GetWeakPtr(), state, error_description),
base::Bind(&MobileSetupHandler::GetPropertiesFailure,
weak_ptr_factory_.GetWeakPtr(), network->path(),
kJsDeviceStatusChangedCallback));
}
void MobileSetupHandler::GetPropertiesAndCallStatusChanged(
MobileActivator::PlanActivationState state,
const std::string& error_description,
const std::string& service_path,
const base::DictionaryValue& properties) {
base::DictionaryValue device_dict;
GetDeviceInfo(properties, &device_dict);
SetActivationStateAndError(state, error_description, &device_dict);
web_ui()->CallJavascriptFunctionUnsafe(kJsDeviceStatusChangedCallback,
device_dict);
}
void MobileSetupHandler::RegisterMessages() {
web_ui()->RegisterMessageCallback(
kJsApiStartActivation,
base::BindRepeating(&MobileSetupHandler::HandleStartActivation,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kJsApiSetTransactionStatus,
base::BindRepeating(&MobileSetupHandler::HandleSetTransactionStatus,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kJsApiPaymentPortalLoad,
base::BindRepeating(&MobileSetupHandler::HandlePaymentPortalLoad,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kJsGetDeviceInfo,
base::BindRepeating(&MobileSetupHandler::HandleGetDeviceInfo,
base::Unretained(this)));
}
void MobileSetupHandler::HandleStartActivation(const base::ListValue* args) {
DCHECK_EQ(TYPE_UNDETERMINED, type_);
if (!web_ui())
return;
std::string path = web_ui()->GetWebContents()->GetURL().path();
if (path.empty())
return;
LOG(WARNING) << "Starting activation for service " << path;
type_ = TYPE_ACTIVATION;
MobileActivator::GetInstance()->AddObserver(this);
MobileActivator::GetInstance()->InitiateActivation(path.substr(1));
}
void MobileSetupHandler::HandleSetTransactionStatus(
const base::ListValue* args) {
DCHECK_EQ(TYPE_ACTIVATION, type_);
if (!web_ui())
return;
const size_t kSetTransactionStatusParamCount = 1;
if (args->GetSize() != kSetTransactionStatusParamCount)
return;
// Get change callback function name.
std::string status;
if (!args->GetString(0, &status))
return;
MobileActivator::GetInstance()->OnSetTransactionStatus(
base::LowerCaseEqualsASCII(status, kJsApiResultOK));
}
void MobileSetupHandler::HandlePaymentPortalLoad(const base::ListValue* args) {
// Only activation flow webui is interested in these events.
if (type_ != TYPE_ACTIVATION || !web_ui())
return;
const size_t kPaymentPortalLoadParamCount = 1;
if (args->GetSize() != kPaymentPortalLoadParamCount)
return;
// Get change callback function name.
std::string result;
if (!args->GetString(0, &result))
return;
MobileActivator::GetInstance()->OnPortalLoaded(
base::LowerCaseEqualsASCII(result, kJsApiResultOK));
}
void MobileSetupHandler::HandleGetDeviceInfo(const base::ListValue* args) {
DCHECK_NE(TYPE_ACTIVATION, type_);
if (!web_ui())
return;
std::string path = web_ui()->GetWebContents()->GetURL().path();
if (path.empty())
return;
chromeos::NetworkStateHandler* nsh =
NetworkHandler::Get()->network_state_handler();
// TODO: Figure out why the path has an extra '/' in the front. (e.g. It is
// '//service/5' instead of '/service/5'.
const NetworkState* network = nsh->GetNetworkState(path.substr(1));
if (!network) {
web_ui()->GetWebContents()->Close();
return;
}
// If this is the initial call, update the network status and start observing
// network changes, but only for LTE networks. The other networks should
// ignore network status.
if (type_ == TYPE_UNDETERMINED) {
if (network->network_technology() == shill::kNetworkTechnologyLte ||
network->network_technology() == shill::kNetworkTechnologyLteAdvanced) {
type_ = TYPE_PORTAL_LTE;
nsh->AddObserver(this, FROM_HERE);
// Update the network status and notify the webui. This is the initial
// network state so the webui should be notified no matter what.
UpdatePortalReachability(network,
true /* force notification */);
} else {
type_ = TYPE_PORTAL;
// For non-LTE networks network state is ignored, so report the portal is
// reachable, so it gets shown.
web_ui()->CallJavascriptFunctionUnsafe(kJsConnectivityChangedCallback,
base::Value(true));
}
}
NetworkHandler::Get()->network_configuration_handler()->GetShillProperties(
network->path(),
base::Bind(&MobileSetupHandler::GetPropertiesAndCallGetDeviceInfo,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&MobileSetupHandler::GetPropertiesFailure,
weak_ptr_factory_.GetWeakPtr(), network->path(),
kJsGetDeviceInfoCallback));
}
void MobileSetupHandler::GetPropertiesAndCallGetDeviceInfo(
const std::string& service_path,
const base::DictionaryValue& properties) {
base::DictionaryValue device_info;
GetDeviceInfo(properties, &device_info);
web_ui()->CallJavascriptFunctionUnsafe(kJsGetDeviceInfoCallback, device_info);
}
void MobileSetupHandler::GetPropertiesFailure(
const std::string& service_path,
const std::string& callback_name,
const std::string& error_name,
std::unique_ptr<base::DictionaryValue> error_data) {
NET_LOG_ERROR("MobileActivator GetProperties Failed: " + error_name,
service_path);
// Invoke |callback_name| with an empty dictionary.
base::DictionaryValue device_dict;
web_ui()->CallJavascriptFunctionUnsafe(callback_name, device_dict);
}
void MobileSetupHandler::DefaultNetworkChanged(
const NetworkState* default_network) {
if (!web_ui())
return;
std::string path = web_ui()->GetWebContents()->GetURL().path().substr(1);
if (path.empty())
return;
const NetworkState* network =
NetworkHandler::Get()->network_state_handler()->GetNetworkState(path);
if (!network) {
LOG(ERROR) << "Service path lost";
web_ui()->GetWebContents()->Close();
return;
}
UpdatePortalReachability(network, false /* do not force notification */);
}
void MobileSetupHandler::NetworkConnectionStateChanged(
const NetworkState* network) {
if (!web_ui())
return;
std::string path = web_ui()->GetWebContents()->GetURL().path().substr(1);
if (path.empty() || path != network->path())
return;
UpdatePortalReachability(network, false /* do not force notification */);
}
void MobileSetupHandler::UpdatePortalReachability(
const NetworkState* network,
bool force_notification) {
DCHECK(web_ui());
DCHECK_EQ(type_, TYPE_PORTAL_LTE);
chromeos::NetworkStateHandler* nsh =
NetworkHandler::Get()->network_state_handler();
bool portal_reachable =
(network->IsConnectedState() ||
(nsh->DefaultNetwork() &&
nsh->DefaultNetwork()->connection_state() == shill::kStateOnline));
if (force_notification || portal_reachable != lte_portal_reachable_) {
web_ui()->CallJavascriptFunctionUnsafe(kJsConnectivityChangedCallback,
base::Value(portal_reachable));
}
lte_portal_reachable_ = portal_reachable;
}
////////////////////////////////////////////////////////////////////////////////
//
// MobileSetupUI
//
////////////////////////////////////////////////////////////////////////////////
MobileSetupUI::MobileSetupUI(content::WebUI* web_ui)
: WebUIController(web_ui) {
web_ui->AddMessageHandler(std::make_unique<MobileSetupHandler>());
MobileSetupUIHTMLSource* html_source = new MobileSetupUIHTMLSource();
// Set up the chrome://mobilesetup/ source.
Profile* profile = Profile::FromWebUI(web_ui);
content::URLDataSource::Add(profile, html_source);
content::WebContentsObserver::Observe(web_ui->GetWebContents());
}
void MobileSetupUI::DidFinishNavigation(
content::NavigationHandle* navigation_handle) {
if (!navigation_handle->HasCommitted() ||
navigation_handle->GetRenderFrameHost()->GetFrameName() !=
"paymentForm") {
return;
}
if (navigation_handle->IsErrorPage()) {
base::Value result_value(-navigation_handle->GetNetErrorCode());
web_ui()->CallJavascriptFunctionUnsafe(kJsPortalFrameLoadFailedCallback,
result_value);
return;
}
web_ui()->CallJavascriptFunctionUnsafe(kJsPortalFrameLoadCompletedCallback);
}