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 "chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h"
16 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
17 #include "chrome/browser/ui/ash/multi_user/multi_user_notification_blocker_chromeos.h"
18 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_chromeos.h"
19 #include "ui/wm/public/activation_client.h"
25 // The minimal possible animation time for animations which should happen
27 const int kMinimalAnimationTimeMS
= 1;
29 // logic while the user gets switched.
30 class UserChangeActionDisabler
{
32 UserChangeActionDisabler() {
33 ash::WindowPositioner::DisableAutoPositioning(true);
34 ash::Shell::GetInstance()->mru_window_tracker()->SetIgnoreActivations(true);
37 ~UserChangeActionDisabler() {
38 ash::WindowPositioner::DisableAutoPositioning(false);
39 ash::Shell::GetInstance()->mru_window_tracker()->SetIgnoreActivations(
44 DISALLOW_COPY_AND_ASSIGN(UserChangeActionDisabler
);
49 UserSwitchAnimatorChromeOS::UserSwitchAnimatorChromeOS(
50 MultiUserWindowManagerChromeOS
* owner
,
51 const std::string
& new_user_id
,
52 int animation_speed_ms
)
54 new_user_id_(new_user_id
),
55 animation_speed_ms_(animation_speed_ms
),
56 animation_step_(ANIMATION_STEP_HIDE_OLD_USER
),
57 screen_cover_(GetScreenCover(NULL
)) {
58 AdvanceUserTransitionAnimation();
60 if (!animation_speed_ms_
) {
63 user_changed_animation_timer_
.reset(new base::Timer(
65 base::TimeDelta::FromMilliseconds(animation_speed_ms_
),
67 &UserSwitchAnimatorChromeOS::AdvanceUserTransitionAnimation
,
68 base::Unretained(this)),
70 user_changed_animation_timer_
->Reset();
74 UserSwitchAnimatorChromeOS::~UserSwitchAnimatorChromeOS() {
79 bool UserSwitchAnimatorChromeOS::CoversScreen(aura::Window
* window
) {
80 // Full screen covers the screen naturally. Since a normal window can have the
81 // same size as the work area, we only compare the bounds against the work
83 if (ash::wm::GetWindowState(window
)->IsFullscreen())
85 gfx::Rect bounds
= window
->GetBoundsInRootWindow();
86 gfx::Rect work_area
= gfx::Screen::GetScreenFor(window
)->
87 GetDisplayNearestWindow(window
).work_area();
88 bounds
.Intersect(work_area
);
89 return work_area
== bounds
;
92 void UserSwitchAnimatorChromeOS::AdvanceUserTransitionAnimation() {
93 DCHECK_NE(animation_step_
, ANIMATION_STEP_ENDED
);
95 TransitionWallpaper(animation_step_
);
96 TransitionUserShelf(animation_step_
);
97 TransitionWindows(animation_step_
);
99 // Advance to the next step.
100 switch (animation_step_
) {
101 case ANIMATION_STEP_HIDE_OLD_USER
:
102 animation_step_
= ANIMATION_STEP_SHOW_NEW_USER
;
104 case ANIMATION_STEP_SHOW_NEW_USER
:
105 animation_step_
= ANIMATION_STEP_FINALIZE
;
107 case ANIMATION_STEP_FINALIZE
:
108 user_changed_animation_timer_
.reset();
109 animation_step_
= ANIMATION_STEP_ENDED
;
111 case ANIMATION_STEP_ENDED
:
117 void UserSwitchAnimatorChromeOS::CancelAnimation() {
118 animation_step_
= ANIMATION_STEP_ENDED
;
121 void UserSwitchAnimatorChromeOS::FinalizeAnimation() {
122 user_changed_animation_timer_
.reset();
123 while (ANIMATION_STEP_ENDED
!= animation_step_
)
124 AdvanceUserTransitionAnimation();
127 void UserSwitchAnimatorChromeOS::TransitionWallpaper(
128 AnimationStep animation_step
) {
129 // Handle the wallpaper switch.
130 ash::UserWallpaperDelegate
* wallpaper_delegate
=
131 ash::Shell::GetInstance()->user_wallpaper_delegate();
132 if (animation_step
== ANIMATION_STEP_HIDE_OLD_USER
) {
133 // Set the wallpaper cross dissolve animation duration to our complete
134 // animation cycle for a fade in and fade out.
136 NO_USER_COVERS_SCREEN
== screen_cover_
? (2 * animation_speed_ms_
) : 0;
137 wallpaper_delegate
->SetAnimationDurationOverride(
138 std::max(duration
, kMinimalAnimationTimeMS
));
139 if (screen_cover_
!= NEW_USER_COVERS_SCREEN
) {
140 chromeos::WallpaperManager::Get()->SetUserWallpaperNow(new_user_id_
);
142 (NO_USER_COVERS_SCREEN
== screen_cover_
? "->" : "") +
145 } else if (animation_step
== ANIMATION_STEP_FINALIZE
) {
146 // Revert the wallpaper cross dissolve animation duration back to the
148 if (screen_cover_
== NEW_USER_COVERS_SCREEN
)
149 chromeos::WallpaperManager::Get()->SetUserWallpaperNow(new_user_id_
);
151 // Coming here the wallpaper user id is the final result. No matter how we
153 wallpaper_user_id_
= new_user_id_
;
154 wallpaper_delegate
->SetAnimationDurationOverride(0);
158 void UserSwitchAnimatorChromeOS::TransitionUserShelf(
159 AnimationStep animation_step
) {
160 ChromeLauncherController
* chrome_launcher_controller
=
161 ChromeLauncherController::instance();
162 // The shelf animation duration override.
163 int duration_override
= animation_speed_ms_
;
164 // Handle the shelf order of items. This is done once the old user is hidden.
165 if (animation_step
== ANIMATION_STEP_SHOW_NEW_USER
) {
166 // Some unit tests have no ChromeLauncherController.
167 if (chrome_launcher_controller
)
168 chrome_launcher_controller
->ActiveUserChanged(new_user_id_
);
169 // Hide the black rectangle on top of each shelf again.
170 aura::Window::Windows root_windows
= ash::Shell::GetAllRootWindows();
171 for (aura::Window::Windows::const_iterator iter
= root_windows
.begin();
172 iter
!= root_windows
.end(); ++iter
) {
173 ash::ShelfWidget
* shelf
=
174 ash::RootWindowController::ForWindow(*iter
)->shelf();
175 shelf
->HideShelfBehindBlackBar(false, duration_override
);
177 // We kicked off the shelf animation above and the override can be
179 duration_override
= 0;
182 if (!animation_speed_ms_
|| animation_step
== ANIMATION_STEP_FINALIZE
)
185 // Note: The animation duration override will be set before the old user gets
186 // hidden and reset after the animations for the new user got kicked off.
187 ash::Shell::RootWindowControllerList controller
=
188 ash::Shell::GetInstance()->GetAllRootWindowControllers();
189 for (ash::Shell::RootWindowControllerList::iterator iter
= controller
.begin();
190 iter
!= controller
.end(); ++iter
) {
191 (*iter
)->GetShelfLayoutManager()->SetAnimationDurationOverride(
195 if (animation_step
!= ANIMATION_STEP_HIDE_OLD_USER
)
198 // For each root window hide the shelf.
199 aura::Window::Windows root_windows
= ash::Shell::GetAllRootWindows();
201 for (aura::Window::Windows::const_iterator iter
= root_windows
.begin();
202 iter
!= root_windows
.end(); ++iter
) {
203 // Hiding the shelf will cause a resize on a maximized window.
204 // If the shelf is then shown for the following user in the same location,
205 // the window gets resized again. Since each resize can cause a considerable
206 // CPU usage and therefore effect jank, we should avoid hiding the shelf if
207 // the start and end location are the same and cover the shelf instead with
208 // a black rectangle on top.
209 if (GetScreenCover(*iter
) != NO_USER_COVERS_SCREEN
&&
210 (!chrome_launcher_controller
||
211 !chrome_launcher_controller
->ShelfBoundsChangesProbablyWithUser(
212 *iter
, new_user_id_
))) {
213 ash::ShelfWidget
* shelf
=
214 ash::RootWindowController::ForWindow(*iter
)->shelf();
215 shelf
->HideShelfBehindBlackBar(true, duration_override
);
217 // This shelf change is only part of the animation and will be updated by
218 // ChromeLauncherController::ActiveUserChanged() to the new users value.
219 // Note that the user preference will not be changed.
220 ash::Shell::GetInstance()->SetShelfAutoHideBehavior(
221 ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN
, *iter
);
226 void UserSwitchAnimatorChromeOS::TransitionWindows(
227 AnimationStep animation_step
) {
228 // Disable the window position manager and the MRU window tracker temporarily.
229 UserChangeActionDisabler disabler
;
231 if (animation_step
== ANIMATION_STEP_HIDE_OLD_USER
||
232 (animation_step
== ANIMATION_STEP_FINALIZE
&&
233 screen_cover_
== BOTH_USERS_COVER_SCREEN
)) {
234 // We need to show/hide the windows in the same order as they were created
235 // in their parent window(s) to keep the layer / window hierarchy in sync.
236 // To achieve that we first collect all parent windows and then enumerate
237 // all windows in those parent windows and show or hide them accordingly.
239 // Create a list of all parent windows we have to check.
240 std::set
<aura::Window
*> parent_list
;
241 for (MultiUserWindowManagerChromeOS::WindowToEntryMap::const_iterator it
=
242 owner_
->window_to_entry().begin();
243 it
!= owner_
->window_to_entry().end(); ++it
) {
244 aura::Window
* parent
= it
->first
->parent();
245 if (parent_list
.find(parent
) == parent_list
.end())
246 parent_list
.insert(parent
);
249 for (std::set
<aura::Window
*>::iterator it_parents
= parent_list
.begin();
250 it_parents
!= parent_list
.end(); ++it_parents
) {
251 const aura::Window::Windows window_list
= (*it_parents
)->children();
252 // In case of |BOTH_USERS_COVER_SCREEN| the desktop might shine through
253 // if all windows fade (in or out). To avoid this we only fade the topmost
254 // covering window (in / out) and make / keep all other covering windows
255 // visible while animating. |foreground_window_found| will get set when
256 // the top fading window was found.
257 bool foreground_window_found
= false;
258 // Covering windows which follow the fade direction will also fade - all
259 // others will get immediately shown / kept shown until the animation is
261 bool foreground_becomes_visible
= false;
262 for (aura::Window::Windows::const_reverse_iterator it_window
=
263 window_list
.rbegin();
264 it_window
!= window_list
.rend(); ++it_window
) {
265 aura::Window
* window
= *it_window
;
266 MultiUserWindowManagerChromeOS::WindowToEntryMap::const_iterator
267 it_map
= owner_
->window_to_entry().find(window
);
268 if (it_map
!= owner_
->window_to_entry().end()) {
269 bool should_be_visible
=
270 it_map
->second
->show_for_user() == new_user_id_
&&
271 it_map
->second
->show();
272 bool is_visible
= window
->IsVisible();
273 ash::wm::WindowState
* window_state
= ash::wm::GetWindowState(window
);
274 if (it_map
->second
->owner() == new_user_id_
&&
275 it_map
->second
->show_for_user() != new_user_id_
&&
276 window_state
->IsMinimized()) {
277 // Pull back minimized visiting windows to the owners desktop.
278 owner_
->ShowWindowForUserIntern(window
, new_user_id_
);
279 window_state
->Unminimize();
280 } else if (should_be_visible
!= is_visible
) {
282 int duration
= animation_step
== ANIMATION_STEP_FINALIZE
?
283 0 : (2 * animation_speed_ms_
);
284 duration
= std::max(kMinimalAnimationTimeMS
, duration
);
285 if (animation_step
!= ANIMATION_STEP_FINALIZE
&&
286 screen_cover_
== BOTH_USERS_COVER_SCREEN
&&
287 CoversScreen(window
)) {
288 if (!foreground_window_found
) {
289 foreground_window_found
= true;
290 foreground_becomes_visible
= should_be_visible
;
291 } else if (should_be_visible
!= foreground_becomes_visible
) {
292 // Covering windows behind the foreground window which are
293 // inverting their visibility should immediately become visible
294 // or stay visible until the animation is finished.
295 duration
= kMinimalAnimationTimeMS
;
296 if (!should_be_visible
)
301 owner_
->SetWindowVisibility(window
, should_be_visible
, duration
);
308 // Activation and real switch are happening after the other user gets shown.
309 if (animation_step
== ANIMATION_STEP_SHOW_NEW_USER
) {
310 // Finally we need to restore the previously active window.
311 ash::MruWindowTracker::WindowList mru_list
=
312 ash::Shell::GetInstance()->mru_window_tracker()->BuildMruWindowList();
313 if (!mru_list
.empty()) {
314 aura::Window
* window
= mru_list
[0];
315 ash::wm::WindowState
* window_state
= ash::wm::GetWindowState(window
);
316 if (owner_
->IsWindowOnDesktopOfUser(window
, new_user_id_
) &&
317 !window_state
->IsMinimized()) {
318 aura::client::ActivationClient
* client
=
319 aura::client::GetActivationClient(window
->GetRootWindow());
320 // Several unit tests come here without an activation client.
322 client
->ActivateWindow(window
);
326 // This is called directly here to make sure notification_blocker will see
327 // the new window status.
328 owner_
->notification_blocker()->ActiveUserChanged(new_user_id_
);
332 UserSwitchAnimatorChromeOS::TransitioningScreenCover
333 UserSwitchAnimatorChromeOS::GetScreenCover(aura::Window
* root_window
) {
334 TransitioningScreenCover cover
= NO_USER_COVERS_SCREEN
;
335 for (MultiUserWindowManagerChromeOS::WindowToEntryMap::const_iterator it_map
=
336 owner_
->window_to_entry().begin();
337 it_map
!= owner_
->window_to_entry().end();
339 aura::Window
* window
= it_map
->first
;
340 if (root_window
&& window
->GetRootWindow() != root_window
)
342 if (window
->IsVisible() && CoversScreen(window
)) {
343 if (cover
== NEW_USER_COVERS_SCREEN
)
344 return BOTH_USERS_COVER_SCREEN
;
346 cover
= OLD_USER_COVERS_SCREEN
;
347 } else if (owner_
->IsWindowOnDesktopOfUser(window
, new_user_id_
) &&
348 CoversScreen(window
)) {
349 if (cover
== OLD_USER_COVERS_SCREEN
)
350 return BOTH_USERS_COVER_SCREEN
;
352 cover
= NEW_USER_COVERS_SCREEN
;
358 } // namespace chrome