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 25 namespace { 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. 29 const size_t kMaxUsers = 6; 30 31 // Minimum number of users we'll show (including Guest and New User pods). 32 const size_t kMinUsers = 3; 33 34 // Used to indicate no user has been selected. 35 const 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. 39 const 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. 44 void 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 64 namespace chromeos { 65 66 ViewsLoginDisplay::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 74 ViewsLoginDisplay::~ViewsLoginDisplay() { 75 ClearErrors(); 76 STLDeleteElements(&controllers_); 77 STLDeleteElements(&invisible_controllers_); 78 } 79 80 //////////////////////////////////////////////////////////////////////////////// 81 // ViewsLoginDisplay, LoginDisplay implementation: 82 // 83 84 void 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 125 void 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 137 void ViewsLoginDisplay::OnUserImageChanged(UserManager::User* user) { 138 UserController* controller = GetUserControllerByEmail(user->email()); 139 if (controller) 140 controller->OnUserImageChanged(user); 141 } 142 143 void 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 178 void ViewsLoginDisplay::OnFadeOut() { 179 controllers_[selected_view_index_]->StopThrobber(); 180 } 181 182 void 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 192 void 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 248 void ViewsLoginDisplay::CreateAccount() { 249 delegate()->CreateAccount(); 250 } 251 252 void ViewsLoginDisplay::Login(UserController* source, 253 const string16& password) { 254 delegate()->Login(source->user().email(), UTF16ToUTF8(password)); 255 } 256 257 void ViewsLoginDisplay::LoginAsGuest() { 258 delegate()->LoginAsGuest(); 259 } 260 261 void ViewsLoginDisplay::ClearErrors() { 262 // bubble_ will be set to NULL in callback. 263 if (bubble_) 264 bubble_->Close(); 265 } 266 267 void 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 283 void ViewsLoginDisplay::RemoveUser(UserController* source) { 284 ClearErrors(); 285 UserManager::Get()->RemoveUser(source->user().email(), this); 286 } 287 288 void 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 297 void ViewsLoginDisplay::StartEnterpriseEnrollment() { 298 delegate()->OnStartEnterpriseEnrollment(); 299 } 300 301 //////////////////////////////////////////////////////////////////////////////// 302 // ViewsLoginDisplay, views::MessageBubbleDelegate implementation: 303 // 304 305 void 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 322 UserController* 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