1// Copyright 2014 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 "athena/system/network_selector.h"
6
7#include "base/memory/weak_ptr.h"
8#include "base/strings/utf_string_conversions.h"
9#include "chromeos/network/network_configuration_handler.h"
10#include "chromeos/network/network_connection_handler.h"
11#include "chromeos/network/network_handler.h"
12#include "chromeos/network/network_profile_handler.h"
13#include "chromeos/network/network_state.h"
14#include "chromeos/network/network_state_handler.h"
15#include "chromeos/network/network_state_handler_observer.h"
16#include "chromeos/network/network_type_pattern.h"
17#include "third_party/cros_system_api/dbus/service_constants.h"
18#include "ui/aura/window.h"
19#include "ui/chromeos/network/network_icon.h"
20#include "ui/chromeos/network/network_info.h"
21#include "ui/chromeos/network/network_list.h"
22#include "ui/chromeos/network/network_list_delegate.h"
23#include "ui/compositor/layer.h"
24#include "ui/gfx/canvas.h"
25#include "ui/gfx/font.h"
26#include "ui/gfx/font_list.h"
27#include "ui/gfx/geometry/rect.h"
28#include "ui/gfx/text_constants.h"
29#include "ui/views/background.h"
30#include "ui/views/border.h"
31#include "ui/views/controls/button/blue_button.h"
32#include "ui/views/controls/button/button.h"
33#include "ui/views/controls/image_view.h"
34#include "ui/views/controls/label.h"
35#include "ui/views/controls/scroll_view.h"
36#include "ui/views/controls/scrollbar/overlay_scroll_bar.h"
37#include "ui/views/controls/textfield/textfield.h"
38#include "ui/views/layout/box_layout.h"
39#include "ui/views/layout/fill_layout.h"
40#include "ui/views/widget/widget.h"
41
42using chromeos::NetworkConfigurationHandler;
43using chromeos::NetworkConnectionHandler;
44using chromeos::NetworkHandler;
45using chromeos::NetworkProfileHandler;
46using chromeos::NetworkState;
47
48namespace {
49
50const int kBackgroundColor = SkColorSetARGB(0x7f, 0, 0, 0);
51
52// The View for the user to enter the password for connceting to a network. This
53// view also shows an error message if the network connection fails.
54class PasswordView : public views::View, public views::ButtonListener {
55 public:
56  PasswordView(const ui::NetworkInfo& network,
57               const base::Callback<void(bool)>& callback,
58               views::View* parent_container)
59      : network_(network),
60        callback_(callback),
61        parent_container_(parent_container),
62        connect_(NULL),
63        cancel_(NULL),
64        textfield_(NULL),
65        error_msg_(NULL),
66        weak_ptr_(this) {
67    const int kHorizontal = 5;
68    const int kVertical = 0;
69    const int kPadding = 0;
70
71    views::BoxLayout* layout = new views::BoxLayout(
72        views::BoxLayout::kVertical, kHorizontal, kVertical, kPadding);
73    layout->set_main_axis_alignment(
74        views::BoxLayout::MAIN_AXIS_ALIGNMENT_START);
75    layout->set_cross_axis_alignment(
76        views::BoxLayout::CROSS_AXIS_ALIGNMENT_STRETCH);
77    SetLayoutManager(layout);
78
79    views::View* container = new views::View;
80    layout = new views::BoxLayout(
81        views::BoxLayout::kHorizontal, kHorizontal, kVertical, kPadding);
82    layout->set_main_axis_alignment(
83        views::BoxLayout::MAIN_AXIS_ALIGNMENT_START);
84    container->SetLayoutManager(layout);
85
86    textfield_ = new views::Textfield();
87    textfield_->set_placeholder_text(base::ASCIIToUTF16("Password"));
88    textfield_->SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD);
89    textfield_->set_default_width_in_chars(35);
90    container->AddChildView(textfield_);
91
92    connect_ = new views::BlueButton(this, base::ASCIIToUTF16("Connect"));
93    container->AddChildView(connect_);
94
95    cancel_ = new views::LabelButton(this, base::ASCIIToUTF16("Cancel"));
96    cancel_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
97    container->AddChildView(cancel_);
98
99    AddChildView(container);
100  }
101
102  virtual ~PasswordView() {}
103
104 private:
105  void Close(bool successful) { callback_.Run(successful); }
106
107  void OnKnownError(const std::string& error_name,
108                    scoped_ptr<base::DictionaryValue> error_data) {
109    std::string message;
110    if (!error_data->GetString(chromeos::network_handler::kDbusErrorMessage,
111                               &message))
112      message = error_name;
113    if (message.empty())
114      message = std::string("Unknown error.");
115    if (!error_msg_) {
116      error_msg_ = new views::Label();
117      error_msg_->SetFontList(
118          error_msg_->font_list().Derive(0, gfx::Font::BOLD));
119      error_msg_->SetEnabledColor(SK_ColorRED);
120    }
121    error_msg_->SetText(base::UTF8ToUTF16(message));
122    if (!error_msg_->parent()) {
123      AddChildView(error_msg_);
124      InvalidateLayout();
125      parent_container_->Layout();
126      ScrollRectToVisible(error_msg_->bounds());
127    }
128    connect_->SetEnabled(true);
129  }
130
131  void OnSetProfileSucceed(const base::string16& password) {
132    base::DictionaryValue properties;
133    properties.SetStringWithoutPathExpansion(shill::kPassphraseProperty,
134                                             textfield_->text());
135    NetworkHandler::Get()->network_configuration_handler()->SetProperties(
136        network_.service_path,
137        properties,
138        base::Bind(&PasswordView::OnSetPropertiesSucceed,
139                   weak_ptr_.GetWeakPtr()),
140        base::Bind(&PasswordView::OnKnownError, weak_ptr_.GetWeakPtr()));
141  }
142
143  void OnSetPropertiesSucceed() {
144    const bool check_error_state = false;
145    NetworkHandler::Get()->network_connection_handler()->ConnectToNetwork(
146        network_.service_path,
147        base::Bind(&PasswordView::OnConnectionSucceed, weak_ptr_.GetWeakPtr()),
148        base::Bind(&PasswordView::OnKnownError, weak_ptr_.GetWeakPtr()),
149        check_error_state);
150  }
151
152  void OnConnectionSucceed() { Close(true); }
153
154  // views::View:
155  virtual void ViewHierarchyChanged(
156      const views::View::ViewHierarchyChangedDetails& details) OVERRIDE {
157    if (details.is_add && details.child == this)
158      textfield_->RequestFocus();
159  }
160
161  // views::ButtonListener:
162  virtual void ButtonPressed(views::Button* sender,
163                             const ui::Event& event) OVERRIDE {
164    if (sender == connect_) {
165      if (error_msg_) {
166        RemoveChildView(error_msg_);
167        delete error_msg_;
168        error_msg_ = NULL;
169      }
170      connect_->SetEnabled(false);
171      NetworkHandler::Get()->network_configuration_handler()->SetNetworkProfile(
172          network_.service_path,
173          NetworkProfileHandler::GetSharedProfilePath(),
174          base::Bind(&PasswordView::OnSetProfileSucceed,
175                     weak_ptr_.GetWeakPtr(),
176                     textfield_->text()),
177          base::Bind(&PasswordView::OnKnownError, weak_ptr_.GetWeakPtr()));
178    } else if (sender == cancel_) {
179      Close(false);
180    } else {
181      NOTREACHED();
182    }
183  }
184
185  ui::NetworkInfo network_;
186  base::Callback<void(bool)> callback_;
187  views::View* parent_container_;
188
189  views::BlueButton* connect_;
190  views::LabelButton* cancel_;
191  views::Textfield* textfield_;
192  views::Label* error_msg_;
193  base::WeakPtrFactory<PasswordView> weak_ptr_;
194
195  DISALLOW_COPY_AND_ASSIGN(PasswordView);
196};
197
198// A View that represents a single row in the network list. This row also
199// contains the View for taking password for password-protected networks.
200class NetworkRow : public views::View {
201 public:
202  NetworkRow(const ui::NetworkInfo& network, views::View* container)
203      : network_(network), container_(container), weak_ptr_(this) {
204    SetBorder(views::Border::CreateEmptyBorder(10, 5, 10, 5));
205    SetLayoutManager(
206        new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 10));
207    Update(network);
208  }
209
210  virtual ~NetworkRow() {}
211
212  void Update(const ui::NetworkInfo& network) {
213    network_ = network;
214    views::ImageView* icon = new views::ImageView();
215    icon->SetImage(network.image);
216    icon->SetBounds(0, 0, network.image.width(), network.image.height());
217
218    views::Label* label = new views::Label(network.label);
219    if (network.highlight)
220      label->SetFontList(label->font_list().Derive(0, gfx::Font::BOLD));
221    AddChildView(icon);
222    AddChildView(label);
223    if (password_view_)
224      AddChildView(password_view_.get());
225  }
226
227  bool has_password_view() const { return password_view_; }
228
229 private:
230  void OnPasswordComplete(bool successful) {
231    password_view_.reset();
232    InvalidateLayout();
233    container_->Layout();
234    ScrollRectToVisible(GetContentsBounds());
235  }
236
237  void ShowPasswordView(const std::string& service_path) {
238    const NetworkState* network =
239        NetworkHandler::Get()->network_state_handler()->GetNetworkState(
240            service_path);
241    if (!network)
242      return;
243
244    // If this is not a wifi network that needs a password, then ignore.
245    if (network->type() != shill::kTypeWifi ||
246        network->security() == shill::kSecurityNone) {
247      return;
248    }
249
250    password_view_.reset(new PasswordView(
251        network_,
252        base::Bind(&NetworkRow::OnPasswordComplete, weak_ptr_.GetWeakPtr()),
253        container_));
254    password_view_->set_owned_by_client();
255    AddChildView(password_view_.get());
256    PreferredSizeChanged();
257    container_->Layout();
258    ScrollRectToVisible(password_view_->bounds());
259  }
260
261  void OnNetworkConnectionError(const std::string& service_path,
262                                const std::string& error_name,
263                                scoped_ptr<base::DictionaryValue> error_data) {
264    if (error_name == NetworkConnectionHandler::kErrorConnectCanceled)
265      return;
266    if (error_name == shill::kErrorBadPassphrase ||
267        error_name == NetworkConnectionHandler::kErrorPassphraseRequired ||
268        error_name == NetworkConnectionHandler::kErrorConfigurationRequired ||
269        error_name == NetworkConnectionHandler::kErrorAuthenticationRequired) {
270      ShowPasswordView(service_path);
271    }
272  }
273
274  void ActivateNetwork() {
275    const chromeos::NetworkState* network =
276        NetworkHandler::Get()->network_state_handler()->GetNetworkState(
277            network_.service_path);
278    if (!network)
279      return;
280    if (network->IsConnectedState()) {
281      NetworkHandler::Get()->network_connection_handler()->DisconnectNetwork(
282          network_.service_path,
283          base::Closure(),
284          chromeos::network_handler::ErrorCallback());
285    } else if (!network->IsConnectingState()) {
286      // |network| is not connected, and not already trying to connect.
287      NetworkHandler::Get()->network_connection_handler()->ConnectToNetwork(
288          network_.service_path,
289          base::Closure(),
290          base::Bind(&NetworkRow::OnNetworkConnectionError,
291                     weak_ptr_.GetWeakPtr(),
292                     network_.service_path),
293          false);
294    }
295  }
296
297  // views::View:
298  virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE {
299    if (event->type() != ui::ET_MOUSE_PRESSED)
300      return;
301    ActivateNetwork();
302    event->SetHandled();
303  }
304
305  virtual void OnGestureEvent(ui::GestureEvent* gesture) OVERRIDE {
306    if (gesture->type() != ui::ET_GESTURE_TAP)
307      return;
308    ActivateNetwork();
309    gesture->SetHandled();
310  }
311
312  ui::NetworkInfo network_;
313  views::View* container_;
314  scoped_ptr<views::View> password_view_;
315  base::WeakPtrFactory<NetworkRow> weak_ptr_;
316
317  DISALLOW_COPY_AND_ASSIGN(NetworkRow);
318};
319
320class NetworkSelector : public ui::NetworkListDelegate,
321                        public chromeos::NetworkStateHandlerObserver,
322                        public ui::EventHandler {
323 public:
324  explicit NetworkSelector(aura::Window* container)
325      : background_view_(NULL),
326        scroll_content_(NULL),
327        scroller_(NULL),
328        network_list_(this) {
329    CreateWidget(container);
330    CreateNetworkList();
331
332    NetworkHandler::Get()->network_state_handler()->RequestScan();
333    NetworkHandler::Get()->network_state_handler()->AddObserver(this,
334                                                                FROM_HERE);
335  }
336
337  virtual ~NetworkSelector() {
338    NetworkHandler::Get()->network_state_handler()->RemoveObserver(this,
339                                                                   FROM_HERE);
340  }
341
342 private:
343  void CreateWidget(aura::Window* container) {
344    views::Widget::InitParams params;
345    params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS;
346    params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
347    params.activatable = views::Widget::InitParams::ACTIVATABLE_DEFAULT;
348    params.accept_events = true;
349    params.bounds = gfx::Rect(container->bounds().size());
350    params.parent = container;
351    params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
352    widget_.reset(new views::Widget());
353    widget_->Init(params);
354    widget_->Show();
355
356    background_view_ = new views::View;
357    background_view_->set_background(
358        views::Background::CreateSolidBackground(kBackgroundColor));
359    background_view_->SetBorder(
360        views::Border::CreateEmptyBorder(100, 300, 300, 300));
361    background_view_->SetLayoutManager(new views::FillLayout());
362    background_view_->set_target_handler(this);
363
364    widget_->SetContentsView(background_view_);
365  }
366
367  void CreateNetworkList() {
368    const int kListHeight = 500;
369    scroller_ = new views::ScrollView();
370    scroller_->set_background(
371        views::Background::CreateSolidBackground(SK_ColorWHITE));
372    scroller_->SetBounds(0, 0, 400, kListHeight);
373
374    scroll_content_ = new views::View;
375    scroll_content_->SetLayoutManager(
376        new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
377    scroller_->SetContents(scroll_content_);
378
379    scroller_->ClipHeightTo(kListHeight, kListHeight);
380    scroller_->SetVerticalScrollBar(new views::OverlayScrollBar(false));
381    background_view_->AddChildView(scroller_);
382
383    background_view_->Layout();
384
385    network_list_.set_content_view(scroll_content_);
386  }
387
388  void UpdateNetworkList() { network_list_.UpdateNetworkList(); }
389
390  void Close() { delete this; }
391
392  // ui::NetworkListDelegate:
393  virtual views::View* CreateViewForNetwork(
394      const ui::NetworkInfo& info) OVERRIDE {
395    return new NetworkRow(info, background_view_);
396  }
397
398  virtual bool IsViewHovered(views::View* view) OVERRIDE {
399    return static_cast<NetworkRow*>(view)->has_password_view();
400  }
401
402  virtual chromeos::NetworkTypePattern GetNetworkTypePattern() const OVERRIDE {
403    return chromeos::NetworkTypePattern::NonVirtual();
404  }
405
406  virtual void UpdateViewForNetwork(views::View* view,
407                                    const ui::NetworkInfo& info) OVERRIDE {
408    static_cast<NetworkRow*>(view)->Update(info);
409  }
410
411  virtual views::Label* CreateInfoLabel() OVERRIDE {
412    views::Label* label = new views::Label();
413    return label;
414  }
415
416  virtual void RelayoutScrollList() OVERRIDE { scroller_->Layout(); }
417
418  // chromeos::NetworkStateHandlerObserver:
419  virtual void NetworkListChanged() OVERRIDE { UpdateNetworkList(); }
420
421  virtual void DeviceListChanged() OVERRIDE {}
422
423  virtual void DefaultNetworkChanged(
424      const chromeos::NetworkState* network) OVERRIDE {}
425
426  virtual void NetworkConnectionStateChanged(
427      const chromeos::NetworkState* network) OVERRIDE {}
428
429  virtual void NetworkPropertiesUpdated(
430      const chromeos::NetworkState* network) OVERRIDE {}
431
432  // ui::EventHandler:
433  virtual void OnMouseEvent(ui::MouseEvent* mouse) OVERRIDE {
434    CHECK_EQ(background_view_, mouse->target());
435    if (mouse->type() == ui::ET_MOUSE_PRESSED && !mouse->handled()) {
436      Close();
437      mouse->SetHandled();
438    }
439  }
440
441  virtual void OnGestureEvent(ui::GestureEvent* gesture) OVERRIDE {
442    CHECK_EQ(background_view_, gesture->target());
443    if (gesture->type() == ui::ET_GESTURE_TAP && !gesture->handled()) {
444      Close();
445      gesture->SetHandled();
446    }
447  }
448
449  scoped_ptr<views::Widget> widget_;
450  views::View* background_view_;
451  views::View* scroll_content_;
452  views::ScrollView* scroller_;
453
454  views::View* connect_;
455
456  ui::NetworkListView network_list_;
457
458  DISALLOW_COPY_AND_ASSIGN(NetworkSelector);
459};
460
461}  // namespace
462
463namespace athena {
464
465void CreateNetworkSelector(aura::Window* container) {
466  new NetworkSelector(container);
467}
468
469}  // namespace athena
470