1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/ash/multi_user/user_switch_animator_chromeos.h"
7 #include "ash/desktop_background/user_wallpaper_delegate.h"
8 #include "ash/root_window_controller.h"
9 #include "ash/shelf/shelf_layout_manager.h"
10 #include "ash/shelf/shelf_widget.h"
11 #include "ash/shell.h"
12 #include "ash/wm/mru_window_tracker.h"
13 #include "ash/wm/window_positioner.h"
14 #include "ash/wm/window_state.h"
15 #include "ash/wm/window_util.h"
16 #include "chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h"
17 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
18 #include "chrome/browser/ui/ash/multi_user/multi_user_notification_blocker_chromeos.h"
19 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_chromeos.h"
20 #include "ui/compositor/layer_animation_observer.h"
21 #include "ui/compositor/layer_tree_owner.h"
22 #include "ui/wm/core/window_util.h"
23 #include "ui/wm/public/activation_client.h"
29 // The minimal possible animation time for animations which should happen
31 const int kMinimalAnimationTimeMS
= 1;
33 // logic while the user gets switched.
34 class UserChangeActionDisabler
{
36 UserChangeActionDisabler() {
37 ash::WindowPositioner::DisableAutoPositioning(true);
38 ash::Shell::GetInstance()->mru_window_tracker()->SetIgnoreActivations(true);
41 ~UserChangeActionDisabler() {
42 ash::WindowPositioner::DisableAutoPositioning(false);
43 ash::Shell::GetInstance()->mru_window_tracker()->SetIgnoreActivations(
48 DISALLOW_COPY_AND_ASSIGN(UserChangeActionDisabler
);
51 // Defines an animation watcher for the 'hide' animation of the first maximized
52 // window we encounter while looping through the old user's windows. This is
53 // to observe the end of the animation so that we can destruct the old detached
54 // layer of the window.
55 class MaximizedWindowAnimationWatcher
: public ui::LayerAnimationObserver
{
57 MaximizedWindowAnimationWatcher(ui::LayerAnimator
* animator_to_watch
,
58 ui::LayerTreeOwner
* old_layer
)
59 : animator_(animator_to_watch
), old_layer_(old_layer
) {
60 animator_
->AddObserver(this);
63 ~MaximizedWindowAnimationWatcher() override
{
64 animator_
->RemoveObserver(this);
67 // ui::LayerAnimationObserver:
68 void OnLayerAnimationEnded(ui::LayerAnimationSequence
* sequence
) override
{
72 void OnLayerAnimationAborted(ui::LayerAnimationSequence
* sequence
) override
{
76 void OnLayerAnimationScheduled(
77 ui::LayerAnimationSequence
* sequence
) override
{}
80 ui::LayerAnimator
* animator_
;
81 scoped_ptr
<ui::LayerTreeOwner
> old_layer_
;
83 DISALLOW_COPY_AND_ASSIGN(MaximizedWindowAnimationWatcher
);
86 // Modifies the given |window_list| such that the most-recently used window (if
87 // any, and if it exists in |window_list|) will be the last window in the list.
88 void PutMruWindowLast(std::vector
<aura::Window
*>* window_list
) {
90 auto active_window
= ash::wm::GetActiveWindow();
94 auto itr
= std::find(window_list
->begin(), window_list
->end(), active_window
);
95 if (itr
!= window_list
->end()) {
96 window_list
->erase(itr
);
97 window_list
->push_back(active_window
);
103 UserSwitchAnimatorChromeOS::UserSwitchAnimatorChromeOS(
104 MultiUserWindowManagerChromeOS
* owner
,
105 const std::string
& new_user_id
,
106 int animation_speed_ms
)
108 new_user_id_(new_user_id
),
109 animation_speed_ms_(animation_speed_ms
),
110 animation_step_(ANIMATION_STEP_HIDE_OLD_USER
),
111 screen_cover_(GetScreenCover(NULL
)),
112 windows_by_user_id_() {
113 BuildUserToWindowsListMap();
114 AdvanceUserTransitionAnimation();
116 if (!animation_speed_ms_
) {
119 user_changed_animation_timer_
.reset(new base::Timer(
121 base::TimeDelta::FromMilliseconds(animation_speed_ms_
),
123 &UserSwitchAnimatorChromeOS::AdvanceUserTransitionAnimation
,
124 base::Unretained(this)),
126 user_changed_animation_timer_
->Reset();
130 UserSwitchAnimatorChromeOS::~UserSwitchAnimatorChromeOS() {
135 bool UserSwitchAnimatorChromeOS::CoversScreen(aura::Window
* window
) {
136 // Full screen covers the screen naturally. Since a normal window can have the
137 // same size as the work area, we only compare the bounds against the work
139 if (ash::wm::GetWindowState(window
)->IsFullscreen())
141 gfx::Rect bounds
= window
->GetBoundsInRootWindow();
142 gfx::Rect work_area
= gfx::Screen::GetScreenFor(window
)->
143 GetDisplayNearestWindow(window
).work_area();
144 bounds
.Intersect(work_area
);
145 return work_area
== bounds
;
148 void UserSwitchAnimatorChromeOS::AdvanceUserTransitionAnimation() {
149 DCHECK_NE(animation_step_
, ANIMATION_STEP_ENDED
);
151 TransitionWallpaper(animation_step_
);
152 TransitionUserShelf(animation_step_
);
153 TransitionWindows(animation_step_
);
155 // Advance to the next step.
156 switch (animation_step_
) {
157 case ANIMATION_STEP_HIDE_OLD_USER
:
158 animation_step_
= ANIMATION_STEP_SHOW_NEW_USER
;
160 case ANIMATION_STEP_SHOW_NEW_USER
:
161 animation_step_
= ANIMATION_STEP_FINALIZE
;
163 case ANIMATION_STEP_FINALIZE
:
164 user_changed_animation_timer_
.reset();
165 animation_step_
= ANIMATION_STEP_ENDED
;
167 case ANIMATION_STEP_ENDED
:
173 void UserSwitchAnimatorChromeOS::CancelAnimation() {
174 animation_step_
= ANIMATION_STEP_ENDED
;
177 void UserSwitchAnimatorChromeOS::FinalizeAnimation() {
178 user_changed_animation_timer_
.reset();
179 while (ANIMATION_STEP_ENDED
!= animation_step_
)
180 AdvanceUserTransitionAnimation();
183 void UserSwitchAnimatorChromeOS::TransitionWallpaper(
184 AnimationStep animation_step
) {
185 // Handle the wallpaper switch.
186 ash::UserWallpaperDelegate
* wallpaper_delegate
=
187 ash::Shell::GetInstance()->user_wallpaper_delegate();
188 if (animation_step
== ANIMATION_STEP_HIDE_OLD_USER
) {
189 // Set the wallpaper cross dissolve animation duration to our complete
190 // animation cycle for a fade in and fade out.
192 NO_USER_COVERS_SCREEN
== screen_cover_
? (2 * animation_speed_ms_
) : 0;
193 wallpaper_delegate
->SetAnimationDurationOverride(
194 std::max(duration
, kMinimalAnimationTimeMS
));
195 if (screen_cover_
!= NEW_USER_COVERS_SCREEN
) {
196 chromeos::WallpaperManager::Get()->SetUserWallpaperNow(new_user_id_
);
198 (NO_USER_COVERS_SCREEN
== screen_cover_
? "->" : "") +
201 } else if (animation_step
== ANIMATION_STEP_FINALIZE
) {
202 // Revert the wallpaper cross dissolve animation duration back to the
204 if (screen_cover_
== NEW_USER_COVERS_SCREEN
)
205 chromeos::WallpaperManager::Get()->SetUserWallpaperNow(new_user_id_
);
207 // Coming here the wallpaper user id is the final result. No matter how we
209 wallpaper_user_id_
= new_user_id_
;
210 wallpaper_delegate
->SetAnimationDurationOverride(0);
214 void UserSwitchAnimatorChromeOS::TransitionUserShelf(
215 AnimationStep animation_step
) {
216 ChromeLauncherController
* chrome_launcher_controller
=
217 ChromeLauncherController::instance();
218 // The shelf animation duration override.
219 int duration_override
= animation_speed_ms_
;
220 // Handle the shelf order of items. This is done once the old user is hidden.
221 if (animation_step
== ANIMATION_STEP_SHOW_NEW_USER
) {
222 // Some unit tests have no ChromeLauncherController.
223 if (chrome_launcher_controller
)
224 chrome_launcher_controller
->ActiveUserChanged(new_user_id_
);
225 // Hide the black rectangle on top of each shelf again.
226 aura::Window::Windows root_windows
= ash::Shell::GetAllRootWindows();
227 for (aura::Window::Windows::const_iterator iter
= root_windows
.begin();
228 iter
!= root_windows
.end(); ++iter
) {
229 ash::ShelfWidget
* shelf
=
230 ash::RootWindowController::ForWindow(*iter
)->shelf();
231 shelf
->HideShelfBehindBlackBar(false, duration_override
);
233 // We kicked off the shelf animation above and the override can be
235 duration_override
= 0;
238 if (!animation_speed_ms_
|| animation_step
== ANIMATION_STEP_FINALIZE
)
241 // Note: The animation duration override will be set before the old user gets
242 // hidden and reset after the animations for the new user got kicked off.
243 ash::Shell::RootWindowControllerList controller
=
244 ash::Shell::GetInstance()->GetAllRootWindowControllers();
245 for (ash::Shell::RootWindowControllerList::iterator iter
= controller
.begin();
246 iter
!= controller
.end(); ++iter
) {
247 (*iter
)->GetShelfLayoutManager()->SetAnimationDurationOverride(
251 if (animation_step
!= ANIMATION_STEP_HIDE_OLD_USER
)
254 // For each root window hide the shelf.
255 aura::Window::Windows root_windows
= ash::Shell::GetAllRootWindows();
257 for (aura::Window::Windows::const_iterator iter
= root_windows
.begin();
258 iter
!= root_windows
.end(); ++iter
) {
259 // Hiding the shelf will cause a resize on a maximized window.
260 // If the shelf is then shown for the following user in the same location,
261 // the window gets resized again. Since each resize can cause a considerable
262 // CPU usage and therefore effect jank, we should avoid hiding the shelf if
263 // the start and end location are the same and cover the shelf instead with
264 // a black rectangle on top.
265 if (GetScreenCover(*iter
) != NO_USER_COVERS_SCREEN
&&
266 (!chrome_launcher_controller
||
267 !chrome_launcher_controller
->ShelfBoundsChangesProbablyWithUser(
268 *iter
, new_user_id_
))) {
269 ash::ShelfWidget
* shelf
=
270 ash::RootWindowController::ForWindow(*iter
)->shelf();
271 shelf
->HideShelfBehindBlackBar(true, duration_override
);
273 // This shelf change is only part of the animation and will be updated by
274 // ChromeLauncherController::ActiveUserChanged() to the new users value.
275 // Note that the user preference will not be changed.
276 ash::Shell::GetInstance()->SetShelfAutoHideBehavior(
277 ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN
, *iter
);
282 void UserSwitchAnimatorChromeOS::TransitionWindows(
283 AnimationStep animation_step
) {
284 // Disable the window position manager and the MRU window tracker temporarily.
285 UserChangeActionDisabler disabler
;
287 // Animation duration.
288 int duration
= std::max(kMinimalAnimationTimeMS
, 2 * animation_speed_ms_
);
290 switch (animation_step
) {
291 case ANIMATION_STEP_HIDE_OLD_USER
: {
292 // Hide the old users.
293 for (auto& user_pair
: windows_by_user_id_
) {
294 auto& show_for_user_id
= user_pair
.first
;
295 if (show_for_user_id
== new_user_id_
)
298 bool found_foreground_maximized_window
= false;
300 // We hide the windows such that the MRU window is the last one to be
301 // hidden, at which point all other windows have already been hidden,
302 // and hence the FocusController will not be able to find a next
303 // activateable window to restore focus to, and so we don't change
304 // window order (crbug.com/424307).
305 PutMruWindowLast(&(user_pair
.second
));
306 for (auto& window
: user_pair
.second
) {
307 auto window_state
= ash::wm::GetWindowState(window
);
309 // Minimized visiting windows (minimized windows with an owner
310 // different than that of the for_show_user_id) should retrun to their
311 // original owners' desktops.
312 MultiUserWindowManagerChromeOS::WindowToEntryMap::const_iterator itr
=
313 owner_
->window_to_entry().find(window
);
314 DCHECK(itr
!= owner_
->window_to_entry().end());
315 if (show_for_user_id
!= itr
->second
->owner() &&
316 window_state
->IsMinimized()) {
317 owner_
->ShowWindowForUserIntern(window
, itr
->second
->owner());
318 window_state
->Unminimize();
322 if (!found_foreground_maximized_window
&& CoversScreen(window
) &&
323 screen_cover_
== BOTH_USERS_COVER_SCREEN
) {
324 // Maximized windows should be hidden, but visually kept visible
325 // in order to prevent showing the background while the animation is
326 // in progress. Therefore we detach the old layer and recreate fresh
327 // ones. The old layers will be destructed at the animation step
328 // |ANIMATION_STEP_FINALIZE|.
329 // old_layers_.push_back(wm::RecreateLayers(window));
330 // We only want to do this for the first (foreground) maximized
331 // window we encounter.
332 found_foreground_maximized_window
= true;
333 ui::LayerTreeOwner
* old_layer
=
334 wm::RecreateLayers(window
).release();
335 window
->layer()->parent()->StackAtBottom(old_layer
->root());
336 new MaximizedWindowAnimationWatcher(window
->layer()->GetAnimator(),
340 owner_
->SetWindowVisibility(window
, false, duration
);
345 auto new_user_itr
= windows_by_user_id_
.find(new_user_id_
);
346 if (new_user_itr
== windows_by_user_id_
.end())
349 for (auto& window
: new_user_itr
->second
) {
350 auto entry
= owner_
->window_to_entry().find(window
);
351 DCHECK(entry
!= owner_
->window_to_entry().end());
353 if (entry
->second
->show())
354 owner_
->SetWindowVisibility(window
, true, duration
);
359 case ANIMATION_STEP_SHOW_NEW_USER
: {
360 // In order to make the animation look better, we had to move the code
361 // that shows the new user to the previous step. Hence, we do nothing
365 case ANIMATION_STEP_FINALIZE
: {
366 // Reactivate the MRU window of the new user.
367 ash::MruWindowTracker::WindowList mru_list
=
368 ash::Shell::GetInstance()->mru_window_tracker()->BuildMruWindowList();
369 if (!mru_list
.empty()) {
370 aura::Window
* window
= mru_list
[0];
371 ash::wm::WindowState
* window_state
= ash::wm::GetWindowState(window
);
372 if (owner_
->IsWindowOnDesktopOfUser(window
, new_user_id_
) &&
373 !window_state
->IsMinimized()) {
374 // Several unit tests come here without an activation client.
375 aura::client::ActivationClient
* client
=
376 aura::client::GetActivationClient(window
->GetRootWindow());
378 client
->ActivateWindow(window
);
381 // If the new user has no windows at all in his MRU windows list, we
382 // must deactivate any active window (by setting the active window to
384 aura::Window
* root_window
= ash::Shell::GetPrimaryRootWindow();
385 aura::client::ActivationClient
* client
=
386 aura::client::GetActivationClient(root_window
);
388 client
->ActivateWindow(nullptr);
391 owner_
->notification_blocker()->ActiveUserChanged(new_user_id_
);
394 case ANIMATION_STEP_ENDED
:
400 UserSwitchAnimatorChromeOS::TransitioningScreenCover
401 UserSwitchAnimatorChromeOS::GetScreenCover(aura::Window
* root_window
) {
402 TransitioningScreenCover cover
= NO_USER_COVERS_SCREEN
;
403 for (MultiUserWindowManagerChromeOS::WindowToEntryMap::const_iterator it_map
=
404 owner_
->window_to_entry().begin();
405 it_map
!= owner_
->window_to_entry().end();
407 aura::Window
* window
= it_map
->first
;
408 if (root_window
&& window
->GetRootWindow() != root_window
)
410 if (window
->IsVisible() && CoversScreen(window
)) {
411 if (cover
== NEW_USER_COVERS_SCREEN
)
412 return BOTH_USERS_COVER_SCREEN
;
414 cover
= OLD_USER_COVERS_SCREEN
;
415 } else if (owner_
->IsWindowOnDesktopOfUser(window
, new_user_id_
) &&
416 CoversScreen(window
)) {
417 if (cover
== OLD_USER_COVERS_SCREEN
)
418 return BOTH_USERS_COVER_SCREEN
;
420 cover
= NEW_USER_COVERS_SCREEN
;
426 void UserSwitchAnimatorChromeOS::BuildUserToWindowsListMap() {
427 // This is to be called only at the time this animation is constructed.
428 DCHECK(windows_by_user_id_
.empty());
430 // For each unique parent window, we enumerate its children windows, and
431 // for each child if it's in the |window_to_entry()| map, we add it to the
432 // |windows_by_user_id_| map.
433 // This gives us a list of windows per each user that is in the same order
434 // they were created in their parent windows.
435 std::set
<aura::Window
*> parent_windows
;
436 auto& window_to_entry_map
= owner_
->window_to_entry();
437 for (auto& window_entry_pair
: window_to_entry_map
) {
438 aura::Window
* parent_window
= window_entry_pair
.first
->parent();
439 if (parent_windows
.find(parent_window
) == parent_windows
.end()) {
440 parent_windows
.insert(parent_window
);
441 for (auto& child_window
: parent_window
->children()) {
442 auto itr
= window_to_entry_map
.find(child_window
);
443 if (itr
!= window_to_entry_map
.end()) {
444 windows_by_user_id_
[itr
->second
->show_for_user()].push_back(
452 } // namespace chrome