manage_profile_handler.cc revision 010d83a9304c5a91596085d917d248abff47903a
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/command_line.h"
10#include "base/prefs/pref_service.h"
11#include "base/prefs/scoped_user_pref_update.h"
12#include "base/strings/string_number_conversions.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 "components/signin/core/browser/signin_manager.h"
35#include "content/public/browser/browser_thread.h"
36#include "content/public/browser/notification_service.h"
37#include "content/public/browser/web_ui.h"
38#include "google_apis/gaia/gaia_auth_util.h"
39#include "grit/generated_resources.h"
40#include "grit/google_chrome_strings.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_MANAGED_USER_ERROR },
91    { "manageProfilesManagedSignedInLabel",
92        IDS_PROFILES_CREATE_MANAGED_SIGNED_IN_LABEL },
93    { "manageProfilesManagedNotSignedInLabel",
94        IDS_PROFILES_CREATE_MANAGED_NOT_SIGNED_IN_LABEL },
95    { "manageProfilesManagedAccountDetailsOutOfDate",
96        IDS_PROFILES_CREATE_MANAGED_ACCOUNT_DETAILS_OUT_OF_DATE_LABEL },
97    { "manageProfilesManagedSignInAgainLink",
98        IDS_PROFILES_CREATE_MANAGED_ACCOUNT_SIGN_IN_AGAIN_LINK },
99    { "manageProfilesManagedNotSignedInLink",
100        IDS_PROFILES_CREATE_MANAGED_NOT_SIGNED_IN_LINK },
101    { "deleteProfileTitle", IDS_PROFILES_DELETE_TITLE },
102    { "deleteProfileOK", IDS_PROFILES_DELETE_OK_BUTTON_LABEL },
103    { "deleteProfileMessage", IDS_PROFILES_DELETE_MESSAGE },
104    { "deleteManagedProfileAddendum", IDS_PROFILES_DELETE_MANAGED_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_PROFILES_CREATE_CONFIRM },
112    { "createProfileShortcutCheckbox", IDS_PROFILES_CREATE_SHORTCUT_CHECKBOX },
113    { "createProfileShortcutButton", IDS_PROFILES_CREATE_SHORTCUT_BUTTON },
114    { "removeProfileShortcutButton", IDS_PROFILES_REMOVE_SHORTCUT_BUTTON },
115    { "importExistingManagedUserLink",
116        IDS_PROFILES_IMPORT_EXISTING_MANAGED_USER_LINK },
117    { "signInToImportManagedUsers",
118        IDS_PROFILES_IMPORT_MANAGED_USER_NOT_SIGNED_IN },
119  };
120
121  RegisterStrings(localized_strings, resources, arraysize(resources));
122  RegisterTitle(localized_strings, "manageProfile",
123                IDS_PROFILES_MANAGE_TITLE);
124  RegisterTitle(localized_strings, "createProfile",
125                IDS_PROFILES_CREATE_TITLE);
126
127  localized_strings->SetBoolean("profileShortcutsEnabled",
128                                ProfileShortcutManager::IsFeatureEnabled());
129
130  GenerateSignedinUserSpecificStrings(localized_strings);
131}
132
133void ManageProfileHandler::InitializeHandler() {
134  registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED,
135                 content::NotificationService::AllSources());
136
137  Profile* profile = Profile::FromWebUI(web_ui());
138  pref_change_registrar_.Init(profile->GetPrefs());
139  pref_change_registrar_.Add(
140      prefs::kManagedUserCreationAllowed,
141      base::Bind(&ManageProfileHandler::OnCreateManagedUserPrefChange,
142                 base::Unretained(this)));
143  ProfileSyncService* service =
144      ProfileSyncServiceFactory::GetForProfile(profile);
145  // Sync may be disabled for tests.
146  if (service)
147    service->AddObserver(this);
148}
149
150void ManageProfileHandler::InitializePage() {
151  SendExistingProfileNames();
152  OnCreateManagedUserPrefChange();
153}
154
155void ManageProfileHandler::RegisterMessages() {
156  web_ui()->RegisterMessageCallback("setProfileIconAndName",
157      base::Bind(&ManageProfileHandler::SetProfileIconAndName,
158                 base::Unretained(this)));
159  web_ui()->RegisterMessageCallback("requestDefaultProfileIcons",
160      base::Bind(&ManageProfileHandler::RequestDefaultProfileIcons,
161                 base::Unretained(this)));
162  web_ui()->RegisterMessageCallback("requestNewProfileDefaults",
163      base::Bind(&ManageProfileHandler::RequestNewProfileDefaults,
164                 base::Unretained(this)));
165  web_ui()->RegisterMessageCallback("requestHasProfileShortcuts",
166      base::Bind(&ManageProfileHandler::RequestHasProfileShortcuts,
167                 base::Unretained(this)));
168  web_ui()->RegisterMessageCallback("requestCreateProfileUpdate",
169      base::Bind(&ManageProfileHandler::RequestCreateProfileUpdate,
170                 base::Unretained(this)));
171  web_ui()->RegisterMessageCallback("profileIconSelectionChanged",
172      base::Bind(&ManageProfileHandler::ProfileIconSelectionChanged,
173                 base::Unretained(this)));
174#if defined(ENABLE_SETTINGS_APP)
175  web_ui()->RegisterMessageCallback("switchAppListProfile",
176      base::Bind(&ManageProfileHandler::SwitchAppListProfile,
177                 base::Unretained(this)));
178#endif
179  web_ui()->RegisterMessageCallback("addProfileShortcut",
180      base::Bind(&ManageProfileHandler::AddProfileShortcut,
181                 base::Unretained(this)));
182  web_ui()->RegisterMessageCallback("removeProfileShortcut",
183      base::Bind(&ManageProfileHandler::RemoveProfileShortcut,
184                 base::Unretained(this)));
185}
186
187void ManageProfileHandler::Uninitialize() {
188  registrar_.RemoveAll();
189}
190
191void ManageProfileHandler::Observe(
192    int type,
193    const content::NotificationSource& source,
194    const content::NotificationDetails& details) {
195  if (type == chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED) {
196    SendExistingProfileNames();
197    base::StringValue value(kManageProfileIdentifier);
198    SendProfileIconsAndNames(value);
199  }
200}
201
202void ManageProfileHandler::OnStateChanged() {
203  RequestCreateProfileUpdate(NULL);
204}
205
206void ManageProfileHandler::GenerateSignedinUserSpecificStrings(
207    base::DictionaryValue* dictionary) {
208  std::string username;
209  std::string domain_name;
210
211#if !defined(OS_CHROMEOS)
212  Profile* profile = Profile::FromWebUI(web_ui());
213  DCHECK(profile);
214  SigninManagerBase* manager = SigninManagerFactory::GetForProfile(profile);
215  if (manager) {
216    username = manager->GetAuthenticatedUsername();
217    // If there is no one logged in or if the profile name is empty then the
218    // domain name is empty. This happens in browser tests.
219    if (!username.empty()) {
220      domain_name = "<span id=disconnect-managed-profile-domain-name>" +
221                    gaia::ExtractDomainName(username) + "</span>";
222    }
223  }
224#endif
225
226  dictionary->SetString(
227      "disconnectManagedProfileDomainInformation",
228      l10n_util::GetStringFUTF16(
229          IDS_PROFILES_DISCONNECT_MANAGED_PROFILE_DOMAIN_INFORMATION,
230          base::ASCIIToUTF16(domain_name)));
231
232  dictionary->SetString(
233      "disconnectManagedProfileText",
234      l10n_util::GetStringFUTF16(
235          IDS_PROFILES_DISCONNECT_MANAGED_PROFILE_TEXT,
236          base::UTF8ToUTF16(username),
237          base::UTF8ToUTF16(chrome::kSyncGoogleDashboardURL)));
238}
239
240void ManageProfileHandler::RequestDefaultProfileIcons(
241    const base::ListValue* args) {
242  std::string mode;
243  bool ok = args->GetString(0, &mode);
244  DCHECK(ok);
245  DCHECK(mode == kCreateProfileIdentifier || mode == kManageProfileIdentifier);
246  if (ok) {
247    base::StringValue value(mode);
248    SendProfileIconsAndNames(value);
249  }
250}
251
252void ManageProfileHandler::RequestNewProfileDefaults(
253    const base::ListValue* args) {
254  const ProfileInfoCache& cache =
255      g_browser_process->profile_manager()->GetProfileInfoCache();
256  const size_t icon_index = cache.ChooseAvatarIconIndexForNewProfile();
257
258  base::DictionaryValue profile_info;
259  profile_info.SetString("name", cache.ChooseNameForNewProfile(icon_index));
260  profile_info.SetString("iconURL",
261      profiles::GetDefaultAvatarIconUrl(icon_index));
262
263  web_ui()->CallJavascriptFunction(
264      "ManageProfileOverlay.receiveNewProfileDefaults", profile_info);
265}
266
267void ManageProfileHandler::SendProfileIconsAndNames(
268    const base::StringValue& mode) {
269  base::ListValue image_url_list;
270  base::ListValue default_name_list;
271
272  // First add the GAIA picture if it's available.
273  const ProfileInfoCache& cache =
274      g_browser_process->profile_manager()->GetProfileInfoCache();
275  Profile* profile = Profile::FromWebUI(web_ui());
276  size_t profile_index = cache.GetIndexOfProfileWithPath(profile->GetPath());
277  if (profile_index != std::string::npos) {
278    const gfx::Image* icon =
279        cache.GetGAIAPictureOfProfileAtIndex(profile_index);
280    if (icon) {
281      gfx::Image icon2 = profiles::GetAvatarIconForWebUI(*icon, true);
282      gaia_picture_url_ = webui::GetBitmapDataUrl(icon2.AsBitmap());
283      image_url_list.AppendString(gaia_picture_url_);
284      default_name_list.AppendString(std::string());
285    }
286  }
287
288  // Next add the default avatar icons and names.
289  for (size_t i = 0; i < profiles::GetDefaultAvatarIconCount(); i++) {
290    std::string url = profiles::GetDefaultAvatarIconUrl(i);
291    image_url_list.AppendString(url);
292    default_name_list.AppendString(cache.ChooseNameForNewProfile(i));
293  }
294
295  web_ui()->CallJavascriptFunction(
296      "ManageProfileOverlay.receiveDefaultProfileIconsAndNames", mode,
297      image_url_list, default_name_list);
298}
299
300void ManageProfileHandler::SendExistingProfileNames() {
301  const ProfileInfoCache& cache =
302      g_browser_process->profile_manager()->GetProfileInfoCache();
303  base::DictionaryValue profile_name_dict;
304  for (size_t i = 0, e = cache.GetNumberOfProfiles(); i < e; ++i) {
305    profile_name_dict.SetBoolean(
306        base::UTF16ToUTF8(cache.GetNameOfProfileAtIndex(i)), true);
307  }
308
309  web_ui()->CallJavascriptFunction(
310      "ManageProfileOverlay.receiveExistingProfileNames", profile_name_dict);
311}
312
313void ManageProfileHandler::SetProfileIconAndName(const base::ListValue* args) {
314  DCHECK(args);
315
316  base::FilePath profile_file_path;
317  if (!GetProfilePathFromArgs(args, &profile_file_path))
318    return;
319
320  ProfileInfoCache& cache =
321      g_browser_process->profile_manager()->GetProfileInfoCache();
322  size_t profile_index = cache.GetIndexOfProfileWithPath(profile_file_path);
323  if (profile_index == std::string::npos)
324    return;
325
326  Profile* profile =
327      g_browser_process->profile_manager()->GetProfile(profile_file_path);
328  if (!profile)
329    return;
330
331  std::string icon_url;
332  if (!args->GetString(1, &icon_url))
333    return;
334
335  // Metrics logging variable.
336  bool previously_using_gaia_icon =
337      cache.IsUsingGAIAPictureOfProfileAtIndex(profile_index);
338
339  size_t new_icon_index;
340  if (icon_url == gaia_picture_url_) {
341    cache.SetIsUsingGAIAPictureOfProfileAtIndex(profile_index, true);
342    if (!previously_using_gaia_icon) {
343      // Only log if they changed to the GAIA photo.
344      // Selection of GAIA photo as avatar is logged as part of the function
345      // below.
346      ProfileMetrics::LogProfileSwitchGaia(ProfileMetrics::GAIA_OPT_IN);
347    }
348  } else if (profiles::IsDefaultAvatarIconUrl(icon_url, &new_icon_index)) {
349    ProfileMetrics::LogProfileAvatarSelection(new_icon_index);
350    PrefService* pref_service = profile->GetPrefs();
351    // Updating the profile preference will cause the cache to be updated for
352    // this preference.
353    pref_service->SetInteger(prefs::kProfileAvatarIndex, new_icon_index);
354    cache.SetIsUsingGAIAPictureOfProfileAtIndex(profile_index, false);
355  }
356  ProfileMetrics::LogProfileUpdate(profile_file_path);
357
358  if (profile->IsManaged())
359    return;
360
361  base::string16 new_profile_name;
362  if (!args->GetString(2, &new_profile_name))
363    return;
364
365  profiles::UpdateProfileName(profile, new_profile_name);
366}
367
368#if defined(ENABLE_SETTINGS_APP)
369void ManageProfileHandler::SwitchAppListProfile(const base::ListValue* args) {
370  DCHECK(args);
371  DCHECK(profiles::IsMultipleProfilesEnabled());
372
373  const base::Value* file_path_value;
374  base::FilePath profile_file_path;
375  if (!args->Get(0, &file_path_value) ||
376      !base::GetValueAsFilePath(*file_path_value, &profile_file_path))
377    return;
378
379  AppListService* app_list_service = AppListService::Get(
380      options::helper::GetDesktopType(web_ui()));
381  app_list_service->SetProfilePath(profile_file_path);
382  app_list_service->Show();
383
384  // Close the settings app, since it will now be for the wrong profile.
385  web_ui()->GetWebContents()->Close();
386}
387#endif  // defined(ENABLE_SETTINGS_APP)
388
389void ManageProfileHandler::ProfileIconSelectionChanged(
390    const base::ListValue* args) {
391  DCHECK(args);
392
393  base::FilePath profile_file_path;
394  if (!GetProfilePathFromArgs(args, &profile_file_path))
395    return;
396
397  // Currently this only supports editing the current profile's info.
398  if (profile_file_path != Profile::FromWebUI(web_ui())->GetPath())
399    return;
400
401  std::string icon_url;
402  if (!args->GetString(1, &icon_url))
403    return;
404
405  if (icon_url != gaia_picture_url_)
406    return;
407
408  // If the selection is the GAIA picture then also show the profile name in the
409  // text field. This will display either the GAIA given name, if available,
410  // or the first name.
411  ProfileInfoCache& cache =
412      g_browser_process->profile_manager()->GetProfileInfoCache();
413  size_t profile_index = cache.GetIndexOfProfileWithPath(profile_file_path);
414  if (profile_index == std::string::npos)
415    return;
416  base::string16 gaia_name = cache.GetNameOfProfileAtIndex(profile_index);
417  if (gaia_name.empty())
418    return;
419
420  base::StringValue gaia_name_value(gaia_name);
421  base::StringValue mode_value(kManageProfileIdentifier);
422  web_ui()->CallJavascriptFunction("ManageProfileOverlay.setProfileName",
423                                   gaia_name_value, mode_value);
424}
425
426void ManageProfileHandler::RequestHasProfileShortcuts(
427    const base::ListValue* args) {
428  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
429  DCHECK(ProfileShortcutManager::IsFeatureEnabled());
430
431  base::FilePath profile_file_path;
432  if (!GetProfilePathFromArgs(args, &profile_file_path))
433    return;
434
435  const ProfileInfoCache& cache =
436      g_browser_process->profile_manager()->GetProfileInfoCache();
437  size_t profile_index = cache.GetIndexOfProfileWithPath(profile_file_path);
438  if (profile_index == std::string::npos)
439    return;
440
441  const base::FilePath profile_path =
442      cache.GetPathOfProfileAtIndex(profile_index);
443  ProfileShortcutManager* shortcut_manager =
444      g_browser_process->profile_manager()->profile_shortcut_manager();
445  shortcut_manager->HasProfileShortcuts(
446      profile_path, base::Bind(&ManageProfileHandler::OnHasProfileShortcuts,
447                               weak_factory_.GetWeakPtr()));
448}
449
450void ManageProfileHandler::RequestCreateProfileUpdate(
451    const base::ListValue* args) {
452  Profile* profile = Profile::FromWebUI(web_ui());
453  SigninManagerBase* manager =
454      SigninManagerFactory::GetForProfile(profile);
455  base::string16 username =
456      base::UTF8ToUTF16(manager->GetAuthenticatedUsername());
457  ProfileSyncService* service =
458     ProfileSyncServiceFactory::GetForProfile(profile);
459  GoogleServiceAuthError::State state = service->GetAuthError().state();
460  bool has_error = (state == GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS ||
461                    state == GoogleServiceAuthError::USER_NOT_SIGNED_UP ||
462                    state == GoogleServiceAuthError::ACCOUNT_DELETED ||
463                    state == GoogleServiceAuthError::ACCOUNT_DISABLED);
464  web_ui()->CallJavascriptFunction("CreateProfileOverlay.updateSignedInStatus",
465                                   base::StringValue(username),
466                                   base::FundamentalValue(has_error));
467
468  base::DictionaryValue replacements;
469  GenerateSignedinUserSpecificStrings(&replacements);
470  web_ui()->CallJavascriptFunction("loadTimeData.overrideValues", replacements);
471
472  OnCreateManagedUserPrefChange();
473}
474
475void ManageProfileHandler::OnCreateManagedUserPrefChange() {
476  PrefService* prefs = Profile::FromWebUI(web_ui())->GetPrefs();
477  base::FundamentalValue allowed(
478      prefs->GetBoolean(prefs::kManagedUserCreationAllowed));
479  web_ui()->CallJavascriptFunction(
480      "CreateProfileOverlay.updateManagedUsersAllowed", allowed);
481}
482
483void ManageProfileHandler::OnHasProfileShortcuts(bool has_shortcuts) {
484  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
485
486  const base::FundamentalValue has_shortcuts_value(has_shortcuts);
487  web_ui()->CallJavascriptFunction(
488      "ManageProfileOverlay.receiveHasProfileShortcuts", has_shortcuts_value);
489}
490
491void ManageProfileHandler::AddProfileShortcut(const base::ListValue* args) {
492  base::FilePath profile_file_path;
493  if (!GetProfilePathFromArgs(args, &profile_file_path))
494    return;
495
496  DCHECK(ProfileShortcutManager::IsFeatureEnabled());
497  ProfileShortcutManager* shortcut_manager =
498      g_browser_process->profile_manager()->profile_shortcut_manager();
499  DCHECK(shortcut_manager);
500
501  shortcut_manager->CreateProfileShortcut(profile_file_path);
502
503  // Update the UI buttons.
504  OnHasProfileShortcuts(true);
505}
506
507void ManageProfileHandler::RemoveProfileShortcut(const base::ListValue* args) {
508  base::FilePath profile_file_path;
509  if (!GetProfilePathFromArgs(args, &profile_file_path))
510    return;
511
512  DCHECK(ProfileShortcutManager::IsFeatureEnabled());
513  ProfileShortcutManager* shortcut_manager =
514    g_browser_process->profile_manager()->profile_shortcut_manager();
515  DCHECK(shortcut_manager);
516
517  shortcut_manager->RemoveProfileShortcuts(profile_file_path);
518
519  // Update the UI buttons.
520  OnHasProfileShortcuts(false);
521}
522
523}  // namespace options
524