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