1 // Copyright (c) 2012 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/profiles/avatar_menu_model.h" 6 7 #include "base/bind.h" 8 #include "base/command_line.h" 9 #include "base/metrics/field_trial.h" 10 #include "base/stl_util.h" 11 #include "base/strings/string_number_conversions.h" 12 #include "base/strings/utf_string_conversions.h" 13 #include "chrome/browser/browser_process.h" 14 #include "chrome/browser/chrome_notification_types.h" 15 #include "chrome/browser/prefs/incognito_mode_prefs.h" 16 #include "chrome/browser/profiles/avatar_menu_model_observer.h" 17 #include "chrome/browser/profiles/profile.h" 18 #include "chrome/browser/profiles/profile_info_cache.h" 19 #include "chrome/browser/profiles/profile_info_util.h" 20 #include "chrome/browser/profiles/profile_manager.h" 21 #include "chrome/browser/profiles/profile_metrics.h" 22 #include "chrome/browser/profiles/profile_window.h" 23 #include "chrome/browser/profiles/profiles_state.h" 24 #include "chrome/browser/signin/signin_promo.h" 25 #include "chrome/browser/ui/browser.h" 26 #include "chrome/browser/ui/browser_list.h" 27 #include "chrome/browser/ui/browser_window.h" 28 #include "chrome/browser/ui/chrome_pages.h" 29 #include "chrome/browser/ui/host_desktop.h" 30 #include "chrome/browser/ui/startup/startup_browser_creator.h" 31 #include "chrome/common/chrome_switches.h" 32 #include "chrome/common/url_constants.h" 33 #include "content/public/browser/browser_thread.h" 34 #include "content/public/browser/notification_service.h" 35 #include "content/public/browser/site_instance.h" 36 #include "google_apis/gaia/gaia_urls.h" 37 #include "grit/generated_resources.h" 38 #include "grit/theme_resources.h" 39 #include "ui/base/l10n/l10n_util.h" 40 #include "ui/base/resource/resource_bundle.h" 41 42 #if defined(ENABLE_MANAGED_USERS) 43 #include "chrome/browser/managed_mode/managed_user_service.h" 44 #include "chrome/browser/managed_mode/managed_user_service_factory.h" 45 #endif 46 47 using content::BrowserThread; 48 49 namespace { 50 51 void OnProfileCreated(bool always_create, 52 chrome::HostDesktopType desktop_type, 53 Profile* profile, 54 Profile::CreateStatus status) { 55 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 56 57 if (status == Profile::CREATE_STATUS_INITIALIZED) { 58 profiles::FindOrCreateNewWindowForProfile( 59 profile, 60 chrome::startup::IS_NOT_PROCESS_STARTUP, 61 chrome::startup::IS_NOT_FIRST_RUN, 62 desktop_type, 63 always_create); 64 } 65 } 66 67 void OnGuestProfileCreated(bool always_create, 68 chrome::HostDesktopType desktop_type, 69 Profile* profile, 70 Profile::CreateStatus status) { 71 IncognitoModePrefs::SetAvailability( 72 profile->GetPrefs(), 73 IncognitoModePrefs::FORCED); 74 OnProfileCreated(always_create, desktop_type, profile, status); 75 } 76 77 // Constants for the show profile switcher experiment 78 const char kShowProfileSwitcherFieldTrialName[] = "ShowProfileSwitcher"; 79 const char kAlwaysShowSwitcherGroupName[] = "AlwaysShow"; 80 81 82 class SignoutTracker : public content::WebContentsObserver { 83 public: 84 SignoutTracker(Profile* profile, const GURL& signout_landing_url, 85 content::WebContents* contents); 86 87 virtual void WebContentsDestroyed(content::WebContents* contents) OVERRIDE; 88 virtual void DidStopLoading(content::RenderViewHost* render_view_host) 89 OVERRIDE; 90 91 private: 92 scoped_ptr<content::WebContents> contents_; 93 GURL signout_landing_url_; 94 Profile* profile_; 95 96 DISALLOW_COPY_AND_ASSIGN(SignoutTracker); 97 }; 98 99 100 SignoutTracker::SignoutTracker(Profile* profile, 101 const GURL& signout_landing_url, 102 content::WebContents* contents) 103 : WebContentsObserver(contents), 104 contents_(contents), 105 signout_landing_url_(signout_landing_url), 106 profile_(profile) { 107 } 108 109 void SignoutTracker::DidStopLoading(content::RenderViewHost* render_view_host) { 110 // Only close when we reach the final landing; ignore redirects until then. 111 if (web_contents()->GetURL() == signout_landing_url_) { 112 Observe(NULL); 113 BrowserList::CloseAllBrowsersWithProfile(profile_); 114 delete this; /* success */ 115 } 116 } 117 118 void SignoutTracker::WebContentsDestroyed(content::WebContents* contents) { 119 delete this; /* failure */ 120 } 121 122 } // namespace 123 124 AvatarMenuModel::AvatarMenuModel(ProfileInfoInterface* profile_cache, 125 AvatarMenuModelObserver* observer, 126 Browser* browser) 127 : profile_info_(profile_cache), 128 observer_(observer), 129 browser_(browser) { 130 DCHECK(profile_info_); 131 // Don't DCHECK(browser_) so that unit tests can reuse this ctor. 132 133 // Register this as an observer of the info cache. 134 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED, 135 content::NotificationService::AllSources()); 136 137 // Build the initial menu. 138 RebuildMenu(); 139 } 140 141 AvatarMenuModel::~AvatarMenuModel() { 142 ClearMenu(); 143 } 144 145 AvatarMenuModel::Item::Item(size_t model_index, const gfx::Image& icon) 146 : icon(icon), 147 active(false), 148 signed_in(false), 149 signin_required(false), 150 model_index(model_index) { 151 } 152 153 AvatarMenuModel::Item::~Item() { 154 } 155 156 void AvatarMenuModel::SwitchToProfile(size_t index, bool always_create) { 157 DCHECK(profiles::IsMultipleProfilesEnabled() || 158 index == GetActiveProfileIndex()); 159 const Item& item = GetItemAt(index); 160 base::FilePath path = 161 profile_info_->GetPathOfProfileAtIndex(item.model_index); 162 163 chrome::HostDesktopType desktop_type = chrome::GetActiveDesktop(); 164 if (browser_) 165 desktop_type = browser_->host_desktop_type(); 166 167 profiles::SwitchToProfile(path, desktop_type, always_create); 168 ProfileMetrics::LogProfileSwitchUser(ProfileMetrics::SWITCH_PROFILE_ICON); 169 } 170 171 void AvatarMenuModel::EditProfile(size_t index) { 172 Browser* browser = browser_; 173 if (!browser) { 174 Profile* profile = g_browser_process->profile_manager()->GetProfileByPath( 175 profile_info_->GetPathOfProfileAtIndex(GetItemAt(index).model_index)); 176 browser = new Browser(Browser::CreateParams(profile, 177 chrome::GetActiveDesktop())); 178 } 179 std::string page = chrome::kManageProfileSubPage; 180 page += "#"; 181 page += base::IntToString(static_cast<int>(index)); 182 chrome::ShowSettingsSubPage(browser, page); 183 } 184 185 void AvatarMenuModel::AddNewProfile(ProfileMetrics::ProfileAdd type) { 186 Browser* browser = browser_; 187 if (!browser) { 188 const Browser::CreateParams params(ProfileManager::GetLastUsedProfile(), 189 chrome::GetActiveDesktop()); 190 browser = new Browser(params); 191 } 192 chrome::ShowSettingsSubPage(browser, chrome::kCreateProfileSubPage); 193 ProfileMetrics::LogProfileAddNewUser(type); 194 } 195 196 base::FilePath AvatarMenuModel::GetProfilePath(size_t index) { 197 const Item& item = GetItemAt(index); 198 return profile_info_->GetPathOfProfileAtIndex(item.model_index); 199 } 200 201 // static 202 void AvatarMenuModel::SwitchToGuestProfileWindow(Browser* browser) { 203 ProfileManager* profile_manager = g_browser_process->profile_manager(); 204 profile_manager->CreateProfileAsync(ProfileManager::GetGuestProfilePath(), 205 base::Bind(&OnGuestProfileCreated, 206 false, 207 browser->host_desktop_type()), 208 string16(), 209 string16(), 210 std::string()); 211 } 212 213 size_t AvatarMenuModel::GetNumberOfItems() { 214 return items_.size(); 215 } 216 217 size_t AvatarMenuModel::GetActiveProfileIndex() { 218 // During singleton profile deletion, this function can be called with no 219 // profiles in the model - crbug.com/102278 . 220 if (items_.size() == 0) 221 return 0; 222 223 Profile* active_profile = NULL; 224 if (!browser_) 225 active_profile = ProfileManager::GetLastUsedProfile(); 226 else 227 active_profile = browser_->profile(); 228 229 size_t index = 230 profile_info_->GetIndexOfProfileWithPath(active_profile->GetPath()); 231 232 DCHECK_LT(index, items_.size()); 233 return index; 234 } 235 236 const AvatarMenuModel::Item& AvatarMenuModel::GetItemAt(size_t index) { 237 DCHECK_LT(index, items_.size()); 238 return *items_[index]; 239 } 240 241 bool AvatarMenuModel::ShouldShowAddNewProfileLink() const { 242 // |browser_| can be NULL in unit_tests. 243 return !browser_ || !browser_->profile()->IsManaged(); 244 } 245 246 base::string16 AvatarMenuModel::GetManagedUserInformation() const { 247 // |browser_| can be NULL in unit_tests. 248 if (browser_ && browser_->profile()->IsManaged()) { 249 #if defined(ENABLE_MANAGED_USERS) 250 ManagedUserService* service = ManagedUserServiceFactory::GetForProfile( 251 browser_->profile()); 252 base::string16 custodian = UTF8ToUTF16(service->GetCustodianEmailAddress()); 253 return l10n_util::GetStringFUTF16(IDS_MANAGED_USER_INFO, custodian); 254 #endif 255 } 256 return base::string16(); 257 } 258 259 const gfx::Image& AvatarMenuModel::GetManagedUserIcon() const { 260 return ResourceBundle::GetSharedInstance().GetNativeImageNamed( 261 IDR_MANAGED_USER_ICON); 262 } 263 264 void AvatarMenuModel::Observe(int type, 265 const content::NotificationSource& source, 266 const content::NotificationDetails& details) { 267 DCHECK_EQ(chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED, type); 268 RebuildMenu(); 269 if (observer_) 270 observer_->OnAvatarMenuModelChanged(this); 271 } 272 273 // static 274 bool AvatarMenuModel::ShouldShowAvatarMenu() { 275 #if defined(OS_CHROMEOS) 276 // On Chrome OS we use different UI for multi-profiles. 277 return false; 278 #endif 279 280 if (base::FieldTrialList::FindFullName(kShowProfileSwitcherFieldTrialName) == 281 kAlwaysShowSwitcherGroupName) { 282 // We should only be in this group when multi-profiles is enabled. 283 DCHECK(profiles::IsMultipleProfilesEnabled()); 284 return true; 285 } 286 if (profiles::IsMultipleProfilesEnabled()) { 287 return profiles::IsNewProfileManagementEnabled() || 288 (g_browser_process->profile_manager() && 289 g_browser_process->profile_manager()->GetNumberOfProfiles() > 1); 290 } 291 return false; 292 } 293 294 void AvatarMenuModel::RebuildMenu() { 295 ClearMenu(); 296 297 const size_t count = profile_info_->GetNumberOfProfiles(); 298 for (size_t i = 0; i < count; ++i) { 299 bool is_gaia_picture = 300 profile_info_->IsUsingGAIAPictureOfProfileAtIndex(i) && 301 profile_info_->GetGAIAPictureOfProfileAtIndex(i); 302 303 gfx::Image icon = profile_info_->GetAvatarIconOfProfileAtIndex(i); 304 if (!CommandLine::ForCurrentProcess()->HasSwitch( 305 switches::kNewProfileManagement)) { 306 // old avatar menu uses resized-small images 307 icon = profiles::GetAvatarIconForMenu(icon, is_gaia_picture); 308 } 309 310 Item* item = new Item(i, icon); 311 item->name = profile_info_->GetNameOfProfileAtIndex(i); 312 item->sync_state = profile_info_->GetUserNameOfProfileAtIndex(i); 313 item->signed_in = !item->sync_state.empty(); 314 if (!item->signed_in) { 315 item->sync_state = l10n_util::GetStringUTF16( 316 profile_info_->ProfileIsManagedAtIndex(i) ? 317 IDS_MANAGED_USER_AVATAR_LABEL : IDS_PROFILES_LOCAL_PROFILE_STATE); 318 } 319 if (browser_) { 320 base::FilePath path = profile_info_->GetPathOfProfileAtIndex(i); 321 item->active = browser_->profile()->GetPath() == path; 322 } 323 item->signin_required = profile_info_->ProfileIsSigninRequiredAtIndex(i); 324 items_.push_back(item); 325 } 326 } 327 328 void AvatarMenuModel::ClearMenu() { 329 STLDeleteElements(&items_); 330 } 331 332 333 content::WebContents* AvatarMenuModel::BeginSignOut() { 334 ProfileManager* profile_manager = g_browser_process->profile_manager(); 335 Profile* current_profile = browser_->profile(); 336 337 ProfileInfoCache& cache = profile_manager->GetProfileInfoCache(); 338 size_t index = cache.GetIndexOfProfileWithPath(current_profile->GetPath()); 339 cache.SetProfileSigninRequiredAtIndex(index, true); 340 341 std::string landing_url = signin::GetLandingURL("close", 1).spec(); 342 GURL logout_url(GaiaUrls::GetInstance()->service_logout_url() + 343 "?continue=" + landing_url); 344 if (!logout_override_.empty()) { 345 // We're testing... 346 landing_url = logout_override_; 347 logout_url = GURL(logout_override_); 348 } 349 350 content::WebContents::CreateParams create_params(current_profile); 351 create_params.site_instance = 352 content::SiteInstance::CreateForURL(current_profile, logout_url); 353 content::WebContents* contents = content::WebContents::Create(create_params); 354 contents->GetController().LoadURL( 355 logout_url, content::Referrer(), 356 content::PAGE_TRANSITION_GENERATED, std::string()); 357 358 // This object may be destructed when the menu closes but we need something 359 // around to finish the sign-out process and close the profile windows. 360 new SignoutTracker(current_profile, GURL(landing_url), contents); 361 362 return contents; // returned for testing purposes 363 } 364