| // 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/mru_window_tracker.h" |
| #include "ash/wm/window_positioner.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/chromeos/login/user_manager.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/activation_client.h" |
| #include "ui/aura/client/aura_constants.h" |
| #include "ui/aura/root_window.h" |
| #include "ui/aura/window.h" |
| #include "ui/base/ui_base_types.h" |
| |
| namespace { |
| chrome::MultiUserWindowManager* g_instance = NULL; |
| } // namespace |
| |
| namespace chrome { |
| |
| // Caching the current multi profile mode since the detection which mode is |
| // used is quite expensive. |
| chrome::MultiUserWindowManager::MultiProfileMode |
| chrome::MultiUserWindowManager::multi_user_mode_ = |
| chrome::MultiUserWindowManager::MULTI_PROFILE_MODE_UNINITIALIZED; |
| |
| // A class to disable updates to the MRU window list and the auto positioning |
| // logic while the user gets switched. |
| class UserChangeActionDisabler { |
| public: |
| UserChangeActionDisabler() { |
| ash::WindowPositioner::DisableAutoPositioning(true); |
| ash::Shell::GetInstance()->mru_window_tracker()->SetIgnoreActivations(true); |
| } |
| |
| ~UserChangeActionDisabler() { |
| ash::WindowPositioner::DisableAutoPositioning(false); |
| ash::Shell::GetInstance()->mru_window_tracker()->SetIgnoreActivations( |
| false); |
| } |
| private: |
| |
| DISALLOW_COPY_AND_ASSIGN(UserChangeActionDisabler); |
| }; |
| |
| // 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() { |
| return g_instance; |
| } |
| |
| MultiUserWindowManager* MultiUserWindowManager::CreateInstance() { |
| if (!g_instance && |
| ash::Shell::GetInstance()->delegate()->IsMultiProfilesEnabled() && |
| !ash::switches::UseFullMultiProfileMode()) { |
| g_instance = CreateInstanceInternal( |
| ash::Shell::GetInstance()->session_state_delegate()->GetUserID(0)); |
| multi_user_mode_ = MULTI_PROFILE_MODE_SEPARATED; |
| } else if (ash::Shell::GetInstance()->delegate()->IsMultiProfilesEnabled()) { |
| multi_user_mode_ = MULTI_PROFILE_MODE_MIXED; |
| } else { |
| multi_user_mode_ = MULTI_PROFILE_MODE_OFF; |
| } |
| return g_instance; |
| } |
| |
| // static |
| MultiUserWindowManager::MultiProfileMode |
| MultiUserWindowManager::GetMultiProfileMode() { |
| return multi_user_mode_; |
| } |
| |
| // static |
| void MultiUserWindowManager::DeleteInstance() { |
| if (g_instance) |
| delete g_instance; |
| g_instance = NULL; |
| multi_user_mode_ = MULTI_PROFILE_MODE_UNINITIALIZED; |
| } |
| |
| // static |
| std::string MultiUserWindowManager::GetUserIDFromProfile(Profile* profile) { |
| return GetUserIDFromEmail(profile->GetOriginalProfile()->GetProfileName()); |
| } |
| |
| // static |
| std::string MultiUserWindowManager::GetUserIDFromEmail( |
| const std::string& email) { |
| return gaia::CanonicalizeEmail(gaia::SanitizeEmail(email)); |
| } |
| |
| // static |
| 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*>::const_iterator profile_iterator = profiles.begin(); |
| for (; profile_iterator != profiles.end(); ++profile_iterator) { |
| if (GetUserIDFromProfile(*profile_iterator) == user_id) |
| return *profile_iterator; |
| } |
| return NULL; |
| } |
| |
| // static |
| bool MultiUserWindowManager::ProfileIsFromActiveUser(Profile* profile) { |
| return MultiUserWindowManager::GetUserIDFromProfile(profile) == |
| chromeos::UserManager::Get()->GetActiveUser()->email(); |
| } |
| |
| 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; |
| // When a user has changed, we need to show only the windows which are visible |
| // to that user. Additionally we need to restore the activation state of the |
| // windows. Changing the activation state however triggers the window position |
| // manager to auto position the windows. To avoid that we disable it |
| // temporarily. |
| |
| // Disable the window position manager and the MRU window tracker temporarily. |
| scoped_ptr<UserChangeActionDisabler> disabler(new UserChangeActionDisabler); |
| |
| for (WindowToEntryMap::iterator it = window_to_entry_.begin(); |
| 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); |
| } |
| |
| // Retrieve the list of visible windows in their order. Use the list to |
| // traverse the windows and activate them, thus restoring the previous state. |
| // TODO(skuhne): Unfortunately the MruWindowTracker does not let us get the |
| // full list (visible and invisible) and after our visibility changes the |
| // actual order of windows can be changed. A workaround would be to implement |
| // our own aura::client::ActivationChangeObserver and track the activations |
| // from our known windows. But that should be part of another CL. |
| ash::MruWindowTracker* tracker = |
| ash::Shell::GetInstance()->mru_window_tracker(); |
| ash::MruWindowTracker::WindowList mru_list = tracker->BuildWindowList(true); |
| for (ash::MruWindowTracker::WindowList::iterator mru_iterator = |
| mru_list.begin(); |
| mru_iterator != mru_list.end(); mru_iterator++) { |
| aura::Window* window = *mru_iterator; |
| ash::wm::WindowState* window_state = ash::wm::GetWindowState(window); |
| if (IsWindowOnDesktopOfUser(window, user_id) && |
| !window_state->IsMinimized()) { |
| aura::client::ActivationClient* client = |
| aura::client::GetActivationClient(window->GetRootWindow()); |
| // Several unit tests come here without an activation client. |
| if (client) |
| client->ActivateWindow(window); |
| } |
| } |
| } |
| |
| 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. |
| Profile* profile = GetProfileFromUserID(current_user_id); |
| if (profile) |
| AddUser(profile); |
| } |
| |
| 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(Profile* profile) { |
| const std::string& user_id = GetUserIDFromProfile(profile); |
| if (user_id_to_app_observer_.find(user_id) != user_id_to_app_observer_.end()) |
| 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) { |
| // A unit test (e.g. CrashRestoreComplexTest.RestoreSessionForThreeUsers) can |
| // come here with no valid window. |
| 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(); |
| } |
| |
| } // namespace chrome |