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