manage_profile_handler.cc revision 3551c9c881056c480085172ff9840cab31610854
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/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/managed_mode/managed_user_service.h"
18#include "chrome/browser/prefs/scoped_user_pref_update.h"
19#include "chrome/browser/profiles/gaia_info_update_service.h"
20#include "chrome/browser/profiles/profile.h"
21#include "chrome/browser/profiles/profile_info_cache.h"
22#include "chrome/browser/profiles/profile_info_util.h"
23#include "chrome/browser/profiles/profile_manager.h"
24#include "chrome/browser/profiles/profile_metrics.h"
25#include "chrome/browser/profiles/profile_shortcut_manager.h"
26#include "chrome/browser/profiles/profile_window.h"
27#include "chrome/browser/profiles/profiles_state.h"
28#include "chrome/browser/signin/signin_manager.h"
29#include "chrome/browser/signin/signin_manager_factory.h"
30#include "chrome/browser/sync/profile_sync_service.h"
31#include "chrome/browser/sync/profile_sync_service_factory.h"
32#include "chrome/browser/ui/browser_finder.h"
33#include "chrome/common/chrome_switches.h"
34#include "chrome/common/pref_names.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 "grit/generated_resources.h"
39#include "ui/base/l10n/l10n_util.h"
40#include "ui/webui/web_ui_util.h"
41
42#if defined(ENABLE_SETTINGS_APP)
43#include "chrome/browser/ui/app_list/app_list_service.h"
44#include "content/public/browser/web_contents.h"
45#endif
46
47namespace options {
48
49namespace {
50
51const char kCreateProfileIconGridName[] = "create-profile-icon-grid";
52const char kManageProfileIconGridName[] = "manage-profile-icon-grid";
53
54// Given |args| from the WebUI, parses value 0 as a FilePath |profile_file_path|
55// and returns true on success.
56bool GetProfilePathFromArgs(const ListValue* args,
57                            base::FilePath* profile_file_path) {
58  const Value* file_path_value;
59  if (!args->Get(0, &file_path_value))
60    return false;
61  return base::GetValueAsFilePath(*file_path_value, profile_file_path);
62}
63
64void OnNewDefaultProfileCreated(
65    chrome::HostDesktopType desktop_type,
66    Profile* profile,
67    Profile::CreateStatus status) {
68  if (status == Profile::CREATE_STATUS_INITIALIZED) {
69    profiles::FindOrCreateNewWindowForProfile(
70        profile,
71        chrome::startup::IS_PROCESS_STARTUP,
72        chrome::startup::IS_FIRST_RUN,
73        desktop_type,
74        false);
75  }
76}
77
78}  // namespace
79
80ManageProfileHandler::ManageProfileHandler()
81    : weak_factory_(this) {
82}
83
84ManageProfileHandler::~ManageProfileHandler() {
85  ProfileSyncService* service =
86      ProfileSyncServiceFactory::GetForProfile(Profile::FromWebUI(web_ui()));
87  // Sync may be disabled in tests.
88  if (service)
89    service->RemoveObserver(this);
90}
91
92void ManageProfileHandler::GetLocalizedValues(
93    DictionaryValue* localized_strings) {
94  DCHECK(localized_strings);
95
96  static OptionsStringResource resources[] = {
97    { "manageProfilesNameLabel", IDS_PROFILES_MANAGE_NAME_LABEL },
98    { "manageProfilesDuplicateNameError",
99        IDS_PROFILES_MANAGE_DUPLICATE_NAME_ERROR },
100    { "manageProfilesIconLabel", IDS_PROFILES_MANAGE_ICON_LABEL },
101    { "manageProfilesManagedSignedInLabel",
102        IDS_PROFILES_CREATE_MANAGED_SIGNED_IN_LABEL },
103    { "manageProfilesManagedNotSignedInLabel",
104        IDS_PROFILES_CREATE_MANAGED_NOT_SIGNED_IN_LABEL },
105    { "manageProfilesManagedAccountDetailsOutOfDate",
106        IDS_PROFILES_CREATE_MANAGED_ACCOUNT_DETAILS_OUT_OF_DATE_LABEL },
107    { "manageProfilesManagedSignInAgainLink",
108        IDS_PROFILES_CREATE_MANAGED_ACCOUNT_SIGN_IN_AGAIN_LINK },
109    { "manageProfilesManagedNotSignedInLink",
110        IDS_PROFILES_CREATE_MANAGED_NOT_SIGNED_IN_LINK },
111    { "manageProfilesSelectExistingManagedProfileLabel",
112        IDS_PROFILES_CREATE_MANAGED_FROM_EXISTING_LABEL},
113    { "deleteProfileTitle", IDS_PROFILES_DELETE_TITLE },
114    { "deleteProfileOK", IDS_PROFILES_DELETE_OK_BUTTON_LABEL },
115    { "deleteProfileMessage", IDS_PROFILES_DELETE_MESSAGE },
116    { "deleteManagedProfileAddendum", IDS_PROFILES_DELETE_MANAGED_ADDENDUM },
117    { "createProfileTitle", IDS_PROFILES_CREATE_TITLE },
118    { "createProfileInstructions", IDS_PROFILES_CREATE_INSTRUCTIONS },
119    { "createProfileConfirm", IDS_PROFILES_CREATE_CONFIRM },
120    { "createProfileShortcutCheckbox", IDS_PROFILES_CREATE_SHORTCUT_CHECKBOX },
121    { "createProfileShortcutButton", IDS_PROFILES_CREATE_SHORTCUT_BUTTON },
122    { "removeProfileShortcutButton", IDS_PROFILES_REMOVE_SHORTCUT_BUTTON },
123  };
124
125  RegisterStrings(localized_strings, resources, arraysize(resources));
126  RegisterTitle(localized_strings, "manageProfile",
127                IDS_PROFILES_MANAGE_TITLE);
128  RegisterTitle(localized_strings, "createProfile",
129                IDS_PROFILES_CREATE_TITLE);
130
131  localized_strings->SetBoolean("profileShortcutsEnabled",
132                                ProfileShortcutManager::IsFeatureEnabled());
133  localized_strings->SetBoolean("managedUsersEnabled",
134                                ManagedUserService::AreManagedUsersEnabled());
135
136  localized_strings->SetBoolean(
137      "allowCreateExistingManagedUsers",
138      CommandLine::ForCurrentProcess()->HasSwitch(
139          switches::kAllowCreateExistingManagedUsers));
140}
141
142void ManageProfileHandler::InitializeHandler() {
143  registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED,
144                 content::NotificationService::AllSources());
145
146  Profile* profile = Profile::FromWebUI(web_ui());
147  pref_change_registrar_.Init(profile->GetPrefs());
148  pref_change_registrar_.Add(
149      prefs::kManagedUserCreationAllowed,
150      base::Bind(&ManageProfileHandler::OnCreateManagedUserPrefChange,
151                 base::Unretained(this)));
152  ProfileSyncService* service =
153      ProfileSyncServiceFactory::GetForProfile(profile);
154  // Sync may be disabled for tests.
155  if (service)
156    service->AddObserver(this);
157}
158
159void ManageProfileHandler::InitializePage() {
160  SendProfileNames();
161  OnCreateManagedUserPrefChange();
162}
163
164void ManageProfileHandler::RegisterMessages() {
165  web_ui()->RegisterMessageCallback("setProfileIconAndName",
166      base::Bind(&ManageProfileHandler::SetProfileIconAndName,
167                 base::Unretained(this)));
168  web_ui()->RegisterMessageCallback("requestDefaultProfileIcons",
169      base::Bind(&ManageProfileHandler::RequestDefaultProfileIcons,
170                 base::Unretained(this)));
171  web_ui()->RegisterMessageCallback("requestNewProfileDefaults",
172      base::Bind(&ManageProfileHandler::RequestNewProfileDefaults,
173                 base::Unretained(this)));
174  web_ui()->RegisterMessageCallback("requestHasProfileShortcuts",
175      base::Bind(&ManageProfileHandler::RequestHasProfileShortcuts,
176                 base::Unretained(this)));
177  web_ui()->RegisterMessageCallback("requestCreateProfileUpdate",
178      base::Bind(&ManageProfileHandler::RequestCreateProfileUpdate,
179                 base::Unretained(this)));
180  web_ui()->RegisterMessageCallback("profileIconSelectionChanged",
181      base::Bind(&ManageProfileHandler::ProfileIconSelectionChanged,
182                 base::Unretained(this)));
183#if defined(ENABLE_SETTINGS_APP)
184  web_ui()->RegisterMessageCallback("switchAppListProfile",
185      base::Bind(&ManageProfileHandler::SwitchAppListProfile,
186                 base::Unretained(this)));
187#endif
188  web_ui()->RegisterMessageCallback("addProfileShortcut",
189      base::Bind(&ManageProfileHandler::AddProfileShortcut,
190                 base::Unretained(this)));
191  web_ui()->RegisterMessageCallback("removeProfileShortcut",
192      base::Bind(&ManageProfileHandler::RemoveProfileShortcut,
193                 base::Unretained(this)));
194  web_ui()->RegisterMessageCallback("requestExistingManagedUsers",
195      base::Bind(&ManageProfileHandler::RequestExistingManagedUsers,
196                 base::Unretained(this)));
197}
198
199void ManageProfileHandler::Observe(
200    int type,
201    const content::NotificationSource& source,
202    const content::NotificationDetails& details) {
203  if (type == chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED) {
204    // If the browser shuts down during supervised-profile creation, deleting
205    // the unregistered supervised-user profile triggers this notification,
206    // but the RenderViewHost the profile info would be sent to has already been
207    // destroyed.
208    if (!web_ui()->GetWebContents()->GetRenderViewHost())
209      return;
210    SendProfileNames();
211    base::StringValue value(kManageProfileIconGridName);
212    SendProfileIcons(value);
213  } else {
214    OptionsPageUIHandler::Observe(type, source, details);
215  }
216}
217
218void ManageProfileHandler::OnStateChanged() {
219  RequestCreateProfileUpdate(NULL);
220}
221
222void ManageProfileHandler::RequestDefaultProfileIcons(const ListValue* args) {
223  base::StringValue create_value(kCreateProfileIconGridName);
224  base::StringValue manage_value(kManageProfileIconGridName);
225  SendProfileIcons(manage_value);
226  SendProfileIcons(create_value);
227}
228
229void ManageProfileHandler::RequestNewProfileDefaults(const ListValue* args) {
230  const ProfileInfoCache& cache =
231      g_browser_process->profile_manager()->GetProfileInfoCache();
232  const size_t icon_index = cache.ChooseAvatarIconIndexForNewProfile();
233
234  DictionaryValue profile_info;
235  profile_info.SetString("name", cache.ChooseNameForNewProfile(icon_index));
236  profile_info.SetString("iconURL", cache.GetDefaultAvatarIconUrl(icon_index));
237
238  web_ui()->CallJavascriptFunction(
239      "ManageProfileOverlay.receiveNewProfileDefaults", profile_info);
240}
241
242void ManageProfileHandler::RequestExistingManagedUsers(const ListValue* args) {
243  Profile* profile = Profile::FromWebUI(web_ui());
244  if (profile->IsManaged())
245    return;
246
247  const ProfileInfoCache& cache =
248      g_browser_process->profile_manager()->GetProfileInfoCache();
249  std::set<std::string> managed_user_ids;
250  for (size_t i = 0; i < cache.GetNumberOfProfiles(); ++i)
251    managed_user_ids.insert(cache.GetManagedUserIdOfProfileAtIndex(i));
252
253  const DictionaryValue* dict =
254      profile->GetPrefs()->GetDictionary(prefs::kManagedUsers);
255  DictionaryValue id_names_dict;
256  for (DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) {
257    if (managed_user_ids.find(it.key()) != managed_user_ids.end())
258      continue;
259    const DictionaryValue* value = NULL;
260    bool success = it.value().GetAsDictionary(&value);
261    DCHECK(success);
262    std::string name;
263    value->GetString("name", &name);
264    id_names_dict.SetString(it.key(), name);
265  }
266
267  web_ui()->CallJavascriptFunction(
268      "CreateProfileOverlay.receiveExistingManagedUsers", id_names_dict);
269}
270
271void ManageProfileHandler::SendProfileIcons(
272    const base::StringValue& icon_grid) {
273  ListValue image_url_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.Append(new base::StringValue(gaia_picture_url_));
287    }
288  }
289
290  // Next add the default avatar icons.
291  for (size_t i = 0; i < ProfileInfoCache::GetDefaultAvatarIconCount(); i++) {
292    std::string url = ProfileInfoCache::GetDefaultAvatarIconUrl(i);
293    image_url_list.Append(new base::StringValue(url));
294  }
295
296  web_ui()->CallJavascriptFunction(
297      "ManageProfileOverlay.receiveDefaultProfileIcons", icon_grid,
298      image_url_list);
299}
300
301void ManageProfileHandler::SendProfileNames() {
302  const ProfileInfoCache& cache =
303      g_browser_process->profile_manager()->GetProfileInfoCache();
304  DictionaryValue profile_name_dict;
305  for (size_t i = 0, e = cache.GetNumberOfProfiles(); i < e; ++i)
306    profile_name_dict.SetBoolean(UTF16ToUTF8(cache.GetNameOfProfileAtIndex(i)),
307                                 true);
308
309  web_ui()->CallJavascriptFunction("ManageProfileOverlay.receiveProfileNames",
310                                   profile_name_dict);
311}
312
313void ManageProfileHandler::SetProfileIconAndName(const 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 (cache.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  string16 new_profile_name;
362  if (!args->GetString(2, &new_profile_name))
363    return;
364
365  if (new_profile_name == cache.GetGAIANameOfProfileAtIndex(profile_index)) {
366    // Set the profile to use the GAIA name as the profile name. Note, this
367    // is a little weird if the user typed their GAIA name manually but
368    // it's not a big deal.
369    cache.SetIsUsingGAIANameOfProfileAtIndex(profile_index, true);
370  } else {
371    PrefService* pref_service = profile->GetPrefs();
372    // Updating the profile preference will cause the cache to be updated for
373    // this preference.
374    pref_service->SetString(prefs::kProfileName, UTF16ToUTF8(new_profile_name));
375
376    // Changing the profile name can invalidate the profile index.
377    profile_index = cache.GetIndexOfProfileWithPath(profile_file_path);
378    if (profile_index == std::string::npos)
379      return;
380
381    cache.SetIsUsingGAIANameOfProfileAtIndex(profile_index, false);
382  }
383}
384
385#if defined(ENABLE_SETTINGS_APP)
386void ManageProfileHandler::SwitchAppListProfile(const ListValue* args) {
387  DCHECK(args);
388  DCHECK(profiles::IsMultipleProfilesEnabled());
389
390  const Value* file_path_value;
391  base::FilePath profile_file_path;
392  if (!args->Get(0, &file_path_value) ||
393      !base::GetValueAsFilePath(*file_path_value, &profile_file_path))
394    return;
395
396  AppListService* app_list_service = AppListService::Get();
397  app_list_service->SetProfilePath(profile_file_path);
398  app_list_service->Show();
399
400  // Close the settings app, since it will now be for the wrong profile.
401  web_ui()->GetWebContents()->Close();
402}
403#endif  // defined(ENABLE_SETTINGS_APP)
404
405void ManageProfileHandler::ProfileIconSelectionChanged(
406    const base::ListValue* args) {
407  DCHECK(args);
408
409  base::FilePath profile_file_path;
410  if (!GetProfilePathFromArgs(args, &profile_file_path))
411    return;
412
413  // Currently this only supports editing the current profile's info.
414  if (profile_file_path != Profile::FromWebUI(web_ui())->GetPath())
415    return;
416
417  std::string icon_url;
418  if (!args->GetString(1, &icon_url))
419    return;
420
421  if (icon_url != gaia_picture_url_)
422    return;
423
424  // If the selection is the GAIA picture then also show the GAIA name in the
425  // text field.
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  string16 gaia_name = cache.GetGAIANameOfProfileAtIndex(profile_index);
432  if (gaia_name.empty())
433    return;
434
435  StringValue gaia_name_value(gaia_name);
436  web_ui()->CallJavascriptFunction("ManageProfileOverlay.setProfileName",
437                                   gaia_name_value);
438}
439
440void ManageProfileHandler::RequestHasProfileShortcuts(const ListValue* args) {
441  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
442  DCHECK(ProfileShortcutManager::IsFeatureEnabled());
443
444  base::FilePath profile_file_path;
445  if (!GetProfilePathFromArgs(args, &profile_file_path))
446    return;
447
448  const ProfileInfoCache& cache =
449      g_browser_process->profile_manager()->GetProfileInfoCache();
450  size_t profile_index = cache.GetIndexOfProfileWithPath(profile_file_path);
451  if (profile_index == std::string::npos)
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  string16 username = UTF8ToUTF16(manager->GetAuthenticatedUsername());
469  ProfileSyncService* service =
470     ProfileSyncServiceFactory::GetForProfile(profile);
471  GoogleServiceAuthError::State state = service->GetAuthError().state();
472  bool has_error = (state == GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS ||
473                    state == GoogleServiceAuthError::USER_NOT_SIGNED_UP ||
474                    state == GoogleServiceAuthError::ACCOUNT_DELETED ||
475                    state == GoogleServiceAuthError::ACCOUNT_DISABLED);
476  web_ui()->CallJavascriptFunction("CreateProfileOverlay.updateSignedInStatus",
477                                   base::StringValue(username),
478                                   base::FundamentalValue(has_error));
479  OnCreateManagedUserPrefChange();
480}
481
482void ManageProfileHandler::OnCreateManagedUserPrefChange() {
483  PrefService* prefs = Profile::FromWebUI(web_ui())->GetPrefs();
484  base::FundamentalValue allowed(
485      prefs->GetBoolean(prefs::kManagedUserCreationAllowed));
486  web_ui()->CallJavascriptFunction(
487      "CreateProfileOverlay.updateManagedUsersAllowed", allowed);
488}
489
490void ManageProfileHandler::OnHasProfileShortcuts(bool has_shortcuts) {
491  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
492
493  const base::FundamentalValue has_shortcuts_value(has_shortcuts);
494  web_ui()->CallJavascriptFunction(
495      "ManageProfileOverlay.receiveHasProfileShortcuts", has_shortcuts_value);
496}
497
498void ManageProfileHandler::AddProfileShortcut(const base::ListValue* args) {
499  base::FilePath profile_file_path;
500  if (!GetProfilePathFromArgs(args, &profile_file_path))
501    return;
502
503  DCHECK(ProfileShortcutManager::IsFeatureEnabled());
504  ProfileShortcutManager* shortcut_manager =
505      g_browser_process->profile_manager()->profile_shortcut_manager();
506  DCHECK(shortcut_manager);
507
508  shortcut_manager->CreateProfileShortcut(profile_file_path);
509
510  // Update the UI buttons.
511  OnHasProfileShortcuts(true);
512}
513
514void ManageProfileHandler::RemoveProfileShortcut(const base::ListValue* args) {
515  base::FilePath profile_file_path;
516  if (!GetProfilePathFromArgs(args, &profile_file_path))
517    return;
518
519  DCHECK(ProfileShortcutManager::IsFeatureEnabled());
520  ProfileShortcutManager* shortcut_manager =
521    g_browser_process->profile_manager()->profile_shortcut_manager();
522  DCHECK(shortcut_manager);
523
524  shortcut_manager->RemoveProfileShortcuts(profile_file_path);
525
526  // Update the UI buttons.
527  OnHasProfileShortcuts(false);
528}
529
530}  // namespace options
531