Home | History | Annotate | Download | only in multi_user
      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.
      4 
      5 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_chromeos.h"
      6 
      7 #include "ash/ash_switches.h"
      8 #include "ash/multi_profile_uma.h"
      9 #include "ash/root_window_controller.h"
     10 #include "ash/session/session_state_delegate.h"
     11 #include "ash/shelf/shelf.h"
     12 #include "ash/shell.h"
     13 #include "ash/shell_delegate.h"
     14 #include "ash/shell_window_ids.h"
     15 #include "ash/system/tray/system_tray_notifier.h"
     16 #include "ash/wm/maximize_mode/maximize_mode_controller.h"
     17 #include "ash/wm/window_state.h"
     18 #include "base/auto_reset.h"
     19 #include "base/message_loop/message_loop.h"
     20 #include "base/strings/string_util.h"
     21 #include "chrome/browser/browser_process.h"
     22 #include "chrome/browser/chrome_notification_types.h"
     23 #include "chrome/browser/profiles/profile.h"
     24 #include "chrome/browser/profiles/profile_manager.h"
     25 #include "chrome/browser/ui/ash/multi_user/multi_user_notification_blocker_chromeos.h"
     26 #include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
     27 #include "chrome/browser/ui/ash/multi_user/user_switch_animator_chromeos.h"
     28 #include "chrome/browser/ui/browser.h"
     29 #include "chrome/browser/ui/browser_finder.h"
     30 #include "chrome/browser/ui/browser_list.h"
     31 #include "chrome/browser/ui/browser_window.h"
     32 #include "content/public/browser/notification_service.h"
     33 #include "extensions/browser/app_window/app_window.h"
     34 #include "extensions/browser/app_window/app_window_registry.h"
     35 #include "google_apis/gaia/gaia_auth_util.h"
     36 #include "ui/aura/client/aura_constants.h"
     37 #include "ui/aura/window.h"
     38 #include "ui/aura/window_event_dispatcher.h"
     39 #include "ui/base/ui_base_types.h"
     40 #include "ui/events/event.h"
     41 #include "ui/message_center/message_center.h"
     42 #include "ui/wm/core/transient_window_manager.h"
     43 #include "ui/wm/core/window_animations.h"
     44 #include "ui/wm/core/window_util.h"
     45 
     46 namespace {
     47 
     48 // The animation time in milliseconds for a single window which is fading
     49 // in / out.
     50 const int kAnimationTimeMS = 100;
     51 
     52 // The animation time in milliseconds for the fade in and / or out when
     53 // switching users.
     54 const int kUserFadeTimeMS = 110;
     55 
     56 // The animation time in ms for a window which get teleported to another screen.
     57 const int kTeleportAnimationTimeMS = 300;
     58 
     59 // Checks if a given event is a user event.
     60 bool IsUserEvent(const ui::Event* e) {
     61   if (e) {
     62     ui::EventType type = e->type();
     63     if (type != ui::ET_CANCEL_MODE &&
     64         type != ui::ET_UMA_DATA &&
     65         type != ui::ET_UNKNOWN)
     66       return true;
     67   }
     68   return false;
     69 }
     70 
     71 // Test if we are currently processing a user event which might lead to a
     72 // browser / app creation.
     73 bool IsProcessingUserEvent() {
     74   // When there is a nested message loop (e.g. active menu or drag and drop
     75   // operation) - we are in a nested loop and can ignore this.
     76   // Note: Unit tests might not have a message loop.
     77   base::MessageLoop* message_loop = base::MessageLoop::current();
     78   if (message_loop && message_loop->is_running() && message_loop->IsNested())
     79     return false;
     80 
     81   // TODO(skuhne): "Open link in new window" will come here after the menu got
     82   // closed, executing the command from the nested menu loop. However at that
     83   // time there is no active event processed. A solution for that need to be
     84   // found past M-32. A global event handler filter (pre and post) might fix
     85   // that problem in conjunction with a depth counter - but - for the menu
     86   // execution we come here after the loop was finished (so it's not nested
     87   // anymore) and the root window should therefore still have the event which
     88   // lead to the menu invocation, but it is not. By fixing that problem this
     89   // would "magically work".
     90   aura::Window::Windows root_window_list = ash::Shell::GetAllRootWindows();
     91   for (aura::Window::Windows::iterator it = root_window_list.begin();
     92        it != root_window_list.end();
     93        ++it) {
     94     if (IsUserEvent((*it)->GetHost()->dispatcher()->current_event()))
     95       return true;
     96   }
     97   return false;
     98 }
     99 
    100 // Records the type of window which was transferred to another desktop.
    101 void RecordUMAForTransferredWindowType(aura::Window* window) {
    102   // We need to figure out what kind of window this is to record the transfer.
    103   Browser* browser = chrome::FindBrowserWithWindow(window);
    104   ash::MultiProfileUMA::TeleportWindowType window_type =
    105       ash::MultiProfileUMA::TELEPORT_WINDOW_UNKNOWN;
    106   if (browser) {
    107     if (browser->profile()->IsOffTheRecord()) {
    108       window_type = ash::MultiProfileUMA::TELEPORT_WINDOW_INCOGNITO_BROWSER;
    109     } else if (browser->is_app()) {
    110       window_type = ash::MultiProfileUMA::TELEPORT_WINDOW_V1_APP;
    111     } else if (browser->is_type_popup()) {
    112       window_type = ash::MultiProfileUMA::TELEPORT_WINDOW_POPUP;
    113     } else {
    114       window_type = ash::MultiProfileUMA::TELEPORT_WINDOW_BROWSER;
    115     }
    116   } else {
    117     // Unit tests might come here without a profile manager.
    118     if (!g_browser_process->profile_manager())
    119       return;
    120     // If it is not a browser, it is probably be a V2 application. In that case
    121     // one of the AppWindowRegistry instances should know about it.
    122     extensions::AppWindow* app_window = NULL;
    123     std::vector<Profile*> profiles =
    124         g_browser_process->profile_manager()->GetLoadedProfiles();
    125     for (std::vector<Profile*>::iterator it = profiles.begin();
    126          it != profiles.end() && app_window == NULL;
    127          it++) {
    128       app_window = extensions::AppWindowRegistry::Get(*it)
    129                        ->GetAppWindowForNativeWindow(window);
    130     }
    131     if (app_window) {
    132       if (app_window->window_type() ==
    133           extensions::AppWindow::WINDOW_TYPE_PANEL ||
    134           app_window->window_type() ==
    135           extensions::AppWindow::WINDOW_TYPE_V1_PANEL) {
    136         window_type = ash::MultiProfileUMA::TELEPORT_WINDOW_PANEL;
    137       } else {
    138         window_type = ash::MultiProfileUMA::TELEPORT_WINDOW_V2_APP;
    139       }
    140     }
    141   }
    142   ash::MultiProfileUMA::RecordTeleportWindowType(window_type);
    143 }
    144 
    145 }  // namespace
    146 
    147 namespace chrome {
    148 
    149 // A class to temporarily change the animation properties for a window.
    150 class AnimationSetter {
    151  public:
    152   AnimationSetter(aura::Window* window, int animation_time_in_ms)
    153       : window_(window),
    154         previous_animation_type_(
    155             wm::GetWindowVisibilityAnimationType(window_)),
    156         previous_animation_time_(
    157             wm::GetWindowVisibilityAnimationDuration(*window_)) {
    158     wm::SetWindowVisibilityAnimationType(
    159         window_,
    160         wm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE);
    161     wm::SetWindowVisibilityAnimationDuration(
    162         window_,
    163         base::TimeDelta::FromMilliseconds(animation_time_in_ms));
    164   }
    165 
    166   ~AnimationSetter() {
    167     wm::SetWindowVisibilityAnimationType(window_,
    168                                                     previous_animation_type_);
    169     wm::SetWindowVisibilityAnimationDuration(
    170         window_,
    171         previous_animation_time_);
    172   }
    173 
    174  private:
    175   // The window which gets used.
    176   aura::Window* window_;
    177 
    178   // Previous animation type.
    179   const int previous_animation_type_;
    180 
    181   // Previous animation time.
    182   const base::TimeDelta previous_animation_time_;
    183 
    184   DISALLOW_COPY_AND_ASSIGN(AnimationSetter);
    185 };
    186 
    187 // This class keeps track of all applications which were started for a user.
    188 // When an app gets created, the window will be tagged for that user. Note
    189 // that the destruction does not need to be tracked here since the universal
    190 // window observer will take care of that.
    191 class AppObserver : public extensions::AppWindowRegistry::Observer {
    192  public:
    193   explicit AppObserver(const std::string& user_id) : user_id_(user_id) {}
    194   virtual ~AppObserver() {}
    195 
    196   // AppWindowRegistry::Observer overrides:
    197   virtual void OnAppWindowAdded(extensions::AppWindow* app_window) OVERRIDE {
    198     aura::Window* window = app_window->GetNativeWindow();
    199     DCHECK(window);
    200     MultiUserWindowManagerChromeOS::GetInstance()->SetWindowOwner(window,
    201                                                                   user_id_);
    202   }
    203 
    204  private:
    205   std::string user_id_;
    206 
    207   DISALLOW_COPY_AND_ASSIGN(AppObserver);
    208 };
    209 
    210 MultiUserWindowManagerChromeOS::MultiUserWindowManagerChromeOS(
    211     const std::string& current_user_id)
    212     : current_user_id_(current_user_id),
    213       notification_blocker_(new MultiUserNotificationBlockerChromeOS(
    214           message_center::MessageCenter::Get(), current_user_id)),
    215       suppress_visibility_changes_(false),
    216       animation_speed_(ANIMATION_SPEED_NORMAL) {
    217   // Add a session state observer to be able to monitor session changes.
    218   if (ash::Shell::HasInstance())
    219     ash::Shell::GetInstance()->session_state_delegate()->
    220         AddSessionStateObserver(this);
    221 
    222   // The BrowserListObserver would have been better to use then the old
    223   // notification system, but that observer fires before the window got created.
    224   registrar_.Add(this, NOTIFICATION_BROWSER_WINDOW_READY,
    225                  content::NotificationService::AllSources());
    226 
    227   // Add an app window observer & all already running apps.
    228   Profile* profile = multi_user_util::GetProfileFromUserID(current_user_id);
    229   if (profile)
    230     AddUser(profile);
    231 }
    232 
    233 MultiUserWindowManagerChromeOS::~MultiUserWindowManagerChromeOS() {
    234   // When the MultiUserWindowManager gets destroyed, ash::Shell is mostly gone.
    235   // As such we should not try to finalize any outstanding user animations.
    236   // Note that the destruction of the object can be done later.
    237   if (animation_.get())
    238     animation_->CancelAnimation();
    239 
    240   // Remove all window observers.
    241   WindowToEntryMap::iterator window = window_to_entry_.begin();
    242   while (window != window_to_entry_.end()) {
    243     OnWindowDestroyed(window->first);
    244     window = window_to_entry_.begin();
    245   }
    246 
    247   // Remove all app observers.
    248   UserIDToAppWindowObserver::iterator app_observer_iterator =
    249       user_id_to_app_observer_.begin();
    250   while (app_observer_iterator != user_id_to_app_observer_.end()) {
    251     Profile* profile = multi_user_util::GetProfileFromUserID(
    252         app_observer_iterator->first);
    253     DCHECK(profile);
    254     extensions::AppWindowRegistry::Get(profile)
    255         ->RemoveObserver(app_observer_iterator->second);
    256     delete app_observer_iterator->second;
    257     user_id_to_app_observer_.erase(app_observer_iterator);
    258     app_observer_iterator = user_id_to_app_observer_.begin();
    259   }
    260 
    261   if (ash::Shell::HasInstance())
    262     ash::Shell::GetInstance()->session_state_delegate()->
    263         RemoveSessionStateObserver(this);
    264 }
    265 
    266 void MultiUserWindowManagerChromeOS::SetWindowOwner(
    267     aura::Window* window,
    268     const std::string& user_id) {
    269   // Make sure the window is valid and there was no owner yet.
    270   DCHECK(window);
    271   DCHECK(!user_id.empty());
    272   if (GetWindowOwner(window) == user_id)
    273     return;
    274   DCHECK(GetWindowOwner(window).empty());
    275   window_to_entry_[window] = new WindowEntry(user_id);
    276 
    277   // Remember the initial visibility of the window.
    278   window_to_entry_[window]->set_show(window->IsVisible());
    279 
    280   // Add observers to track state changes.
    281   window->AddObserver(this);
    282   wm::TransientWindowManager::Get(window)->AddObserver(this);
    283 
    284   // Check if this window was created due to a user interaction. If it was,
    285   // transfer it to the current user.
    286   if (IsProcessingUserEvent())
    287     window_to_entry_[window]->set_show_for_user(current_user_id_);
    288 
    289   // Add all transient children to our set of windows. Note that the function
    290   // will add the children but not the owner to the transient children map.
    291   AddTransientOwnerRecursive(window, window);
    292 
    293   // Notify entry adding.
    294   FOR_EACH_OBSERVER(Observer, observers_, OnOwnerEntryAdded(window));
    295 
    296   if (!IsWindowOnDesktopOfUser(window, current_user_id_))
    297     SetWindowVisibility(window, false, 0);
    298 }
    299 
    300 const std::string& MultiUserWindowManagerChromeOS::GetWindowOwner(
    301     aura::Window* window) const {
    302   WindowToEntryMap::const_iterator it = window_to_entry_.find(window);
    303   return it != window_to_entry_.end() ? it->second->owner()
    304                                       : base::EmptyString();
    305 }
    306 
    307 void MultiUserWindowManagerChromeOS::ShowWindowForUser(
    308     aura::Window* window,
    309     const std::string& user_id) {
    310   std::string previous_owner(GetUserPresentingWindow(window));
    311   if (!ShowWindowForUserIntern(window, user_id))
    312     return;
    313   // The window switched to a new desktop and we have to switch to that desktop,
    314   // but only when it was on the visible desktop and the the target is not the
    315   // visible desktop.
    316   if (user_id == current_user_id_ || previous_owner != current_user_id_)
    317     return;
    318 
    319   ash::Shell::GetInstance()->session_state_delegate()->SwitchActiveUser(
    320       user_id);
    321 }
    322 
    323 bool MultiUserWindowManagerChromeOS::AreWindowsSharedAmongUsers() const {
    324   WindowToEntryMap::const_iterator it = window_to_entry_.begin();
    325   for (; it != window_to_entry_.end(); ++it) {
    326     if (it->second->owner() != it->second->show_for_user())
    327       return true;
    328   }
    329   return false;
    330 }
    331 
    332 void MultiUserWindowManagerChromeOS::GetOwnersOfVisibleWindows(
    333     std::set<std::string>* user_ids) const {
    334   for (WindowToEntryMap::const_iterator it = window_to_entry_.begin();
    335        it != window_to_entry_.end();
    336        ++it) {
    337     if (it->first->IsVisible())
    338       user_ids->insert(it->second->owner());
    339   }
    340 }
    341 
    342 bool MultiUserWindowManagerChromeOS::IsWindowOnDesktopOfUser(
    343     aura::Window* window,
    344     const std::string& user_id) const {
    345   const std::string& presenting_user = GetUserPresentingWindow(window);
    346   return presenting_user.empty() || presenting_user == user_id;
    347 }
    348 
    349 const std::string& MultiUserWindowManagerChromeOS::GetUserPresentingWindow(
    350     aura::Window* window) const {
    351   WindowToEntryMap::const_iterator it = window_to_entry_.find(window);
    352   // If the window is not owned by anyone it is shown on all desktops and we
    353   // return the empty string.
    354   if (it == window_to_entry_.end())
    355     return base::EmptyString();
    356   // Otherwise we ask the object for its desktop.
    357   return it->second->show_for_user();
    358 }
    359 
    360 void MultiUserWindowManagerChromeOS::AddUser(content::BrowserContext* context) {
    361   Profile* profile = Profile::FromBrowserContext(context);
    362   const std::string& user_id = multi_user_util::GetUserIDFromProfile(profile);
    363   if (user_id_to_app_observer_.find(user_id) != user_id_to_app_observer_.end())
    364     return;
    365 
    366   user_id_to_app_observer_[user_id] = new AppObserver(user_id);
    367   extensions::AppWindowRegistry::Get(profile)
    368       ->AddObserver(user_id_to_app_observer_[user_id]);
    369 
    370   // Account all existing application windows of this user accordingly.
    371   const extensions::AppWindowRegistry::AppWindowList& app_windows =
    372       extensions::AppWindowRegistry::Get(profile)->app_windows();
    373   extensions::AppWindowRegistry::AppWindowList::const_iterator it =
    374       app_windows.begin();
    375   for (; it != app_windows.end(); ++it)
    376     user_id_to_app_observer_[user_id]->OnAppWindowAdded(*it);
    377 
    378   // Account all existing browser windows of this user accordingly.
    379   BrowserList* browser_list = BrowserList::GetInstance(HOST_DESKTOP_TYPE_ASH);
    380   BrowserList::const_iterator browser_it = browser_list->begin();
    381   for (; browser_it != browser_list->end(); ++browser_it) {
    382     if ((*browser_it)->profile()->GetOriginalProfile() == profile)
    383       AddBrowserWindow(*browser_it);
    384   }
    385 }
    386 
    387 void MultiUserWindowManagerChromeOS::AddObserver(Observer* observer) {
    388   observers_.AddObserver(observer);
    389 }
    390 
    391 void MultiUserWindowManagerChromeOS::RemoveObserver(Observer* observer) {
    392   observers_.RemoveObserver(observer);
    393 }
    394 
    395 void MultiUserWindowManagerChromeOS::ActiveUserChanged(
    396     const std::string& user_id) {
    397   DCHECK(user_id != current_user_id_);
    398   // This needs to be set before the animation starts.
    399   current_user_id_ = user_id;
    400 
    401   animation_.reset(
    402       new UserSwichAnimatorChromeOS(
    403           this, user_id, GetAdjustedAnimationTimeInMS(kUserFadeTimeMS)));
    404   // Call notifier here instead of observing ActiveUserChanged because
    405   // this must happen after MultiUserWindowManagerChromeOS is notified.
    406   ash::Shell::GetInstance()
    407       ->system_tray_notifier()
    408       ->NotifyMediaCaptureChanged();
    409 }
    410 
    411 void MultiUserWindowManagerChromeOS::OnWindowDestroyed(aura::Window* window) {
    412   if (GetWindowOwner(window).empty()) {
    413     // This must be a window in the transient chain - remove it and its
    414     // children from the owner.
    415     RemoveTransientOwnerRecursive(window);
    416     return;
    417   }
    418   wm::TransientWindowManager::Get(window)->RemoveObserver(this);
    419   // Remove the window from the owners list.
    420   delete window_to_entry_[window];
    421   window_to_entry_.erase(window);
    422 
    423   // Notify entry change.
    424   FOR_EACH_OBSERVER(Observer, observers_, OnOwnerEntryRemoved(window));
    425 }
    426 
    427 void MultiUserWindowManagerChromeOS::OnWindowVisibilityChanging(
    428     aura::Window* window, bool visible) {
    429   // This command gets called first and immediately when show or hide gets
    430   // called. We remember here the desired state for restoration IF we were
    431   // not ourselves issuing the call.
    432   // Note also that using the OnWindowVisibilityChanged callback cannot be
    433   // used for this.
    434   if (suppress_visibility_changes_)
    435     return;
    436 
    437   WindowToEntryMap::iterator it = window_to_entry_.find(window);
    438   // If the window is not owned by anyone it is shown on all desktops.
    439   if (it != window_to_entry_.end()) {
    440     // Remember what was asked for so that we can restore this when the user's
    441     // desktop gets restored.
    442     it->second->set_show(visible);
    443   } else {
    444     TransientWindowToVisibility::iterator it =
    445         transient_window_to_visibility_.find(window);
    446     if (it != transient_window_to_visibility_.end())
    447       it->second = visible;
    448   }
    449 }
    450 
    451 void MultiUserWindowManagerChromeOS::OnWindowVisibilityChanged(
    452     aura::Window* window, bool visible) {
    453   if (suppress_visibility_changes_)
    454     return;
    455 
    456   // Don't allow to make the window visible if it shouldn't be.
    457   if (visible && !IsWindowOnDesktopOfUser(window, current_user_id_)) {
    458     SetWindowVisibility(window, false, 0);
    459     return;
    460   }
    461   aura::Window* owned_parent = GetOwningWindowInTransientChain(window);
    462   if (owned_parent && owned_parent != window && visible &&
    463       !IsWindowOnDesktopOfUser(owned_parent, current_user_id_))
    464     SetWindowVisibility(window, false, 0);
    465 }
    466 
    467 void MultiUserWindowManagerChromeOS::OnTransientChildAdded(
    468     aura::Window* window,
    469     aura::Window* transient_window) {
    470   if (!GetWindowOwner(window).empty()) {
    471     AddTransientOwnerRecursive(transient_window, window);
    472     return;
    473   }
    474   aura::Window* owned_parent =
    475       GetOwningWindowInTransientChain(transient_window);
    476   if (!owned_parent)
    477     return;
    478 
    479   AddTransientOwnerRecursive(transient_window, owned_parent);
    480 }
    481 
    482 void MultiUserWindowManagerChromeOS::OnTransientChildRemoved(
    483     aura::Window* window,
    484     aura::Window* transient_window) {
    485   // Remove the transient child if the window itself is owned, or one of the
    486   // windows in its transient parents chain.
    487   if (!GetWindowOwner(window).empty() ||
    488       GetOwningWindowInTransientChain(window))
    489     RemoveTransientOwnerRecursive(transient_window);
    490 }
    491 
    492 void MultiUserWindowManagerChromeOS::Observe(
    493     int type,
    494     const content::NotificationSource& source,
    495     const content::NotificationDetails& details) {
    496   if (type == NOTIFICATION_BROWSER_WINDOW_READY)
    497     AddBrowserWindow(content::Source<Browser>(source).ptr());
    498 }
    499 
    500 void MultiUserWindowManagerChromeOS::SetAnimationSpeedForTest(
    501     MultiUserWindowManagerChromeOS::AnimationSpeed speed) {
    502   animation_speed_ = speed;
    503 }
    504 
    505 bool MultiUserWindowManagerChromeOS::IsAnimationRunningForTest() {
    506   return animation_.get() != NULL && !animation_->IsAnimationFinished();
    507 }
    508 
    509 const std::string& MultiUserWindowManagerChromeOS::GetCurrentUserForTest()
    510     const {
    511   return current_user_id_;
    512 }
    513 
    514 bool MultiUserWindowManagerChromeOS::ShowWindowForUserIntern(
    515     aura::Window* window,
    516     const std::string& user_id) {
    517   // If there is either no owner, or the owner is the current user, no action
    518   // is required.
    519   const std::string& owner = GetWindowOwner(window);
    520   if (owner.empty() ||
    521       (owner == user_id && IsWindowOnDesktopOfUser(window, user_id)))
    522     return false;
    523 
    524   bool minimized = ash::wm::GetWindowState(window)->IsMinimized();
    525   // Check that we are not trying to transfer ownership of a minimized window.
    526   if (user_id != owner && minimized)
    527     return false;
    528 
    529   if (minimized) {
    530     // If it is minimized it falls back to the original desktop.
    531     ash::MultiProfileUMA::RecordTeleportAction(
    532         ash::MultiProfileUMA::TELEPORT_WINDOW_RETURN_BY_MINIMIZE);
    533   } else {
    534     // If the window was transferred without getting minimized, we should record
    535     // the window type.
    536     RecordUMAForTransferredWindowType(window);
    537   }
    538 
    539   WindowToEntryMap::iterator it = window_to_entry_.find(window);
    540   it->second->set_show_for_user(user_id);
    541 
    542   // Show the window if the added user is the current one.
    543   if (user_id == current_user_id_) {
    544     // Only show the window if it should be shown according to its state.
    545     if (it->second->show())
    546       SetWindowVisibility(window, true, kTeleportAnimationTimeMS);
    547   } else {
    548     SetWindowVisibility(window, false, kTeleportAnimationTimeMS);
    549   }
    550 
    551   // Notify entry change.
    552   FOR_EACH_OBSERVER(Observer, observers_, OnOwnerEntryChanged(window));
    553   return true;
    554 }
    555 
    556 void MultiUserWindowManagerChromeOS::SetWindowVisibility(
    557     aura::Window* window, bool visible, int animation_time_in_ms) {
    558   if (window->IsVisible() == visible)
    559     return;
    560 
    561   // Hiding a system modal dialog should not be allowed. Instead we switch to
    562   // the user which is showing the system modal window.
    563   // Note that in some cases (e.g. unit test) windows might not have a root
    564   // window.
    565   if (!visible && window->GetRootWindow()) {
    566     // Get the system modal container for the window's root window.
    567     aura::Window* system_modal_container =
    568         window->GetRootWindow()->GetChildById(
    569             ash::kShellWindowId_SystemModalContainer);
    570     if (window->parent() == system_modal_container) {
    571       // The window is system modal and we need to find the parent which owns
    572       // it so that we can switch to the desktop accordingly.
    573       std::string user_id = GetUserPresentingWindow(window);
    574       if (user_id.empty()) {
    575         aura::Window* owning_window = GetOwningWindowInTransientChain(window);
    576         DCHECK(owning_window);
    577         user_id = GetUserPresentingWindow(owning_window);
    578         DCHECK(!user_id.empty());
    579       }
    580       ash::Shell::GetInstance()->session_state_delegate()->SwitchActiveUser(
    581           user_id);
    582       return;
    583     }
    584   }
    585 
    586   // To avoid that these commands are recorded as any other commands, we are
    587   // suppressing any window entry changes while this is going on.
    588   base::AutoReset<bool> suppressor(&suppress_visibility_changes_, true);
    589 
    590   if (visible) {
    591     ShowWithTransientChildrenRecursive(window, animation_time_in_ms);
    592   } else {
    593     if (window->HasFocus())
    594       window->Blur();
    595     SetWindowVisible(window, false, animation_time_in_ms);
    596   }
    597 }
    598 
    599 void MultiUserWindowManagerChromeOS::AddBrowserWindow(Browser* browser) {
    600   // A unit test (e.g. CrashRestoreComplexTest.RestoreSessionForThreeUsers) can
    601   // come here with no valid window.
    602   if (!browser->window() || !browser->window()->GetNativeWindow())
    603     return;
    604   SetWindowOwner(browser->window()->GetNativeWindow(),
    605                  multi_user_util::GetUserIDFromProfile(browser->profile()));
    606 }
    607 
    608 void MultiUserWindowManagerChromeOS::ShowWithTransientChildrenRecursive(
    609     aura::Window* window, int animation_time_in_ms) {
    610   aura::Window::Windows::const_iterator it =
    611       wm::GetTransientChildren(window).begin();
    612   for (; it != wm::GetTransientChildren(window).end(); ++it)
    613     ShowWithTransientChildrenRecursive(*it, animation_time_in_ms);
    614 
    615   // We show all children which were not explicitly hidden.
    616   TransientWindowToVisibility::iterator it2 =
    617       transient_window_to_visibility_.find(window);
    618   if (it2 == transient_window_to_visibility_.end() || it2->second)
    619     SetWindowVisible(window, true, animation_time_in_ms);
    620 }
    621 
    622 aura::Window* MultiUserWindowManagerChromeOS::GetOwningWindowInTransientChain(
    623     aura::Window* window) const {
    624   if (!GetWindowOwner(window).empty())
    625     return NULL;
    626   aura::Window* parent = wm::GetTransientParent(window);
    627   while (parent) {
    628     if (!GetWindowOwner(parent).empty())
    629       return parent;
    630     parent = wm::GetTransientParent(parent);
    631   }
    632   return NULL;
    633 }
    634 
    635 void MultiUserWindowManagerChromeOS::AddTransientOwnerRecursive(
    636     aura::Window* window,
    637     aura::Window* owned_parent) {
    638   // First add all child windows.
    639   aura::Window::Windows::const_iterator it =
    640       wm::GetTransientChildren(window).begin();
    641   for (; it != wm::GetTransientChildren(window).end(); ++it)
    642     AddTransientOwnerRecursive(*it, owned_parent);
    643 
    644   // If this window is the owned window, we do not have to handle it again.
    645   if (window == owned_parent)
    646     return;
    647 
    648   // Remember the current visibility.
    649   DCHECK(transient_window_to_visibility_.find(window) ==
    650              transient_window_to_visibility_.end());
    651   transient_window_to_visibility_[window] = window->IsVisible();
    652 
    653   // Add observers to track state changes.
    654   window->AddObserver(this);
    655   wm::TransientWindowManager::Get(window)->AddObserver(this);
    656 
    657   // Hide the window if it should not be shown. Note that this hide operation
    658   // will hide recursively this and all children - but we have already collected
    659   // their initial view state.
    660   if (!IsWindowOnDesktopOfUser(owned_parent, current_user_id_))
    661     SetWindowVisibility(window, false, kAnimationTimeMS);
    662 }
    663 
    664 void MultiUserWindowManagerChromeOS::RemoveTransientOwnerRecursive(
    665     aura::Window* window) {
    666   // First remove all child windows.
    667   aura::Window::Windows::const_iterator it =
    668       wm::GetTransientChildren(window).begin();
    669   for (; it != wm::GetTransientChildren(window).end(); ++it)
    670     RemoveTransientOwnerRecursive(*it);
    671 
    672   // Find from transient window storage the visibility for the given window,
    673   // set the visibility accordingly and delete the window from the map.
    674   TransientWindowToVisibility::iterator visibility_item =
    675       transient_window_to_visibility_.find(window);
    676   DCHECK(visibility_item != transient_window_to_visibility_.end());
    677 
    678   window->RemoveObserver(this);
    679   wm::TransientWindowManager::Get(window)->RemoveObserver(this);
    680 
    681   bool unowned_view_state = visibility_item->second;
    682   transient_window_to_visibility_.erase(visibility_item);
    683   if (unowned_view_state && !window->IsVisible()) {
    684     // To prevent these commands from being recorded as any other commands, we
    685     // are suppressing any window entry changes while this is going on.
    686     // Instead of calling SetWindowVisible, only show gets called here since all
    687     // dependents have been shown previously already.
    688     base::AutoReset<bool> suppressor(&suppress_visibility_changes_, true);
    689     window->Show();
    690   }
    691 }
    692 
    693 void MultiUserWindowManagerChromeOS::SetWindowVisible(
    694     aura::Window* window,
    695     bool visible,
    696     int animation_time_in_ms) {
    697   // The MaximizeModeWindowManager will not handle invisible windows since they
    698   // are not user activatable. Since invisible windows are not being tracked,
    699   // we tell it to maximize / track this window now before it gets shown, to
    700   // reduce animation jank from multiple resizes.
    701   if (visible)
    702     ash::Shell::GetInstance()->maximize_mode_controller()->AddWindow(window);
    703 
    704   AnimationSetter animation_setter(
    705       window,
    706       GetAdjustedAnimationTimeInMS(animation_time_in_ms));
    707 
    708   if (visible)
    709     window->Show();
    710   else
    711     window->Hide();
    712 
    713   // Make sure that animations have no influence on the window state after the
    714   // call.
    715   DCHECK_EQ(visible, window->IsVisible());
    716 }
    717 
    718 int MultiUserWindowManagerChromeOS::GetAdjustedAnimationTimeInMS(
    719     int default_time_in_ms) const {
    720   return animation_speed_ == ANIMATION_SPEED_NORMAL ? default_time_in_ms :
    721       (animation_speed_ == ANIMATION_SPEED_FAST ? 10 : 0);
    722 }
    723 
    724 }  // namespace chrome
    725