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
46namespace {
47
48// The animation time in milliseconds for a single window which is fading
49// in / out.
50const int kAnimationTimeMS = 100;
51
52// The animation time in milliseconds for the fade in and / or out when
53// switching users.
54const int kUserFadeTimeMS = 110;
55
56// The animation time in ms for a window which get teleported to another screen.
57const int kTeleportAnimationTimeMS = 300;
58
59// Checks if a given event is a user event.
60bool 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.
73bool 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.
101void 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
147namespace chrome {
148
149// A class to temporarily change the animation properties for a window.
150class 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.
191class 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
210MultiUserWindowManagerChromeOS::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
233MultiUserWindowManagerChromeOS::~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
266void 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
300const 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
307void 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
323bool 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
332void 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
342bool 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
349const 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
360void 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
387void MultiUserWindowManagerChromeOS::AddObserver(Observer* observer) {
388  observers_.AddObserver(observer);
389}
390
391void MultiUserWindowManagerChromeOS::RemoveObserver(Observer* observer) {
392  observers_.RemoveObserver(observer);
393}
394
395void 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
411void 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
427void 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
451void 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
467void 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
482void 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
492void 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
500void MultiUserWindowManagerChromeOS::SetAnimationSpeedForTest(
501    MultiUserWindowManagerChromeOS::AnimationSpeed speed) {
502  animation_speed_ = speed;
503}
504
505bool MultiUserWindowManagerChromeOS::IsAnimationRunningForTest() {
506  return animation_.get() != NULL && !animation_->IsAnimationFinished();
507}
508
509const std::string& MultiUserWindowManagerChromeOS::GetCurrentUserForTest()
510    const {
511  return current_user_id_;
512}
513
514bool 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
556void 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
599void 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
608void 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
622aura::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
635void 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
664void 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
693void 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
718int 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