// Copyright 2014 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 "athena/system/network_selector.h" #include "base/memory/weak_ptr.h" #include "base/strings/utf_string_conversions.h" #include "chromeos/network/network_configuration_handler.h" #include "chromeos/network/network_connection_handler.h" #include "chromeos/network/network_handler.h" #include "chromeos/network/network_profile_handler.h" #include "chromeos/network/network_state.h" #include "chromeos/network/network_state_handler.h" #include "chromeos/network/network_state_handler_observer.h" #include "chromeos/network/network_type_pattern.h" #include "third_party/cros_system_api/dbus/service_constants.h" #include "ui/aura/window.h" #include "ui/chromeos/network/network_icon.h" #include "ui/chromeos/network/network_info.h" #include "ui/chromeos/network/network_list.h" #include "ui/chromeos/network/network_list_delegate.h" #include "ui/compositor/layer.h" #include "ui/gfx/canvas.h" #include "ui/gfx/font.h" #include "ui/gfx/font_list.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/text_constants.h" #include "ui/views/background.h" #include "ui/views/border.h" #include "ui/views/controls/button/blue_button.h" #include "ui/views/controls/button/button.h" #include "ui/views/controls/image_view.h" #include "ui/views/controls/label.h" #include "ui/views/controls/scroll_view.h" #include "ui/views/controls/scrollbar/overlay_scroll_bar.h" #include "ui/views/controls/textfield/textfield.h" #include "ui/views/layout/box_layout.h" #include "ui/views/layout/fill_layout.h" #include "ui/views/widget/widget.h" using chromeos::NetworkConfigurationHandler; using chromeos::NetworkConnectionHandler; using chromeos::NetworkHandler; using chromeos::NetworkProfileHandler; using chromeos::NetworkState; namespace { const int kBackgroundColor = SkColorSetARGB(0x7f, 0, 0, 0); // The View for the user to enter the password for connceting to a network. This // view also shows an error message if the network connection fails. class PasswordView : public views::View, public views::ButtonListener { public: PasswordView(const ui::NetworkInfo& network, const base::Callback& callback, views::View* parent_container) : network_(network), callback_(callback), parent_container_(parent_container), connect_(NULL), cancel_(NULL), textfield_(NULL), error_msg_(NULL), weak_ptr_(this) { const int kHorizontal = 5; const int kVertical = 0; const int kPadding = 0; views::BoxLayout* layout = new views::BoxLayout( views::BoxLayout::kVertical, kHorizontal, kVertical, kPadding); layout->set_main_axis_alignment( views::BoxLayout::MAIN_AXIS_ALIGNMENT_START); layout->set_cross_axis_alignment( views::BoxLayout::CROSS_AXIS_ALIGNMENT_STRETCH); SetLayoutManager(layout); views::View* container = new views::View; layout = new views::BoxLayout( views::BoxLayout::kHorizontal, kHorizontal, kVertical, kPadding); layout->set_main_axis_alignment( views::BoxLayout::MAIN_AXIS_ALIGNMENT_START); container->SetLayoutManager(layout); textfield_ = new views::Textfield(); textfield_->set_placeholder_text(base::ASCIIToUTF16("Password")); textfield_->SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD); textfield_->set_default_width_in_chars(35); container->AddChildView(textfield_); connect_ = new views::BlueButton(this, base::ASCIIToUTF16("Connect")); container->AddChildView(connect_); cancel_ = new views::LabelButton(this, base::ASCIIToUTF16("Cancel")); cancel_->SetHorizontalAlignment(gfx::ALIGN_CENTER); container->AddChildView(cancel_); AddChildView(container); } virtual ~PasswordView() {} private: void Close(bool successful) { callback_.Run(successful); } void OnKnownError(const std::string& error_name, scoped_ptr error_data) { std::string message; if (!error_data->GetString(chromeos::network_handler::kDbusErrorMessage, &message)) message = error_name; if (message.empty()) message = std::string("Unknown error."); if (!error_msg_) { error_msg_ = new views::Label(); error_msg_->SetFontList( error_msg_->font_list().Derive(0, gfx::Font::BOLD)); error_msg_->SetEnabledColor(SK_ColorRED); } error_msg_->SetText(base::UTF8ToUTF16(message)); if (!error_msg_->parent()) { AddChildView(error_msg_); InvalidateLayout(); parent_container_->Layout(); ScrollRectToVisible(error_msg_->bounds()); } connect_->SetEnabled(true); } void OnSetProfileSucceed(const base::string16& password) { base::DictionaryValue properties; properties.SetStringWithoutPathExpansion(shill::kPassphraseProperty, textfield_->text()); NetworkHandler::Get()->network_configuration_handler()->SetProperties( network_.service_path, properties, base::Bind(&PasswordView::OnSetPropertiesSucceed, weak_ptr_.GetWeakPtr()), base::Bind(&PasswordView::OnKnownError, weak_ptr_.GetWeakPtr())); } void OnSetPropertiesSucceed() { const bool check_error_state = false; NetworkHandler::Get()->network_connection_handler()->ConnectToNetwork( network_.service_path, base::Bind(&PasswordView::OnConnectionSucceed, weak_ptr_.GetWeakPtr()), base::Bind(&PasswordView::OnKnownError, weak_ptr_.GetWeakPtr()), check_error_state); } void OnConnectionSucceed() { Close(true); } // views::View: virtual void ViewHierarchyChanged( const views::View::ViewHierarchyChangedDetails& details) OVERRIDE { if (details.is_add && details.child == this) textfield_->RequestFocus(); } // views::ButtonListener: virtual void ButtonPressed(views::Button* sender, const ui::Event& event) OVERRIDE { if (sender == connect_) { if (error_msg_) { RemoveChildView(error_msg_); delete error_msg_; error_msg_ = NULL; } connect_->SetEnabled(false); NetworkHandler::Get()->network_configuration_handler()->SetNetworkProfile( network_.service_path, NetworkProfileHandler::GetSharedProfilePath(), base::Bind(&PasswordView::OnSetProfileSucceed, weak_ptr_.GetWeakPtr(), textfield_->text()), base::Bind(&PasswordView::OnKnownError, weak_ptr_.GetWeakPtr())); } else if (sender == cancel_) { Close(false); } else { NOTREACHED(); } } ui::NetworkInfo network_; base::Callback callback_; views::View* parent_container_; views::BlueButton* connect_; views::LabelButton* cancel_; views::Textfield* textfield_; views::Label* error_msg_; base::WeakPtrFactory weak_ptr_; DISALLOW_COPY_AND_ASSIGN(PasswordView); }; // A View that represents a single row in the network list. This row also // contains the View for taking password for password-protected networks. class NetworkRow : public views::View { public: NetworkRow(const ui::NetworkInfo& network, views::View* container) : network_(network), container_(container), weak_ptr_(this) { SetBorder(views::Border::CreateEmptyBorder(10, 5, 10, 5)); SetLayoutManager( new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 10)); Update(network); } virtual ~NetworkRow() {} void Update(const ui::NetworkInfo& network) { network_ = network; views::ImageView* icon = new views::ImageView(); icon->SetImage(network.image); icon->SetBounds(0, 0, network.image.width(), network.image.height()); views::Label* label = new views::Label(network.label); if (network.highlight) label->SetFontList(label->font_list().Derive(0, gfx::Font::BOLD)); AddChildView(icon); AddChildView(label); if (password_view_) AddChildView(password_view_.get()); } bool has_password_view() const { return password_view_; } private: void OnPasswordComplete(bool successful) { password_view_.reset(); InvalidateLayout(); container_->Layout(); ScrollRectToVisible(GetContentsBounds()); } void ShowPasswordView(const std::string& service_path) { const NetworkState* network = NetworkHandler::Get()->network_state_handler()->GetNetworkState( service_path); if (!network) return; // If this is not a wifi network that needs a password, then ignore. if (network->type() != shill::kTypeWifi || network->security() == shill::kSecurityNone) { return; } password_view_.reset(new PasswordView( network_, base::Bind(&NetworkRow::OnPasswordComplete, weak_ptr_.GetWeakPtr()), container_)); password_view_->set_owned_by_client(); AddChildView(password_view_.get()); PreferredSizeChanged(); container_->Layout(); ScrollRectToVisible(password_view_->bounds()); } void OnNetworkConnectionError(const std::string& service_path, const std::string& error_name, scoped_ptr error_data) { if (error_name == NetworkConnectionHandler::kErrorConnectCanceled) return; if (error_name == shill::kErrorBadPassphrase || error_name == NetworkConnectionHandler::kErrorPassphraseRequired || error_name == NetworkConnectionHandler::kErrorConfigurationRequired || error_name == NetworkConnectionHandler::kErrorAuthenticationRequired) { ShowPasswordView(service_path); } } void ActivateNetwork() { const chromeos::NetworkState* network = NetworkHandler::Get()->network_state_handler()->GetNetworkState( network_.service_path); if (!network) return; if (network->IsConnectedState()) { NetworkHandler::Get()->network_connection_handler()->DisconnectNetwork( network_.service_path, base::Closure(), chromeos::network_handler::ErrorCallback()); } else if (!network->IsConnectingState()) { // |network| is not connected, and not already trying to connect. NetworkHandler::Get()->network_connection_handler()->ConnectToNetwork( network_.service_path, base::Closure(), base::Bind(&NetworkRow::OnNetworkConnectionError, weak_ptr_.GetWeakPtr(), network_.service_path), false); } } // views::View: virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE { if (event->type() != ui::ET_MOUSE_PRESSED) return; ActivateNetwork(); event->SetHandled(); } virtual void OnGestureEvent(ui::GestureEvent* gesture) OVERRIDE { if (gesture->type() != ui::ET_GESTURE_TAP) return; ActivateNetwork(); gesture->SetHandled(); } ui::NetworkInfo network_; views::View* container_; scoped_ptr password_view_; base::WeakPtrFactory weak_ptr_; DISALLOW_COPY_AND_ASSIGN(NetworkRow); }; class NetworkSelector : public ui::NetworkListDelegate, public chromeos::NetworkStateHandlerObserver, public ui::EventHandler { public: explicit NetworkSelector(aura::Window* container) : background_view_(NULL), scroll_content_(NULL), scroller_(NULL), network_list_(this) { CreateWidget(container); CreateNetworkList(); NetworkHandler::Get()->network_state_handler()->RequestScan(); NetworkHandler::Get()->network_state_handler()->AddObserver(this, FROM_HERE); } virtual ~NetworkSelector() { NetworkHandler::Get()->network_state_handler()->RemoveObserver(this, FROM_HERE); } private: void CreateWidget(aura::Window* container) { views::Widget::InitParams params; params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS; params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; params.activatable = views::Widget::InitParams::ACTIVATABLE_DEFAULT; params.accept_events = true; params.bounds = gfx::Rect(container->bounds().size()); params.parent = container; params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; widget_.reset(new views::Widget()); widget_->Init(params); widget_->Show(); background_view_ = new views::View; background_view_->set_background( views::Background::CreateSolidBackground(kBackgroundColor)); background_view_->SetBorder( views::Border::CreateEmptyBorder(100, 300, 300, 300)); background_view_->SetLayoutManager(new views::FillLayout()); background_view_->set_target_handler(this); widget_->SetContentsView(background_view_); } void CreateNetworkList() { const int kListHeight = 500; scroller_ = new views::ScrollView(); scroller_->set_background( views::Background::CreateSolidBackground(SK_ColorWHITE)); scroller_->SetBounds(0, 0, 400, kListHeight); scroll_content_ = new views::View; scroll_content_->SetLayoutManager( new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0)); scroller_->SetContents(scroll_content_); scroller_->ClipHeightTo(kListHeight, kListHeight); scroller_->SetVerticalScrollBar(new views::OverlayScrollBar(false)); background_view_->AddChildView(scroller_); background_view_->Layout(); network_list_.set_content_view(scroll_content_); } void UpdateNetworkList() { network_list_.UpdateNetworkList(); } void Close() { delete this; } // ui::NetworkListDelegate: virtual views::View* CreateViewForNetwork( const ui::NetworkInfo& info) OVERRIDE { return new NetworkRow(info, background_view_); } virtual bool IsViewHovered(views::View* view) OVERRIDE { return static_cast(view)->has_password_view(); } virtual chromeos::NetworkTypePattern GetNetworkTypePattern() const OVERRIDE { return chromeos::NetworkTypePattern::NonVirtual(); } virtual void UpdateViewForNetwork(views::View* view, const ui::NetworkInfo& info) OVERRIDE { static_cast(view)->Update(info); } virtual views::Label* CreateInfoLabel() OVERRIDE { views::Label* label = new views::Label(); return label; } virtual void RelayoutScrollList() OVERRIDE { scroller_->Layout(); } // chromeos::NetworkStateHandlerObserver: virtual void NetworkListChanged() OVERRIDE { UpdateNetworkList(); } virtual void DeviceListChanged() OVERRIDE {} virtual void DefaultNetworkChanged( const chromeos::NetworkState* network) OVERRIDE {} virtual void NetworkConnectionStateChanged( const chromeos::NetworkState* network) OVERRIDE {} virtual void NetworkPropertiesUpdated( const chromeos::NetworkState* network) OVERRIDE {} // ui::EventHandler: virtual void OnMouseEvent(ui::MouseEvent* mouse) OVERRIDE { CHECK_EQ(background_view_, mouse->target()); if (mouse->type() == ui::ET_MOUSE_PRESSED && !mouse->handled()) { Close(); mouse->SetHandled(); } } virtual void OnGestureEvent(ui::GestureEvent* gesture) OVERRIDE { CHECK_EQ(background_view_, gesture->target()); if (gesture->type() == ui::ET_GESTURE_TAP && !gesture->handled()) { Close(); gesture->SetHandled(); } } scoped_ptr widget_; views::View* background_view_; views::View* scroll_content_; views::ScrollView* scroller_; views::View* connect_; ui::NetworkListView network_list_; DISALLOW_COPY_AND_ASSIGN(NetworkSelector); }; } // namespace namespace athena { void CreateNetworkSelector(aura::Window* container) { new NetworkSelector(container); } } // namespace athena