1 // Copyright 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.
5 #include "chrome/browser/ui/ash/multi_user_window_manager.h"
7 #include "apps/shell_window.h"
8 #include "apps/shell_window_registry.h"
9 #include "ash/ash_switches.h"
10 #include "ash/multi_profile_uma.h"
11 #include "ash/session_state_delegate.h"
12 #include "ash/shell.h"
13 #include "ash/shell_delegate.h"
14 #include "ash/wm/mru_window_tracker.h"
15 #include "ash/wm/window_positioner.h"
16 #include "ash/wm/window_state.h"
17 #include "base/auto_reset.h"
18 #include "base/message_loop/message_loop.h"
19 #include "base/strings/string_util.h"
20 #include "chrome/browser/browser_process.h"
21 #include "chrome/browser/chrome_notification_types.h"
22 #include "chrome/browser/chromeos/login/user_manager.h"
23 #include "chrome/browser/profiles/profile.h"
24 #include "chrome/browser/profiles/profile_manager.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 "content/public/browser/notification_service.h"
29 #include "google_apis/gaia/gaia_auth_util.h"
30 #include "ui/aura/client/activation_client.h"
31 #include "ui/aura/client/aura_constants.h"
32 #include "ui/aura/root_window.h"
33 #include "ui/aura/window.h"
34 #include "ui/base/ui_base_types.h"
35 #include "ui/events/event.h"
38 chrome::MultiUserWindowManager* g_instance = NULL;
41 // Checks if a given event is a user event.
42 bool IsUserEvent(ui::Event* e) {
44 ui::EventType type = e->type();
45 if (type != ui::ET_CANCEL_MODE &&
46 type != ui::ET_UMA_DATA &&
47 type != ui::ET_UNKNOWN)
53 // Test if we are currently processing a user event which might lead to a
54 // browser / app creation.
55 bool IsProcessingUserEvent() {
56 // When there is a nested message loop (e.g. active menu or drag and drop
57 // operation) - we are in a nested loop and can ignore this.
58 // Note: Unit tests might not have a message loop.
59 base::MessageLoop* message_loop = base::MessageLoop::current();
60 if (message_loop && message_loop->is_running() && message_loop->IsNested())
63 // TODO(skuhne): "Open link in new window" will come here after the menu got
64 // closed, executing the command from the nested menu loop. However at that
65 // time there is no active event processed. A solution for that need to be
66 // found past M-32. A global event handler filter (pre and post) might fix
67 // that problem in conjunction with a depth counter - but - for the menu
68 // execution we come here after the loop was finished (so it's not nested
69 // anymore) and the root window should therefore still have the event which
70 // lead to the menu invocation, but it is not. By fixing that problem this
71 // would "magically work".
72 ash::Shell::RootWindowList root_window_list = ash::Shell::GetAllRootWindows();
73 for (ash::Shell::RootWindowList::iterator it = root_window_list.begin();
74 it != root_window_list.end();
76 if (IsUserEvent((*it)->current_event()))
86 // Caching the current multi profile mode since the detection which mode is
87 // used is quite expensive.
88 chrome::MultiUserWindowManager::MultiProfileMode
89 chrome::MultiUserWindowManager::multi_user_mode_ =
90 chrome::MultiUserWindowManager::MULTI_PROFILE_MODE_UNINITIALIZED;
92 // A class to disable updates to the MRU window list and the auto positioning
93 // logic while the user gets switched.
94 class UserChangeActionDisabler {
96 UserChangeActionDisabler() {
97 ash::WindowPositioner::DisableAutoPositioning(true);
98 ash::Shell::GetInstance()->mru_window_tracker()->SetIgnoreActivations(true);
101 ~UserChangeActionDisabler() {
102 ash::WindowPositioner::DisableAutoPositioning(false);
103 ash::Shell::GetInstance()->mru_window_tracker()->SetIgnoreActivations(
108 DISALLOW_COPY_AND_ASSIGN(UserChangeActionDisabler);
111 // This class keeps track of all applications which were started for a user.
112 // When an app gets created, the window will be tagged for that user. Note
113 // that the destruction does not need to be tracked here since the universal
114 // window observer will take care of that.
115 class AppObserver : public apps::ShellWindowRegistry::Observer {
117 explicit AppObserver(const std::string& user_id) : user_id_(user_id) {}
118 virtual ~AppObserver() {}
120 // ShellWindowRegistry::Observer overrides:
121 virtual void OnShellWindowAdded(apps::ShellWindow* shell_window) OVERRIDE {
122 aura::Window* window = shell_window->GetNativeWindow();
124 chrome::MultiUserWindowManager::GetInstance()->SetWindowOwner(window,
127 virtual void OnShellWindowIconChanged(apps::ShellWindow* shell_window)
129 virtual void OnShellWindowRemoved(apps::ShellWindow* shell_window)
133 std::string user_id_;
135 DISALLOW_COPY_AND_ASSIGN(AppObserver);
139 MultiUserWindowManager* MultiUserWindowManager::GetInstance() {
143 MultiUserWindowManager* MultiUserWindowManager::CreateInstance() {
144 ash::MultiProfileUMA::SessionMode mode =
145 ash::MultiProfileUMA::SESSION_SINGLE_USER_MODE;
147 ash::Shell::GetInstance()->delegate()->IsMultiProfilesEnabled() &&
148 !ash::switches::UseFullMultiProfileMode()) {
149 g_instance = CreateInstanceInternal(
150 ash::Shell::GetInstance()->session_state_delegate()->GetUserID(0));
151 multi_user_mode_ = MULTI_PROFILE_MODE_SEPARATED;
152 mode = ash::MultiProfileUMA::SESSION_SEPARATE_DESKTOP_MODE;
153 } else if (ash::Shell::GetInstance()->delegate()->IsMultiProfilesEnabled()) {
154 multi_user_mode_ = MULTI_PROFILE_MODE_MIXED;
155 mode = ash::MultiProfileUMA::SESSION_SIDE_BY_SIDE_MODE;
157 multi_user_mode_ = MULTI_PROFILE_MODE_OFF;
159 ash::MultiProfileUMA::RecordSessionMode(mode);
164 MultiUserWindowManager::MultiProfileMode
165 MultiUserWindowManager::GetMultiProfileMode() {
166 return multi_user_mode_;
170 void MultiUserWindowManager::DeleteInstance() {
174 multi_user_mode_ = MULTI_PROFILE_MODE_UNINITIALIZED;
178 std::string MultiUserWindowManager::GetUserIDFromProfile(Profile* profile) {
179 return GetUserIDFromEmail(profile->GetOriginalProfile()->GetProfileName());
183 std::string MultiUserWindowManager::GetUserIDFromEmail(
184 const std::string& email) {
185 return gaia::CanonicalizeEmail(gaia::SanitizeEmail(email));
189 Profile* MultiUserWindowManager::GetProfileFromUserID(
190 const std::string& user_id) {
191 // This can only happen for unit tests. If it happens we return NULL.
192 if (!g_browser_process || !g_browser_process->profile_manager())
195 std::vector<Profile*> profiles =
196 g_browser_process->profile_manager()->GetLoadedProfiles();
198 std::vector<Profile*>::const_iterator profile_iterator = profiles.begin();
199 for (; profile_iterator != profiles.end(); ++profile_iterator) {
200 if (GetUserIDFromProfile(*profile_iterator) == user_id)
201 return *profile_iterator;
207 bool MultiUserWindowManager::ProfileIsFromActiveUser(Profile* profile) {
208 return MultiUserWindowManager::GetUserIDFromProfile(profile) ==
209 chromeos::UserManager::Get()->GetActiveUser()->email();
212 void MultiUserWindowManager::SetWindowOwner(aura::Window* window,
213 const std::string& user_id) {
214 // Make sure the window is valid and there was no owner yet.
216 DCHECK(!user_id.empty());
217 if (GetWindowOwner(window) == user_id)
219 DCHECK(GetWindowOwner(window).empty());
220 window_to_entry_[window] = new WindowEntry(user_id);
222 // Remember the initial visibility of the window.
223 window_to_entry_[window]->set_show(window->IsVisible());
225 // Set the window and the state observer.
226 window->AddObserver(this);
227 ash::wm::GetWindowState(window)->AddObserver(this);
229 // Check if this window was created due to a user interaction. If it was,
230 // transfer it to the current user.
231 if (IsProcessingUserEvent())
232 window_to_entry_[window]->set_show_for_user(current_user_id_);
234 // Add all transient children to our set of windows. Note that the function
235 // will add the children but not the owner to the transient children map.
236 AddTransientOwnerRecursive(window, window);
238 if (!IsWindowOnDesktopOfUser(window, current_user_id_))
239 SetWindowVisibility(window, false);
242 const std::string& MultiUserWindowManager::GetWindowOwner(
243 aura::Window* window) {
244 WindowToEntryMap::iterator it = window_to_entry_.find(window);
245 return it != window_to_entry_.end() ? it->second->owner() : EmptyString();
248 void MultiUserWindowManager::ShowWindowForUser(aura::Window* window,
249 const std::string& user_id) {
250 // If there is either no owner, or the owner is the current user, no action
252 const std::string& owner = GetWindowOwner(window);
254 (owner == user_id && IsWindowOnDesktopOfUser(window, user_id)))
257 // Check that we are not trying to transfer ownership of a minimized window.
258 if (user_id != owner && ash::wm::GetWindowState(window)->IsMinimized())
261 ash::MultiProfileUMA::RecordTeleportAction(
262 ash::MultiProfileUMA::TELEPORT_WINDOW_RETURN_BY_MINIMIZE);
264 WindowToEntryMap::iterator it = window_to_entry_.find(window);
265 it->second->set_show_for_user(user_id);
267 // Show the window if the added user is the current one.
268 if (user_id == current_user_id_) {
269 // Only show the window if it should be shown according to its state.
270 if (it->second->show())
271 SetWindowVisibility(window, true);
273 SetWindowVisibility(window, false);
277 bool MultiUserWindowManager::AreWindowsSharedAmongUsers() {
278 WindowToEntryMap::iterator it = window_to_entry_.begin();
279 for (; it != window_to_entry_.end(); ++it) {
280 if (it->second->owner() != it->second->show_for_user())
286 bool MultiUserWindowManager::IsWindowOnDesktopOfUser(
287 aura::Window* window,
288 const std::string& user_id) {
289 const std::string& presenting_user = GetUserPresentingWindow(window);
290 return presenting_user.empty() || presenting_user == user_id;
293 const std::string& MultiUserWindowManager::GetUserPresentingWindow(
294 aura::Window* window) {
295 WindowToEntryMap::iterator it = window_to_entry_.find(window);
296 // If the window is not owned by anyone it is shown on all desktops and we
297 // return the empty string.
298 if (it == window_to_entry_.end())
299 return EmptyString();
300 // Otherwise we ask the object for its desktop.
301 return it->second->show_for_user();
304 void MultiUserWindowManager::ActiveUserChanged(const std::string& user_id) {
305 DCHECK(user_id != current_user_id_);
306 std::string old_user = current_user_id_;
307 current_user_id_ = user_id;
308 // Disable the window position manager and the MRU window tracker temporarily.
309 scoped_ptr<UserChangeActionDisabler> disabler(new UserChangeActionDisabler);
311 // We need to show/hide the windows in the same order as they were created in
312 // their parent window(s) to keep the layer / window hierarchy in sync. To
313 // achieve that we first collect all parent windows and then enumerate all
314 // windows in those parent windows and show or hide them accordingly.
316 // Create a list of all parent windows we have to check and their parents.
317 std::set<aura::Window*> parent_list;
318 for (WindowToEntryMap::iterator it = window_to_entry_.begin();
319 it != window_to_entry_.end(); ++it) {
320 aura::Window* parent = it->first->parent();
321 if (parent_list.find(parent) == parent_list.end())
322 parent_list.insert(parent);
325 // Traverse the found parent windows to handle their child windows in order of
327 for (std::set<aura::Window*>::iterator it_parents = parent_list.begin();
328 it_parents != parent_list.end(); ++it_parents) {
329 const aura::Window::Windows window_list = (*it_parents)->children();
330 for (aura::Window::Windows::const_iterator it_window = window_list.begin();
331 it_window != window_list.end(); ++it_window) {
332 aura::Window* window = *it_window;
333 WindowToEntryMap::iterator it_map = window_to_entry_.find(window);
334 if (it_map != window_to_entry_.end()) {
335 bool should_be_visible = it_map->second->show_for_user() == user_id &&
336 it_map->second->show();
337 bool is_visible = window->IsVisible();
338 if (should_be_visible != is_visible)
339 SetWindowVisibility(window, should_be_visible);
344 // Finally we need to restore the previously active window.
345 ash::MruWindowTracker::WindowList mru_list =
346 ash::Shell::GetInstance()->mru_window_tracker()->BuildMruWindowList();
347 if (mru_list.size()) {
348 aura::Window* window = mru_list[0];
349 ash::wm::WindowState* window_state = ash::wm::GetWindowState(window);
350 if (IsWindowOnDesktopOfUser(window, user_id) &&
351 !window_state->IsMinimized()) {
352 aura::client::ActivationClient* client =
353 aura::client::GetActivationClient(window->GetRootWindow());
354 // Several unit tests come here without an activation client.
356 client->ActivateWindow(window);
361 void MultiUserWindowManager::OnWindowDestroyed(aura::Window* window) {
362 if (GetWindowOwner(window).empty()) {
363 // This must be a window in the transient chain - remove it and its
364 // children from the owner.
365 RemoveTransientOwnerRecursive(window);
368 // Remove the state and the window observer.
369 ash::wm::GetWindowState(window)->RemoveObserver(this);
370 window->RemoveObserver(this);
371 // Remove the window from the owners list.
372 delete window_to_entry_[window];
373 window_to_entry_.erase(window);
376 void MultiUserWindowManager::OnWindowVisibilityChanging(
377 aura::Window* window, bool visible) {
378 // This command gets called first and immediately when show or hide gets
379 // called. We remember here the desired state for restoration IF we were
380 // not ourselves issuing the call.
381 // Note also that using the OnWindowVisibilityChanged callback cannot be
383 if (suppress_visibility_changes_)
386 WindowToEntryMap::iterator it = window_to_entry_.find(window);
387 // If the window is not owned by anyone it is shown on all desktops.
388 if (it != window_to_entry_.end()) {
389 // Remember what was asked for so that we can restore this when the user's
390 // desktop gets restored.
391 it->second->set_show(visible);
393 TransientWindowToVisibility::iterator it =
394 transient_window_to_visibility_.find(window);
395 if (it != transient_window_to_visibility_.end())
396 it->second = visible;
400 void MultiUserWindowManager::OnWindowVisibilityChanged(
401 aura::Window* window, bool visible) {
402 if (suppress_visibility_changes_)
405 // Don't allow to make the window visible if it shouldn't be.
406 if (visible && !IsWindowOnDesktopOfUser(window, current_user_id_)) {
407 SetWindowVisibility(window, false);
410 aura::Window* owned_parent = GetOwningWindowInTransientChain(window);
411 if (owned_parent && owned_parent != window && visible &&
412 !IsWindowOnDesktopOfUser(owned_parent, current_user_id_))
413 SetWindowVisibility(window, false);
416 void MultiUserWindowManager::OnAddTransientChild(
417 aura::Window* window,
418 aura::Window* transient_window) {
419 if (!GetWindowOwner(window).empty()) {
420 AddTransientOwnerRecursive(transient_window, window);
423 aura::Window* owned_parent =
424 GetOwningWindowInTransientChain(transient_window);
428 AddTransientOwnerRecursive(transient_window, owned_parent);
431 void MultiUserWindowManager::OnRemoveTransientChild(
432 aura::Window* window,
433 aura::Window* transient_window) {
434 // Remove the transient child if the window itself is owned, or one of the
435 // windows in its transient parents chain.
436 if (!GetWindowOwner(window).empty() ||
437 GetOwningWindowInTransientChain(window))
438 RemoveTransientOwnerRecursive(transient_window);
441 void MultiUserWindowManager::OnWindowShowTypeChanged(
442 ash::wm::WindowState* window_state,
443 ash::wm::WindowShowType old_type) {
444 if (!window_state->IsMinimized())
447 aura::Window* window = window_state->window();
448 // If the window was shown on a different users desktop: move it back.
449 const std::string& owner = GetWindowOwner(window);
450 if (!IsWindowOnDesktopOfUser(window, owner))
451 ShowWindowForUser(window, owner);
454 void MultiUserWindowManager::Observe(
456 const content::NotificationSource& source,
457 const content::NotificationDetails& details) {
458 if (type == chrome::NOTIFICATION_BROWSER_WINDOW_READY)
459 AddBrowserWindow(content::Source<Browser>(source).ptr());
463 MultiUserWindowManager* MultiUserWindowManager::CreateInstanceInternal(
464 std::string active_user_id) {
466 g_instance = new MultiUserWindowManager(active_user_id);
470 MultiUserWindowManager::MultiUserWindowManager(
471 const std::string& current_user_id)
472 : current_user_id_(current_user_id),
473 suppress_visibility_changes_(false) {
474 // Add a session state observer to be able to monitor session changes.
475 if (ash::Shell::HasInstance())
476 ash::Shell::GetInstance()->session_state_delegate()->
477 AddSessionStateObserver(this);
479 // The BrowserListObserver would have been better to use then the old
480 // notification system, but that observer fires before the window got created.
481 registrar_.Add(this, chrome::NOTIFICATION_BROWSER_WINDOW_READY,
482 content::NotificationService::AllSources());
484 // Add an app window observer & all already running apps.
485 Profile* profile = GetProfileFromUserID(current_user_id);
490 MultiUserWindowManager::~MultiUserWindowManager() {
491 // Remove all window observers.
492 WindowToEntryMap::iterator window_observer = window_to_entry_.begin();
493 while (window_observer != window_to_entry_.end()) {
494 OnWindowDestroyed(window_observer->first);
495 window_observer = window_to_entry_.begin();
498 // Remove all app observers.
499 UserIDToShellWindowObserver::iterator app_observer_iterator =
500 user_id_to_app_observer_.begin();
501 while (app_observer_iterator != user_id_to_app_observer_.end()) {
502 Profile* profile = GetProfileFromUserID(app_observer_iterator->first);
504 apps::ShellWindowRegistry::Get(profile)->RemoveObserver(
505 app_observer_iterator->second);
506 delete app_observer_iterator->second;
507 user_id_to_app_observer_.erase(app_observer_iterator);
508 app_observer_iterator = user_id_to_app_observer_.begin();
511 if (ash::Shell::HasInstance())
512 ash::Shell::GetInstance()->session_state_delegate()->
513 RemoveSessionStateObserver(this);
516 void MultiUserWindowManager::AddUser(Profile* profile) {
517 const std::string& user_id = GetUserIDFromProfile(profile);
518 if (user_id_to_app_observer_.find(user_id) != user_id_to_app_observer_.end())
521 user_id_to_app_observer_[user_id] = new AppObserver(user_id);
522 apps::ShellWindowRegistry::Get(profile)->AddObserver(
523 user_id_to_app_observer_[user_id]);
525 // Account all existing application windows of this user accordingly.
526 const apps::ShellWindowRegistry::ShellWindowList& shell_windows =
527 apps::ShellWindowRegistry::Get(profile)->shell_windows();
528 apps::ShellWindowRegistry::ShellWindowList::const_iterator it =
529 shell_windows.begin();
530 for (; it != shell_windows.end(); ++it)
531 user_id_to_app_observer_[user_id]->OnShellWindowAdded(*it);
533 // Account all existing browser windows of this user accordingly.
534 BrowserList* browser_list = BrowserList::GetInstance(HOST_DESKTOP_TYPE_ASH);
535 BrowserList::const_iterator browser_it = browser_list->begin();
536 for (; browser_it != browser_list->end(); ++browser_it) {
537 if ((*browser_it)->profile()->GetOriginalProfile() == profile)
538 AddBrowserWindow(*browser_it);
542 void MultiUserWindowManager::AddBrowserWindow(Browser* browser) {
543 // A unit test (e.g. CrashRestoreComplexTest.RestoreSessionForThreeUsers) can
544 // come here with no valid window.
545 if (!browser->window() || !browser->window()->GetNativeWindow())
547 SetWindowOwner(browser->window()->GetNativeWindow(),
548 GetUserIDFromProfile(browser->profile()));
551 void MultiUserWindowManager::SetWindowVisibility(
552 aura::Window* window, bool visible) {
553 if (window->IsVisible() == visible)
556 // To avoid that these commands are recorded as any other commands, we are
557 // suppressing any window entry changes while this is going on.
558 base::AutoReset<bool> suppressor(&suppress_visibility_changes_, true);
561 ShowWithTransientChildrenRecursive(window);
563 if (window->HasFocus())
569 void MultiUserWindowManager::ShowWithTransientChildrenRecursive(
570 aura::Window* window) {
571 aura::Window::Windows::const_iterator it =
572 window->transient_children().begin();
573 for (; it != window->transient_children().end(); ++it)
574 ShowWithTransientChildrenRecursive(*it);
576 // We show all children which were not explicitly hidden.
577 TransientWindowToVisibility::iterator it2 =
578 transient_window_to_visibility_.find(window);
579 if (it2 == transient_window_to_visibility_.end() || it2->second)
583 aura::Window* MultiUserWindowManager::GetOwningWindowInTransientChain(
584 aura::Window* window) {
585 if (!GetWindowOwner(window).empty())
587 aura::Window* parent = window->transient_parent();
589 if (!GetWindowOwner(parent).empty())
591 parent = parent->transient_parent();
596 void MultiUserWindowManager::AddTransientOwnerRecursive(
597 aura::Window* window,
598 aura::Window* owned_parent) {
599 // First add all child windows.
600 aura::Window::Windows::const_iterator it =
601 window->transient_children().begin();
602 for (; it != window->transient_children().end(); ++it)
603 AddTransientOwnerRecursive(*it, owned_parent);
605 // If this window is the owned window, we do not have to handle it again.
606 if (window == owned_parent)
609 // Remember the current visibility.
610 DCHECK(transient_window_to_visibility_.find(window) ==
611 transient_window_to_visibility_.end());
612 transient_window_to_visibility_[window] = window->IsVisible();
614 // Add a window observer to make sure that we catch status changes.
615 window->AddObserver(this);
617 // Hide the window if it should not be shown. Note that this hide operation
618 // will hide recursively this and all children - but we have already collected
619 // their initial view state.
620 if (!IsWindowOnDesktopOfUser(owned_parent, current_user_id_))
621 SetWindowVisibility(window, false);
624 void MultiUserWindowManager::RemoveTransientOwnerRecursive(
625 aura::Window* window) {
626 // First remove all child windows.
627 aura::Window::Windows::const_iterator it =
628 window->transient_children().begin();
629 for (; it != window->transient_children().end(); ++it)
630 RemoveTransientOwnerRecursive(*it);
632 // Find from transient window storage the visibility for the given window,
633 // set the visibility accordingly and delete the window from the map.
634 TransientWindowToVisibility::iterator visibility_item =
635 transient_window_to_visibility_.find(window);
636 DCHECK(visibility_item != transient_window_to_visibility_.end());
638 // Remove the window observer.
639 window->RemoveObserver(this);
641 bool unowned_view_state = visibility_item->second;
642 transient_window_to_visibility_.erase(visibility_item);
643 if (unowned_view_state && !window->IsVisible()) {
644 // To prevent these commands from being recorded as any other commands, we
645 // are suppressing any window entry changes while this is going on.
646 // Instead of calling SetWindowVisible, only show gets called here since all
647 // dependents have been shown previously already.
648 base::AutoReset<bool> suppressor(&suppress_visibility_changes_, true);
653 } // namespace chrome