1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/chromeos/login/views_login_display.h"
6
7#include <algorithm>
8
9#include "base/stl_util-inl.h"
10#include "base/utf_string_conversions.h"
11#include "chrome/browser/chromeos/login/help_app_launcher.h"
12#include "chrome/browser/chromeos/login/message_bubble.h"
13#include "chrome/browser/chromeos/login/wizard_accessibility_helper.h"
14#include "chrome/browser/chromeos/view_ids.h"
15#include "chrome/browser/chromeos/wm_ipc.h"
16#include "chrome/browser/ui/views/window.h"
17#include "grit/chromium_strings.h"
18#include "grit/generated_resources.h"
19#include "grit/theme_resources.h"
20#include "ui/base/l10n/l10n_util.h"
21#include "ui/base/resource/resource_bundle.h"
22#include "views/widget/widget_gtk.h"
23#include "views/window/window.h"
24
25namespace {
26
27// Max number of users we'll show. The true max is the min of this and the
28// number of windows that fit on the screen.
29const size_t kMaxUsers = 6;
30
31// Minimum number of users we'll show (including Guest and New User pods).
32const size_t kMinUsers = 3;
33
34// Used to indicate no user has been selected.
35const size_t kNotSelected = -1;
36
37// Offset of cursor in first position from edit left side. It's used to position
38// info bubble arrow to cursor.
39const int kCursorOffset = 5;
40
41// Checks if display names are unique. If there are duplicates, enables
42// tooltips with full emails to let users distinguish their accounts.
43// Otherwise, disables the tooltips.
44void EnableTooltipsIfNeeded(
45    const std::vector<chromeos::UserController*>& controllers) {
46  std::map<std::string, int> visible_display_names;
47  for (size_t i = 0; i + 1 < controllers.size(); ++i) {
48    const std::string& display_name =
49        controllers[i]->user().GetDisplayName();
50    ++visible_display_names[display_name];
51  }
52  for (size_t i = 0; i < controllers.size(); ++i) {
53    const std::string& display_name =
54        controllers[i]->user().GetDisplayName();
55    bool show_tooltip = controllers[i]->is_new_user() ||
56                        controllers[i]->is_guest() ||
57                        visible_display_names[display_name] > 1;
58    controllers[i]->EnableNameTooltip(show_tooltip);
59  }
60}
61
62}  // namespace
63
64namespace chromeos {
65
66ViewsLoginDisplay::ViewsLoginDisplay(LoginDisplay::Delegate* delegate,
67                                     const gfx::Rect& background_bounds)
68    : LoginDisplay(delegate, background_bounds),
69      bubble_(NULL),
70      controller_for_removal_(NULL),
71      selected_view_index_(kNotSelected) {
72}
73
74ViewsLoginDisplay::~ViewsLoginDisplay() {
75  ClearErrors();
76  STLDeleteElements(&controllers_);
77  STLDeleteElements(&invisible_controllers_);
78}
79
80////////////////////////////////////////////////////////////////////////////////
81// ViewsLoginDisplay, LoginDisplay implementation:
82//
83
84void ViewsLoginDisplay::Init(const std::vector<UserManager::User>& users,
85                             bool show_guest,
86                             bool show_new_user) {
87  size_t max_users = kMaxUsers;
88  if (width() > 0) {
89    size_t users_per_screen = (width() - login::kUserImageSize) /
90        (UserController::kUnselectedSize + UserController::kPadding);
91    max_users = std::max(kMinUsers, std::min(kMaxUsers, users_per_screen));
92  }
93
94  size_t visible_users_count = std::min(users.size(), max_users -
95      static_cast<int>(show_guest) - static_cast<int>(show_new_user));
96  for (size_t i = 0; i < users.size(); ++i) {
97    UserController* user_controller = new UserController(this, users[i]);
98    if (controllers_.size() < visible_users_count) {
99      controllers_.push_back(user_controller);
100    } else if (user_controller->is_owner()) {
101      // Make sure that owner of the device is always visible on login screen.
102      invisible_controllers_.insert(invisible_controllers_.begin(),
103                                    controllers_.back());
104      controllers_.back() = user_controller;
105    } else {
106      invisible_controllers_.push_back(user_controller);
107    }
108  }
109  if (show_guest)
110    controllers_.push_back(new UserController(this, true));
111
112  if (show_new_user)
113    controllers_.push_back(new UserController(this, false));
114
115  // If there's only new user pod, show the guest session link on it.
116  bool show_guest_link = controllers_.size() == 1;
117  for (size_t i = 0; i < controllers_.size(); ++i) {
118    (controllers_[i])->Init(static_cast<int>(i),
119                            static_cast<int>(controllers_.size()),
120                            show_guest_link);
121  }
122  EnableTooltipsIfNeeded(controllers_);
123}
124
125void ViewsLoginDisplay::OnBeforeUserRemoved(const std::string& username) {
126  controller_for_removal_ = controllers_[selected_view_index_];
127  controllers_.erase(controllers_.begin() + selected_view_index_);
128  EnableTooltipsIfNeeded(controllers_);
129
130  // Update user count before unmapping windows, otherwise window manager won't
131  // be in the right state.
132  int new_size = static_cast<int>(controllers_.size());
133  for (int i = 0; i < new_size; ++i)
134    controllers_[i]->UpdateUserCount(i, new_size);
135}
136
137void ViewsLoginDisplay::OnUserImageChanged(UserManager::User* user) {
138  UserController* controller = GetUserControllerByEmail(user->email());
139  if (controller)
140    controller->OnUserImageChanged(user);
141}
142
143void ViewsLoginDisplay::OnUserRemoved(const std::string& username) {
144  // We need to unmap entry windows, the windows will be unmapped in destructor.
145  MessageLoop::current()->DeleteSoon(FROM_HERE, controller_for_removal_);
146  controller_for_removal_ = NULL;
147
148  // Nothing to insert.
149  if (invisible_controllers_.empty())
150    return;
151
152  // Insert just before guest or add new user pods if any.
153  size_t new_size = controllers_.size();
154  size_t insert_position = new_size;
155  while (insert_position > 0 &&
156         (controllers_[insert_position - 1]->is_new_user() ||
157          controllers_[insert_position - 1]->is_guest())) {
158    --insert_position;
159  }
160
161  controllers_.insert(controllers_.begin() + insert_position,
162                      invisible_controllers_[0]);
163  invisible_controllers_.erase(invisible_controllers_.begin());
164
165  // Update counts for exiting pods.
166  new_size = controllers_.size();
167  for (size_t i = 0; i < new_size; ++i) {
168    if (i != insert_position)
169      controllers_[i]->UpdateUserCount(i, new_size);
170  }
171
172  // And initialize new one that was invisible.
173  controllers_[insert_position]->Init(insert_position, new_size, false);
174
175  EnableTooltipsIfNeeded(controllers_);
176}
177
178void ViewsLoginDisplay::OnFadeOut() {
179  controllers_[selected_view_index_]->StopThrobber();
180}
181
182void ViewsLoginDisplay::SetUIEnabled(bool is_enabled) {
183  // Send message to WM to enable/disable click on windows.
184  WmIpc::Message message(WM_IPC_MESSAGE_WM_SET_LOGIN_STATE);
185  message.set_param(0, is_enabled);
186  WmIpc::instance()->SendMessage(message);
187
188  if (is_enabled)
189    controllers_[selected_view_index_]->ClearAndEnablePassword();
190}
191
192void ViewsLoginDisplay::ShowError(int error_msg_id,
193                                  int login_attempts,
194                                  HelpAppLauncher::HelpTopic help_topic_id) {
195  ClearErrors();
196  string16 error_text;
197  error_msg_id_ = error_msg_id;
198  help_topic_id_ = help_topic_id;
199
200  // GetStringF fails on debug build if there's no replacement in the string.
201  if (error_msg_id == IDS_LOGIN_ERROR_AUTHENTICATING_HOSTED) {
202    error_text = l10n_util::GetStringFUTF16(
203        error_msg_id, l10n_util::GetStringUTF16(IDS_PRODUCT_OS_NAME));
204  } else if (error_msg_id == IDS_LOGIN_ERROR_CAPTIVE_PORTAL) {
205    error_text = l10n_util::GetStringFUTF16(
206        error_msg_id, delegate()->GetConnectedNetworkName());
207  } else {
208    error_text = l10n_util::GetStringUTF16(error_msg_id);
209  }
210
211  gfx::Rect bounds =
212      controllers_[selected_view_index_]->GetMainInputScreenBounds();
213  BubbleBorder::ArrowLocation arrow;
214  if (controllers_[selected_view_index_]->is_new_user()) {
215    arrow = BubbleBorder::LEFT_TOP;
216  } else {
217    // Point info bubble arrow to cursor position (approximately).
218    bounds.set_width(kCursorOffset * 2);
219    arrow = BubbleBorder::BOTTOM_LEFT;
220  }
221
222  string16 help_link;
223  if (error_msg_id == IDS_LOGIN_ERROR_CAPTIVE_PORTAL) {
224    help_link = l10n_util::GetStringUTF16(IDS_LOGIN_FIX_CAPTIVE_PORTAL);
225  } else if (error_msg_id == IDS_LOGIN_ERROR_CAPTIVE_PORTAL_NO_GUEST_MODE) {
226    // No help link is needed.
227  } else if (error_msg_id == IDS_LOGIN_ERROR_AUTHENTICATING_HOSTED ||
228             login_attempts > 1) {
229    help_link = l10n_util::GetStringUTF16(IDS_LEARN_MORE);
230  }
231
232  bubble_ = MessageBubble::Show(
233      controllers_[selected_view_index_]->controls_window(),
234      bounds,
235      arrow,
236      ResourceBundle::GetSharedInstance().GetBitmapNamed(IDR_WARNING),
237      UTF16ToWide(error_text),
238      UTF16ToWide(help_link),
239      this);
240  WizardAccessibilityHelper::GetInstance()->MaybeSpeak(
241      UTF16ToUTF8(error_text).c_str(), false, false);
242}
243
244////////////////////////////////////////////////////////////////////////////////
245// ViewsLoginDisplay, UserController::Delegate implementation:
246//
247
248void ViewsLoginDisplay::CreateAccount() {
249  delegate()->CreateAccount();
250}
251
252void ViewsLoginDisplay::Login(UserController* source,
253                              const string16& password) {
254  delegate()->Login(source->user().email(), UTF16ToUTF8(password));
255}
256
257void ViewsLoginDisplay::LoginAsGuest() {
258  delegate()->LoginAsGuest();
259}
260
261void ViewsLoginDisplay::ClearErrors() {
262  // bubble_ will be set to NULL in callback.
263  if (bubble_)
264    bubble_->Close();
265}
266
267void ViewsLoginDisplay::OnUserSelected(UserController* source) {
268  std::vector<UserController*>::const_iterator i =
269      std::find(controllers_.begin(), controllers_.end(), source);
270  DCHECK(i != controllers_.end());
271  size_t new_selected_index = i - controllers_.begin();
272  if (new_selected_index != selected_view_index_ &&
273      selected_view_index_ != kNotSelected) {
274    controllers_[selected_view_index_]->ClearAndEnableFields();
275    controllers_[new_selected_index]->ClearAndEnableFields();
276    delegate()->OnUserSelected(source->user().email());
277  }
278  selected_view_index_ = new_selected_index;
279  WizardAccessibilityHelper::GetInstance()->MaybeSpeak(
280      source->GetAccessibleUserLabel().c_str(), false, true);
281}
282
283void ViewsLoginDisplay::RemoveUser(UserController* source) {
284  ClearErrors();
285  UserManager::Get()->RemoveUser(source->user().email(), this);
286}
287
288void ViewsLoginDisplay::SelectUser(int index) {
289  if (index >= 0 && index < static_cast<int>(controllers_.size()) &&
290      index != static_cast<int>(selected_view_index_)) {
291    WmIpc::Message message(WM_IPC_MESSAGE_WM_SELECT_LOGIN_USER);
292    message.set_param(0, index);
293    WmIpc::instance()->SendMessage(message);
294  }
295}
296
297void ViewsLoginDisplay::StartEnterpriseEnrollment() {
298  delegate()->OnStartEnterpriseEnrollment();
299}
300
301////////////////////////////////////////////////////////////////////////////////
302// ViewsLoginDisplay, views::MessageBubbleDelegate implementation:
303//
304
305void ViewsLoginDisplay::OnHelpLinkActivated() {
306  ClearErrors();
307  if (error_msg_id_ == IDS_LOGIN_ERROR_CAPTIVE_PORTAL) {
308    delegate()->FixCaptivePortal();
309    return;
310  }
311  if (!parent_window())
312    return;
313  if (!help_app_.get())
314    help_app_ = new HelpAppLauncher(parent_window());
315  help_app_->ShowHelpTopic(help_topic_id_);
316}
317
318////////////////////////////////////////////////////////////////////////////////
319// ViewsLoginDisplay, private:
320//
321
322UserController* ViewsLoginDisplay::GetUserControllerByEmail(
323    const std::string& email) {
324  std::vector<UserController*>::iterator i;
325  for (i = controllers_.begin(); i != controllers_.end(); ++i) {
326    if ((*i)->user().email() == email)
327      return *i;
328  }
329  for (i = invisible_controllers_.begin();
330       i != invisible_controllers_.end(); ++i) {
331    if ((*i)->user().email() == email)
332      return *i;
333  }
334  return NULL;
335}
336
337}  // namespace chromeos
338