1// Copyright (c) 2013 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/ui/webui/options/manage_profile_handler.h" 6 7#include "base/bind.h" 8#include "base/bind_helpers.h" 9#include "base/prefs/pref_service.h" 10#include "base/prefs/scoped_user_pref_update.h" 11#include "base/strings/string_number_conversions.h" 12#include "base/strings/string_util.h" 13#include "base/strings/utf_string_conversions.h" 14#include "base/value_conversions.h" 15#include "base/values.h" 16#include "chrome/browser/browser_process.h" 17#include "chrome/browser/chrome_notification_types.h" 18#include "chrome/browser/profiles/gaia_info_update_service.h" 19#include "chrome/browser/profiles/profile.h" 20#include "chrome/browser/profiles/profile_avatar_icon_util.h" 21#include "chrome/browser/profiles/profile_info_cache.h" 22#include "chrome/browser/profiles/profile_manager.h" 23#include "chrome/browser/profiles/profile_metrics.h" 24#include "chrome/browser/profiles/profile_shortcut_manager.h" 25#include "chrome/browser/profiles/profile_window.h" 26#include "chrome/browser/profiles/profiles_state.h" 27#include "chrome/browser/signin/signin_manager_factory.h" 28#include "chrome/browser/sync/profile_sync_service.h" 29#include "chrome/browser/sync/profile_sync_service_factory.h" 30#include "chrome/browser/ui/browser_finder.h" 31#include "chrome/browser/ui/webui/options/options_handlers_helper.h" 32#include "chrome/common/pref_names.h" 33#include "chrome/common/url_constants.h" 34#include "chrome/grit/generated_resources.h" 35#include "chrome/grit/google_chrome_strings.h" 36#include "components/signin/core/browser/signin_manager.h" 37#include "content/public/browser/browser_thread.h" 38#include "content/public/browser/notification_service.h" 39#include "content/public/browser/web_ui.h" 40#include "google_apis/gaia/gaia_auth_util.h" 41#include "ui/base/l10n/l10n_util.h" 42#include "ui/base/webui/web_ui_util.h" 43 44#if defined(ENABLE_SETTINGS_APP) 45#include "chrome/browser/ui/app_list/app_list_service.h" 46#include "content/public/browser/web_contents.h" 47#endif 48 49namespace options { 50 51namespace { 52 53const char kCreateProfileIdentifier[] = "create"; 54const char kManageProfileIdentifier[] = "manage"; 55 56// Given |args| from the WebUI, parses value 0 as a FilePath |profile_file_path| 57// and returns true on success. 58bool GetProfilePathFromArgs(const base::ListValue* args, 59 base::FilePath* profile_file_path) { 60 const base::Value* file_path_value; 61 if (!args->Get(0, &file_path_value)) 62 return false; 63 return base::GetValueAsFilePath(*file_path_value, profile_file_path); 64} 65 66} // namespace 67 68ManageProfileHandler::ManageProfileHandler() 69 : weak_factory_(this) { 70} 71 72ManageProfileHandler::~ManageProfileHandler() { 73 ProfileSyncService* service = 74 ProfileSyncServiceFactory::GetForProfile(Profile::FromWebUI(web_ui())); 75 // Sync may be disabled in tests. 76 if (service) 77 service->RemoveObserver(this); 78} 79 80void ManageProfileHandler::GetLocalizedValues( 81 base::DictionaryValue* localized_strings) { 82 DCHECK(localized_strings); 83 84 static OptionsStringResource resources[] = { 85 { "manageProfilesNameLabel", IDS_PROFILES_MANAGE_NAME_LABEL }, 86 { "manageProfilesDuplicateNameError", 87 IDS_PROFILES_MANAGE_DUPLICATE_NAME_ERROR }, 88 { "manageProfilesIconLabel", IDS_PROFILES_MANAGE_ICON_LABEL }, 89 { "manageProfilesExistingSupervisedUser", 90 IDS_PROFILES_CREATE_EXISTING_SUPERVISED_USER_ERROR }, 91 { "manageProfilesSupervisedSignedInLabel", 92 IDS_PROFILES_CREATE_SUPERVISED_SIGNED_IN_LABEL }, 93 { "manageProfilesSupervisedNotSignedIn", 94 IDS_PROFILES_CREATE_SUPERVISED_NOT_SIGNED_IN_HTML }, 95 { "manageProfilesSupervisedAccountDetailsOutOfDate", 96 IDS_PROFILES_CREATE_SUPERVISED_ACCOUNT_DETAILS_OUT_OF_DATE_LABEL }, 97 { "manageProfilesSupervisedSignInAgainLink", 98 IDS_PROFILES_CREATE_SUPERVISED_SIGN_IN_AGAIN_LINK }, 99 { "manageProfilesConfirm", IDS_SAVE }, 100 { "deleteProfileTitle", IDS_PROFILES_DELETE_TITLE }, 101 { "deleteProfileOK", IDS_PROFILES_DELETE_OK_BUTTON_LABEL }, 102 { "deleteProfileMessage", IDS_PROFILES_DELETE_MESSAGE }, 103 { "deleteSupervisedProfileAddendum", 104 IDS_PROFILES_DELETE_SUPERVISED_ADDENDUM }, 105 { "disconnectManagedProfileTitle", 106 IDS_PROFILES_DISCONNECT_MANAGED_PROFILE_TITLE }, 107 { "disconnectManagedProfileOK", 108 IDS_PROFILES_DISCONNECT_MANAGED_PROFILE_OK_BUTTON_LABEL }, 109 { "createProfileTitle", IDS_PROFILES_CREATE_TITLE }, 110 { "createProfileInstructions", IDS_PROFILES_CREATE_INSTRUCTIONS }, 111 { "createProfileConfirm", IDS_ADD }, 112 { "createProfileShortcutCheckbox", IDS_PROFILES_CREATE_SHORTCUT_CHECKBOX }, 113 { "createProfileShortcutButton", IDS_PROFILES_CREATE_SHORTCUT_BUTTON }, 114 { "removeProfileShortcutButton", IDS_PROFILES_REMOVE_SHORTCUT_BUTTON }, 115 { "importExistingSupervisedUserLink", 116 IDS_PROFILES_IMPORT_EXISTING_SUPERVISED_USER_LINK }, 117 }; 118 119 RegisterStrings(localized_strings, resources, arraysize(resources)); 120 RegisterTitle(localized_strings, "manageProfile", IDS_PROFILES_MANAGE_TITLE); 121 RegisterTitle(localized_strings, "createProfile", IDS_PROFILES_CREATE_TITLE); 122 123 localized_strings->SetBoolean("profileShortcutsEnabled", 124 ProfileShortcutManager::IsFeatureEnabled()); 125 126 GenerateSignedinUserSpecificStrings(localized_strings); 127} 128 129void ManageProfileHandler::InitializeHandler() { 130 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED, 131 content::NotificationService::AllSources()); 132 133 Profile* profile = Profile::FromWebUI(web_ui()); 134 pref_change_registrar_.Init(profile->GetPrefs()); 135 pref_change_registrar_.Add( 136 prefs::kSupervisedUserCreationAllowed, 137 base::Bind(&ManageProfileHandler::OnCreateSupervisedUserPrefChange, 138 base::Unretained(this))); 139 ProfileSyncService* service = 140 ProfileSyncServiceFactory::GetForProfile(profile); 141 // Sync may be disabled for tests. 142 if (service) 143 service->AddObserver(this); 144} 145 146void ManageProfileHandler::InitializePage() { 147 SendExistingProfileNames(); 148 OnCreateSupervisedUserPrefChange(); 149} 150 151void ManageProfileHandler::RegisterMessages() { 152 web_ui()->RegisterMessageCallback("setProfileIconAndName", 153 base::Bind(&ManageProfileHandler::SetProfileIconAndName, 154 base::Unretained(this))); 155 web_ui()->RegisterMessageCallback("requestDefaultProfileIcons", 156 base::Bind(&ManageProfileHandler::RequestDefaultProfileIcons, 157 base::Unretained(this))); 158 web_ui()->RegisterMessageCallback("requestNewProfileDefaults", 159 base::Bind(&ManageProfileHandler::RequestNewProfileDefaults, 160 base::Unretained(this))); 161 web_ui()->RegisterMessageCallback("requestHasProfileShortcuts", 162 base::Bind(&ManageProfileHandler::RequestHasProfileShortcuts, 163 base::Unretained(this))); 164 web_ui()->RegisterMessageCallback("requestCreateProfileUpdate", 165 base::Bind(&ManageProfileHandler::RequestCreateProfileUpdate, 166 base::Unretained(this))); 167 web_ui()->RegisterMessageCallback("profileIconSelectionChanged", 168 base::Bind(&ManageProfileHandler::ProfileIconSelectionChanged, 169 base::Unretained(this))); 170#if defined(ENABLE_SETTINGS_APP) 171 web_ui()->RegisterMessageCallback("switchAppListProfile", 172 base::Bind(&ManageProfileHandler::SwitchAppListProfile, 173 base::Unretained(this))); 174#endif 175 web_ui()->RegisterMessageCallback("addProfileShortcut", 176 base::Bind(&ManageProfileHandler::AddProfileShortcut, 177 base::Unretained(this))); 178 web_ui()->RegisterMessageCallback("removeProfileShortcut", 179 base::Bind(&ManageProfileHandler::RemoveProfileShortcut, 180 base::Unretained(this))); 181 web_ui()->RegisterMessageCallback("refreshGaiaPicture", 182 base::Bind(&ManageProfileHandler::RefreshGaiaPicture, 183 base::Unretained(this))); 184 web_ui()->RegisterMessageCallback( 185 "showDisconnectManagedProfileDialog", 186 base::Bind(&ManageProfileHandler::ShowDisconnectManagedProfileDialog, 187 base::Unretained(this))); 188} 189 190void ManageProfileHandler::Uninitialize() { 191 registrar_.RemoveAll(); 192} 193 194void ManageProfileHandler::Observe( 195 int type, 196 const content::NotificationSource& source, 197 const content::NotificationDetails& details) { 198 if (type == chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED) { 199 SendExistingProfileNames(); 200 base::StringValue value(kManageProfileIdentifier); 201 SendProfileIconsAndNames(value); 202 } 203} 204 205void ManageProfileHandler::OnStateChanged() { 206 RequestCreateProfileUpdate(NULL); 207} 208 209void ManageProfileHandler::GenerateSignedinUserSpecificStrings( 210 base::DictionaryValue* dictionary) { 211 std::string username; 212 std::string domain_name; 213 214#if !defined(OS_CHROMEOS) 215 Profile* profile = Profile::FromWebUI(web_ui()); 216 DCHECK(profile); 217 SigninManagerBase* manager = SigninManagerFactory::GetForProfile(profile); 218 if (manager) { 219 username = manager->GetAuthenticatedUsername(); 220 // If there is no one logged in or if the profile name is empty then the 221 // domain name is empty. This happens in browser tests. 222 if (!username.empty()) { 223 domain_name = "<span id=disconnect-managed-profile-domain-name>" + 224 gaia::ExtractDomainName(username) + "</span>"; 225 } 226 } 227#endif 228 229 dictionary->SetString( 230 "disconnectManagedProfileDomainInformation", 231 l10n_util::GetStringFUTF16( 232 IDS_PROFILES_DISCONNECT_MANAGED_PROFILE_DOMAIN_INFORMATION, 233 base::ASCIIToUTF16(domain_name))); 234 235 dictionary->SetString( 236 "disconnectManagedProfileText", 237 l10n_util::GetStringFUTF16( 238 IDS_PROFILES_DISCONNECT_MANAGED_PROFILE_TEXT, 239 base::UTF8ToUTF16(username), 240 base::UTF8ToUTF16(chrome::kSyncGoogleDashboardURL))); 241} 242 243void ManageProfileHandler::RequestDefaultProfileIcons( 244 const base::ListValue* args) { 245 std::string mode; 246 bool ok = args->GetString(0, &mode); 247 DCHECK(ok); 248 DCHECK(mode == kCreateProfileIdentifier || mode == kManageProfileIdentifier); 249 if (ok) { 250 base::StringValue value(mode); 251 SendProfileIconsAndNames(value); 252 } 253} 254 255void ManageProfileHandler::RequestNewProfileDefaults( 256 const base::ListValue* args) { 257 const ProfileInfoCache& cache = 258 g_browser_process->profile_manager()->GetProfileInfoCache(); 259 const size_t icon_index = cache.ChooseAvatarIconIndexForNewProfile(); 260 261 base::DictionaryValue profile_info; 262 profile_info.SetString("name", cache.ChooseNameForNewProfile(icon_index)); 263 profile_info.SetString("iconURL", 264 profiles::GetDefaultAvatarIconUrl(icon_index)); 265 266 web_ui()->CallJavascriptFunction( 267 "ManageProfileOverlay.receiveNewProfileDefaults", profile_info); 268} 269 270void ManageProfileHandler::SendProfileIconsAndNames( 271 const base::StringValue& mode) { 272 base::ListValue image_url_list; 273 base::ListValue default_name_list; 274 275 // First add the GAIA picture if it's available. 276 const ProfileInfoCache& cache = 277 g_browser_process->profile_manager()->GetProfileInfoCache(); 278 Profile* profile = Profile::FromWebUI(web_ui()); 279 size_t profile_index = cache.GetIndexOfProfileWithPath(profile->GetPath()); 280 if (profile_index != std::string::npos) { 281 const gfx::Image* icon = 282 cache.GetGAIAPictureOfProfileAtIndex(profile_index); 283 if (icon) { 284 gfx::Image icon2 = profiles::GetAvatarIconForWebUI(*icon, true); 285 gaia_picture_url_ = webui::GetBitmapDataUrl(icon2.AsBitmap()); 286 image_url_list.AppendString(gaia_picture_url_); 287 default_name_list.AppendString(std::string()); 288 } 289 } 290 291 // Next add the default avatar icons and names. 292 for (size_t i = 0; i < profiles::GetDefaultAvatarIconCount(); i++) { 293 std::string url = profiles::GetDefaultAvatarIconUrl(i); 294 image_url_list.AppendString(url); 295 default_name_list.AppendString(cache.ChooseNameForNewProfile(i)); 296 } 297 298 web_ui()->CallJavascriptFunction( 299 "ManageProfileOverlay.receiveDefaultProfileIconsAndNames", mode, 300 image_url_list, default_name_list); 301} 302 303void ManageProfileHandler::SendExistingProfileNames() { 304 const ProfileInfoCache& cache = 305 g_browser_process->profile_manager()->GetProfileInfoCache(); 306 base::DictionaryValue profile_name_dict; 307 for (size_t i = 0, e = cache.GetNumberOfProfiles(); i < e; ++i) { 308 profile_name_dict.SetBoolean( 309 base::UTF16ToUTF8(cache.GetNameOfProfileAtIndex(i)), true); 310 } 311 312 web_ui()->CallJavascriptFunction( 313 "ManageProfileOverlay.receiveExistingProfileNames", profile_name_dict); 314} 315 316void ManageProfileHandler::ShowDisconnectManagedProfileDialog( 317 const base::ListValue* args) { 318 base::DictionaryValue replacements; 319 GenerateSignedinUserSpecificStrings(&replacements); 320 web_ui()->CallJavascriptFunction( 321 "ManageProfileOverlay.showDisconnectManagedProfileDialog", replacements); 322} 323 324void ManageProfileHandler::SetProfileIconAndName(const base::ListValue* args) { 325 DCHECK(args); 326 327 base::FilePath profile_file_path; 328 if (!GetProfilePathFromArgs(args, &profile_file_path)) 329 return; 330 331 Profile* profile = 332 g_browser_process->profile_manager()->GetProfile(profile_file_path); 333 if (!profile) 334 return; 335 336 std::string icon_url; 337 if (!args->GetString(1, &icon_url)) 338 return; 339 340 PrefService* pref_service = profile->GetPrefs(); 341 // Updating the profile preferences will cause the cache to be updated. 342 343 // Metrics logging variable. 344 bool previously_using_gaia_icon = 345 pref_service->GetBoolean(prefs::kProfileUsingGAIAAvatar); 346 347 size_t new_icon_index; 348 if (icon_url == gaia_picture_url_) { 349 pref_service->SetBoolean(prefs::kProfileUsingDefaultAvatar, false); 350 pref_service->SetBoolean(prefs::kProfileUsingGAIAAvatar, true); 351 if (!previously_using_gaia_icon) { 352 // Only log if they changed to the GAIA photo. 353 // Selection of GAIA photo as avatar is logged as part of the function 354 // below. 355 ProfileMetrics::LogProfileSwitchGaia(ProfileMetrics::GAIA_OPT_IN); 356 } 357 } else if (profiles::IsDefaultAvatarIconUrl(icon_url, &new_icon_index)) { 358 ProfileMetrics::LogProfileAvatarSelection(new_icon_index); 359 pref_service->SetInteger(prefs::kProfileAvatarIndex, new_icon_index); 360 pref_service->SetBoolean(prefs::kProfileUsingDefaultAvatar, false); 361 pref_service->SetBoolean(prefs::kProfileUsingGAIAAvatar, false); 362 } 363 ProfileMetrics::LogProfileUpdate(profile_file_path); 364 365 if (profile->IsSupervised()) 366 return; 367 368 base::string16 new_profile_name; 369 if (!args->GetString(2, &new_profile_name)) 370 return; 371 372 base::TrimWhitespace(new_profile_name, base::TRIM_ALL, &new_profile_name); 373 CHECK(!new_profile_name.empty()); 374 profiles::UpdateProfileName(profile, new_profile_name); 375} 376 377#if defined(ENABLE_SETTINGS_APP) 378void ManageProfileHandler::SwitchAppListProfile(const base::ListValue* args) { 379 DCHECK(args); 380 DCHECK(profiles::IsMultipleProfilesEnabled()); 381 382 const base::Value* file_path_value; 383 base::FilePath profile_file_path; 384 if (!args->Get(0, &file_path_value) || 385 !base::GetValueAsFilePath(*file_path_value, &profile_file_path)) 386 return; 387 388 AppListService* app_list_service = AppListService::Get( 389 options::helper::GetDesktopType(web_ui())); 390 app_list_service->SetProfilePath(profile_file_path); 391 app_list_service->Show(); 392 393 // Close the settings app, since it will now be for the wrong profile. 394 web_ui()->GetWebContents()->Close(); 395} 396#endif // defined(ENABLE_SETTINGS_APP) 397 398void ManageProfileHandler::ProfileIconSelectionChanged( 399 const base::ListValue* args) { 400 DCHECK(args); 401 402 base::FilePath profile_file_path; 403 if (!GetProfilePathFromArgs(args, &profile_file_path)) 404 return; 405 406 // Currently this only supports editing the current profile's info. 407 if (profile_file_path != Profile::FromWebUI(web_ui())->GetPath()) 408 return; 409 410 std::string icon_url; 411 if (!args->GetString(1, &icon_url)) 412 return; 413 414 if (icon_url != gaia_picture_url_) 415 return; 416 417 // If the selection is the GAIA picture then also show the profile name in the 418 // text field. This will display either the GAIA given name, if available, 419 // or the first name. 420 ProfileInfoCache& cache = 421 g_browser_process->profile_manager()->GetProfileInfoCache(); 422 size_t profile_index = cache.GetIndexOfProfileWithPath(profile_file_path); 423 if (profile_index == std::string::npos) 424 return; 425 base::string16 gaia_name = cache.GetNameOfProfileAtIndex(profile_index); 426 if (gaia_name.empty()) 427 return; 428 429 base::StringValue gaia_name_value(gaia_name); 430 base::StringValue mode_value(kManageProfileIdentifier); 431 web_ui()->CallJavascriptFunction("ManageProfileOverlay.setProfileName", 432 gaia_name_value, mode_value); 433} 434 435void ManageProfileHandler::RequestHasProfileShortcuts( 436 const base::ListValue* args) { 437 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 438 DCHECK(ProfileShortcutManager::IsFeatureEnabled()); 439 440 base::FilePath profile_file_path; 441 if (!GetProfilePathFromArgs(args, &profile_file_path)) 442 return; 443 444 const ProfileInfoCache& cache = 445 g_browser_process->profile_manager()->GetProfileInfoCache(); 446 size_t profile_index = cache.GetIndexOfProfileWithPath(profile_file_path); 447 if (profile_index == std::string::npos) 448 return; 449 450 // Don't show the add/remove desktop shortcut button in the single user case. 451 if (cache.GetNumberOfProfiles() <= 1) 452 return; 453 454 const base::FilePath profile_path = 455 cache.GetPathOfProfileAtIndex(profile_index); 456 ProfileShortcutManager* shortcut_manager = 457 g_browser_process->profile_manager()->profile_shortcut_manager(); 458 shortcut_manager->HasProfileShortcuts( 459 profile_path, base::Bind(&ManageProfileHandler::OnHasProfileShortcuts, 460 weak_factory_.GetWeakPtr())); 461} 462 463void ManageProfileHandler::RequestCreateProfileUpdate( 464 const base::ListValue* args) { 465 Profile* profile = Profile::FromWebUI(web_ui()); 466 SigninManagerBase* manager = 467 SigninManagerFactory::GetForProfile(profile); 468 base::string16 username = 469 base::UTF8ToUTF16(manager->GetAuthenticatedUsername()); 470 ProfileSyncService* service = 471 ProfileSyncServiceFactory::GetForProfile(profile); 472 GoogleServiceAuthError::State state = service->GetAuthError().state(); 473 bool has_error = (state == GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS || 474 state == GoogleServiceAuthError::USER_NOT_SIGNED_UP || 475 state == GoogleServiceAuthError::ACCOUNT_DELETED || 476 state == GoogleServiceAuthError::ACCOUNT_DISABLED); 477 web_ui()->CallJavascriptFunction("CreateProfileOverlay.updateSignedInStatus", 478 base::StringValue(username), 479 base::FundamentalValue(has_error)); 480 481 OnCreateSupervisedUserPrefChange(); 482} 483 484void ManageProfileHandler::OnCreateSupervisedUserPrefChange() { 485 PrefService* prefs = Profile::FromWebUI(web_ui())->GetPrefs(); 486 base::FundamentalValue allowed( 487 prefs->GetBoolean(prefs::kSupervisedUserCreationAllowed)); 488 web_ui()->CallJavascriptFunction( 489 "CreateProfileOverlay.updateSupervisedUsersAllowed", allowed); 490} 491 492void ManageProfileHandler::OnHasProfileShortcuts(bool has_shortcuts) { 493 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 494 495 const base::FundamentalValue has_shortcuts_value(has_shortcuts); 496 web_ui()->CallJavascriptFunction( 497 "ManageProfileOverlay.receiveHasProfileShortcuts", has_shortcuts_value); 498} 499 500void ManageProfileHandler::AddProfileShortcut(const base::ListValue* args) { 501 base::FilePath profile_file_path; 502 if (!GetProfilePathFromArgs(args, &profile_file_path)) 503 return; 504 505 DCHECK(ProfileShortcutManager::IsFeatureEnabled()); 506 ProfileShortcutManager* shortcut_manager = 507 g_browser_process->profile_manager()->profile_shortcut_manager(); 508 DCHECK(shortcut_manager); 509 510 shortcut_manager->CreateProfileShortcut(profile_file_path); 511 512 // Update the UI buttons. 513 OnHasProfileShortcuts(true); 514} 515 516void ManageProfileHandler::RemoveProfileShortcut(const base::ListValue* args) { 517 base::FilePath profile_file_path; 518 if (!GetProfilePathFromArgs(args, &profile_file_path)) 519 return; 520 521 DCHECK(ProfileShortcutManager::IsFeatureEnabled()); 522 ProfileShortcutManager* shortcut_manager = 523 g_browser_process->profile_manager()->profile_shortcut_manager(); 524 DCHECK(shortcut_manager); 525 526 shortcut_manager->RemoveProfileShortcuts(profile_file_path); 527 528 // Update the UI buttons. 529 OnHasProfileShortcuts(false); 530} 531 532void ManageProfileHandler::RefreshGaiaPicture(const base::ListValue* args) { 533 profiles::UpdateGaiaProfilePhotoIfNeeded(Profile::FromWebUI(web_ui())); 534} 535 536} // namespace options 537