// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/ui/ash/multi_user_window_manager.h" #include "apps/shell_window.h" #include "apps/shell_window_registry.h" #include "ash/ash_switches.h" #include "ash/session_state_delegate.h" #include "ash/shell.h" #include "ash/shell_delegate.h" #include "ash/wm/window_state.h" #include "base/auto_reset.h" #include "base/strings/string_util.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/browser_window.h" #include "content/public/browser/notification_service.h" #include "google_apis/gaia/gaia_auth_util.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/window.h" #include "ui/base/ui_base_types.h" namespace { chrome::MultiUserWindowManager* g_instance = NULL; } // namespace namespace chrome { // This class keeps track of all applications which were started for a user. // When an app gets created, the window will be tagged for that user. Note // that the destruction does not need to be tracked here since the universal // window observer will take care of that. class AppObserver : public apps::ShellWindowRegistry::Observer { public: explicit AppObserver(const std::string& user_id) : user_id_(user_id) {} virtual ~AppObserver() {} // ShellWindowRegistry::Observer overrides: virtual void OnShellWindowAdded(apps::ShellWindow* shell_window) OVERRIDE { aura::Window* window = shell_window->GetNativeWindow(); DCHECK(window); chrome::MultiUserWindowManager::GetInstance()->SetWindowOwner(window, user_id_); } virtual void OnShellWindowIconChanged(apps::ShellWindow* shell_window) OVERRIDE {} virtual void OnShellWindowRemoved(apps::ShellWindow* shell_window) OVERRIDE {} private: std::string user_id_; DISALLOW_COPY_AND_ASSIGN(AppObserver); }; // static MultiUserWindowManager* MultiUserWindowManager::GetInstance() { if (!g_instance && ash::Shell::GetInstance()->delegate()->IsMultiProfilesEnabled() && !ash::switches::UseFullMultiProfileMode()) { g_instance = CreateInstanceInternal( GetUserIDFromProfile(ProfileManager::GetDefaultProfile())); } return g_instance; } // static void MultiUserWindowManager::DeleteInstance() { if (g_instance) delete g_instance; g_instance = NULL; } // static std::string MultiUserWindowManager::GetUserIDFromProfile(Profile* profile) { return gaia::CanonicalizeEmail(gaia::SanitizeEmail( profile->GetOriginalProfile()->GetProfileName())); } void MultiUserWindowManager::SetWindowOwner(aura::Window* window, const std::string& user_id) { // Make sure the window is valid and there was no owner yet. DCHECK(window); DCHECK(!user_id.empty()); if (GetWindowOwner(window) == user_id) return; DCHECK(GetWindowOwner(window).empty()); window_to_entry_[window] = new WindowEntry(user_id); // Set the window and the state observer. window->AddObserver(this); ash::wm::GetWindowState(window)->AddObserver(this); if (user_id != current_user_id_) SetWindowVisibility(window, false); } const std::string& MultiUserWindowManager::GetWindowOwner( aura::Window* window) { WindowToEntryMap::iterator it = window_to_entry_.find(window); return it != window_to_entry_.end() ? it->second->owner() : EmptyString(); } void MultiUserWindowManager::ShowWindowForUser(aura::Window* window, const std::string& user_id) { // If there is either no owner, or the owner is the current user, no action // is required. const std::string& owner = GetWindowOwner(window); if (owner.empty() || (owner == user_id && IsWindowOnDesktopOfUser(window, user_id))) return; // Check that we are not trying to transfer ownership of a minimized window. if (user_id != owner && ash::wm::GetWindowState(window)->IsMinimized()) return; WindowToEntryMap::iterator it = window_to_entry_.find(window); it->second->set_show_for_user(user_id); // Show the window if the added user is the current one. if (user_id == current_user_id_) SetWindowVisibility(window, true); else SetWindowVisibility(window, false); } bool MultiUserWindowManager::AreWindowsSharedAmongUsers() { WindowToEntryMap::iterator it = window_to_entry_.begin(); for (; it != window_to_entry_.end(); ++it) { if (it->second->owner() != it->second->show_for_user()) return true; } return false; } bool MultiUserWindowManager::IsWindowOnDesktopOfUser( aura::Window* window, const std::string& user_id) { const std::string& presenting_user = GetUserPresentingWindow(window); return presenting_user.empty() || presenting_user == user_id; } const std::string& MultiUserWindowManager::GetUserPresentingWindow( aura::Window* window) { WindowToEntryMap::iterator it = window_to_entry_.find(window); // If the window is not owned by anyone it is shown on all desktops and we // return the empty string. if (it == window_to_entry_.end()) return EmptyString(); // Otherwise we ask the object for its desktop. return it->second->show_for_user(); } void MultiUserWindowManager::ActiveUserChanged(const std::string& user_id) { DCHECK(user_id != current_user_id_); std::string old_user = current_user_id_; current_user_id_ = user_id; // Hide all windows which are not shown and show which should get shown. WindowToEntryMap::iterator it = window_to_entry_.begin(); for (; it != window_to_entry_.end(); ++it) { aura::Window* window = it->first; bool should_be_visible = it->second->show_for_user() == user_id && it->second->show(); bool is_visible = window->IsVisible(); if (should_be_visible != is_visible) SetWindowVisibility(window, should_be_visible); } } void MultiUserWindowManager::UserAddedToSession(const std::string& user_id) { // Make sure that all newly created applications get properly added to this // user's account. AddUser(user_id); } void MultiUserWindowManager::OnWindowDestroyed(aura::Window* window) { DCHECK(!GetWindowOwner(window).empty()); // Remove the state and the window observer. ash::wm::GetWindowState(window)->RemoveObserver(this); window->RemoveObserver(this); // Remove the window from the owners list. delete window_to_entry_[window]; window_to_entry_.erase(window); } void MultiUserWindowManager::OnWindowVisibilityChanging( aura::Window* window, bool visible) { // This command gets called first and immediately when show or hide gets // called. We remember here the desired state for restoration IF we were // not ourselves issuing the call. // Note also that using the OnWindowVisibilityChanged callback cannot be // used for this. if (!suppress_visibility_changes_) { WindowToEntryMap::iterator it = window_to_entry_.find(window); // If the window is not owned by anyone it is shown on all desktops. if (it != window_to_entry_.end()) { // Remember what was asked for so that we can restore this when the users // desktop gets restored. it->second->set_show(visible); } } } void MultiUserWindowManager::OnWindowVisibilityChanged( aura::Window* window, bool visible) { // Don't allow to make the window visible if it shouldn't be. if (visible && !IsWindowOnDesktopOfUser(window, current_user_id_)) SetWindowVisibility(window, false); } void MultiUserWindowManager::OnWindowShowTypeChanged( ash::wm::WindowState* window_state, ash::wm::WindowShowType old_type) { if (!window_state->IsMinimized()) return; aura::Window* window = window_state->window(); // If the window was shown on a different users desktop: move it back. const std::string& owner = GetWindowOwner(window); if (!IsWindowOnDesktopOfUser(window, owner)) ShowWindowForUser(window, owner); } void MultiUserWindowManager::Observe( int type, const content::NotificationSource& source, const content::NotificationDetails& details) { if (type == chrome::NOTIFICATION_BROWSER_WINDOW_READY) AddBrowserWindow(content::Source<Browser>(source).ptr()); } // static MultiUserWindowManager* MultiUserWindowManager::CreateInstanceInternal( std::string active_user_id) { DCHECK(!g_instance); g_instance = new MultiUserWindowManager(active_user_id); return g_instance; } MultiUserWindowManager::MultiUserWindowManager( const std::string& current_user_id) : current_user_id_(current_user_id), suppress_visibility_changes_(false) { // Add a session state observer to be able to monitor session changes. if (ash::Shell::HasInstance()) ash::Shell::GetInstance()->session_state_delegate()-> AddSessionStateObserver(this); // The BrowserListObserver would have been better to use then the old // notification system, but that observer fires before the window got created. registrar_.Add(this, chrome::NOTIFICATION_BROWSER_WINDOW_READY, content::NotificationService::AllSources()); // Add an app window observer & all already running apps. AddUser(current_user_id); } MultiUserWindowManager::~MultiUserWindowManager() { // Remove all window observers. WindowToEntryMap::iterator window_observer = window_to_entry_.begin(); while (window_observer != window_to_entry_.end()) { OnWindowDestroyed(window_observer->first); window_observer = window_to_entry_.begin(); } // Remove all app observers. UserIDToShellWindowObserver::iterator app_observer_iterator = user_id_to_app_observer_.begin(); while (app_observer_iterator != user_id_to_app_observer_.end()) { Profile* profile = GetProfileFromUserID(app_observer_iterator->first); DCHECK(profile); apps::ShellWindowRegistry::Get(profile)->RemoveObserver( app_observer_iterator->second); delete app_observer_iterator->second; user_id_to_app_observer_.erase(app_observer_iterator); app_observer_iterator = user_id_to_app_observer_.begin(); } if (ash::Shell::HasInstance()) ash::Shell::GetInstance()->session_state_delegate()-> RemoveSessionStateObserver(this); } void MultiUserWindowManager::AddUser(const std::string& user_id) { if (user_id_to_app_observer_.find(user_id) != user_id_to_app_observer_.end()) return; // Add an observer for all shell window changes. Profile* profile = GetProfileFromUserID(user_id); // In case of unit tests we might have no profile. if (!profile) return; user_id_to_app_observer_[user_id] = new AppObserver(user_id); apps::ShellWindowRegistry::Get(profile)->AddObserver( user_id_to_app_observer_[user_id]); // Account all existing application windows of this user accordingly. const apps::ShellWindowRegistry::ShellWindowList& shell_windows = apps::ShellWindowRegistry::Get(profile)->shell_windows(); apps::ShellWindowRegistry::ShellWindowList::const_iterator it = shell_windows.begin(); for (; it != shell_windows.end(); ++it) user_id_to_app_observer_[user_id]->OnShellWindowAdded(*it); // Account all existing browser windows of this user accordingly. BrowserList* browser_list = BrowserList::GetInstance(HOST_DESKTOP_TYPE_ASH); BrowserList::const_iterator browser_it = browser_list->begin(); for (; browser_it != browser_list->end(); ++browser_it) { if ((*browser_it)->profile()->GetOriginalProfile() == profile) AddBrowserWindow(*browser_it); } } void MultiUserWindowManager::AddBrowserWindow(Browser* browser) { if (browser->window() && !browser->window()->GetNativeWindow()) return; SetWindowOwner(browser->window()->GetNativeWindow(), GetUserIDFromProfile(browser->profile())); } void MultiUserWindowManager::SetWindowVisibility( aura::Window* window, bool visible) { if (window->IsVisible() == visible) return; // To avoid that these commands are recorded as any other commands, we are // suppressing any window entry changes while this is going on. base::AutoReset<bool> suppressor(&suppress_visibility_changes_, true); if (visible) window->Show(); else window->Hide(); } Profile* MultiUserWindowManager::GetProfileFromUserID( const std::string& user_id) { // This can only happen for unit tests. If it happens we return NULL. if (!g_browser_process || !g_browser_process->profile_manager()) return NULL; std::vector<Profile*> profiles = g_browser_process->profile_manager()->GetLoadedProfiles(); std::vector<Profile*>::iterator profile_iterator = profiles.begin(); for (; profile_iterator != profiles.end(); ++profile_iterator) { if (GetUserIDFromProfile(*profile_iterator) == user_id) return *profile_iterator; } return NULL; } } // namespace chrome