Home | History | Annotate | Download | only in profiles
      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