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/shell.h"
11 #include "ash/wm/mru_window_tracker.h"
12 #include "ash/wm/window_positioner.h"
13 #include "ash/wm/window_state.h"
14 #include "chrome/browser/chromeos/login/wallpaper_manager.h"
15 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
16 #include "chrome/browser/ui/ash/multi_user/multi_user_notification_blocker_chromeos.h"
17 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_chromeos.h"
18 #include "ui/wm/public/activation_client.h"
24 // The minimal possible animation time for animations which should happen
26 const int kMinimalAnimationTimeMS = 1;
28 // logic while the user gets switched.
29 class UserChangeActionDisabler {
31 UserChangeActionDisabler() {
32 ash::WindowPositioner::DisableAutoPositioning(true);
33 ash::Shell::GetInstance()->mru_window_tracker()->SetIgnoreActivations(true);
36 ~UserChangeActionDisabler() {
37 ash::WindowPositioner::DisableAutoPositioning(false);
38 ash::Shell::GetInstance()->mru_window_tracker()->SetIgnoreActivations(
43 DISALLOW_COPY_AND_ASSIGN(UserChangeActionDisabler);
48 UserSwichAnimatorChromeOS::UserSwichAnimatorChromeOS(
49 MultiUserWindowManagerChromeOS* owner,
50 const std::string& new_user_id,
51 int animation_speed_ms)
53 new_user_id_(new_user_id),
54 animation_speed_ms_(animation_speed_ms),
55 animation_step_(ANIMATION_STEP_HIDE_OLD_USER),
56 screen_cover_(GetScreenCover()) {
57 AdvanceUserTransitionAnimation();
59 if (!animation_speed_ms_) {
62 user_changed_animation_timer_.reset(new base::Timer(
64 base::TimeDelta::FromMilliseconds(animation_speed_ms_),
66 &UserSwichAnimatorChromeOS::AdvanceUserTransitionAnimation,
67 base::Unretained(this)),
69 user_changed_animation_timer_->Reset();
73 UserSwichAnimatorChromeOS::~UserSwichAnimatorChromeOS() {
78 bool UserSwichAnimatorChromeOS::CoversScreen(aura::Window* window) {
79 // Full screen covers the screen naturally. Since a normal window can have the
80 // same size as the work area, we only compare the bounds against the work
82 if (ash::wm::GetWindowState(window)->IsFullscreen())
84 gfx::Rect bounds = window->GetBoundsInRootWindow();
85 gfx::Rect work_area = gfx::Screen::GetScreenFor(window)->
86 GetDisplayNearestWindow(window).work_area();
87 bounds.Intersect(work_area);
88 return work_area == bounds;
91 void UserSwichAnimatorChromeOS::AdvanceUserTransitionAnimation() {
92 DCHECK_NE(animation_step_, ANIMATION_STEP_ENDED);
94 TransitionWallpaper(animation_step_);
95 TransitionUserShelf(animation_step_);
96 TransitionWindows(animation_step_);
98 // Advance to the next step.
99 switch (animation_step_) {
100 case ANIMATION_STEP_HIDE_OLD_USER:
101 animation_step_ = ANIMATION_STEP_SHOW_NEW_USER;
103 case ANIMATION_STEP_SHOW_NEW_USER:
104 animation_step_ = ANIMATION_STEP_FINALIZE;
106 case ANIMATION_STEP_FINALIZE:
107 user_changed_animation_timer_.reset();
108 animation_step_ = ANIMATION_STEP_ENDED;
110 case ANIMATION_STEP_ENDED:
116 void UserSwichAnimatorChromeOS::CancelAnimation() {
117 animation_step_ = ANIMATION_STEP_ENDED;
120 void UserSwichAnimatorChromeOS::FinalizeAnimation() {
121 user_changed_animation_timer_.reset();
122 while (ANIMATION_STEP_ENDED != animation_step_)
123 AdvanceUserTransitionAnimation();
126 void UserSwichAnimatorChromeOS::TransitionWallpaper(
127 AnimationStep animation_step) {
128 // Handle the wallpaper switch.
129 ash::UserWallpaperDelegate* wallpaper_delegate =
130 ash::Shell::GetInstance()->user_wallpaper_delegate();
131 if (animation_step == ANIMATION_STEP_HIDE_OLD_USER) {
132 // Set the wallpaper cross dissolve animation duration to our complete
133 // animation cycle for a fade in and fade out.
135 NO_USER_COVERS_SCREEN == screen_cover_ ? (2 * animation_speed_ms_) : 0;
136 wallpaper_delegate->SetAnimationDurationOverride(
137 std::max(duration, kMinimalAnimationTimeMS));
138 if (screen_cover_ != NEW_USER_COVERS_SCREEN) {
139 chromeos::WallpaperManager::Get()->SetUserWallpaperNow(new_user_id_);
141 (NO_USER_COVERS_SCREEN == screen_cover_ ? "->" : "") +
144 } else if (animation_step == ANIMATION_STEP_FINALIZE) {
145 // Revert the wallpaper cross dissolve animation duration back to the
147 if (screen_cover_ == NEW_USER_COVERS_SCREEN)
148 chromeos::WallpaperManager::Get()->SetUserWallpaperNow(new_user_id_);
150 // Coming here the wallpaper user id is the final result. No matter how we
152 wallpaper_user_id_ = new_user_id_;
153 wallpaper_delegate->SetAnimationDurationOverride(0);
157 void UserSwichAnimatorChromeOS::TransitionUserShelf(
158 AnimationStep animation_step) {
159 // The shelf animation duration override.
160 int duration_override = animation_speed_ms_;
161 // Handle the shelf order of items. This is done once the old user is hidden.
162 if (animation_step == ANIMATION_STEP_SHOW_NEW_USER) {
163 // Some unit tests have no ChromeLauncherController.
164 if (ChromeLauncherController::instance())
165 ChromeLauncherController::instance()->ActiveUserChanged(new_user_id_);
166 // We kicked off the shelf animation in the command above. As such we can
167 // disable the override now again.
168 duration_override = 0;
171 if (!animation_speed_ms_ || animation_step == ANIMATION_STEP_FINALIZE)
174 ash::Shell::RootWindowControllerList controller =
175 ash::Shell::GetInstance()->GetAllRootWindowControllers();
176 for (ash::Shell::RootWindowControllerList::iterator iter = controller.begin();
177 iter != controller.end(); ++iter) {
178 (*iter)->GetShelfLayoutManager()->SetAnimationDurationOverride(
182 // For each root window hide the shelf.
183 if (animation_step == ANIMATION_STEP_HIDE_OLD_USER) {
184 aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows();
185 for (aura::Window::Windows::const_iterator iter = root_windows.begin();
186 iter != root_windows.end(); ++iter) {
187 // This shelf change is only part of the animation and will be updated by
188 // ChromeLauncherController::ActiveUserChanged() to the new users value.
189 // Note that the user perference will not be changed.
190 ash::Shell::GetInstance()->SetShelfAutoHideBehavior(
191 ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN, *iter);
196 void UserSwichAnimatorChromeOS::TransitionWindows(
197 AnimationStep animation_step) {
198 // Disable the window position manager and the MRU window tracker temporarily.
199 UserChangeActionDisabler disabler;
201 if (animation_step == ANIMATION_STEP_HIDE_OLD_USER ||
202 (animation_step == ANIMATION_STEP_FINALIZE &&
203 screen_cover_ == BOTH_USERS_COVER_SCREEN)) {
204 // We need to show/hide the windows in the same order as they were created
205 // in their parent window(s) to keep the layer / window hierarchy in sync.
206 // To achieve that we first collect all parent windows and then enumerate
207 // all windows in those parent windows and show or hide them accordingly.
209 // Create a list of all parent windows we have to check.
210 std::set<aura::Window*> parent_list;
211 for (MultiUserWindowManagerChromeOS::WindowToEntryMap::const_iterator it =
212 owner_->window_to_entry().begin();
213 it != owner_->window_to_entry().end(); ++it) {
214 aura::Window* parent = it->first->parent();
215 if (parent_list.find(parent) == parent_list.end())
216 parent_list.insert(parent);
219 for (std::set<aura::Window*>::iterator it_parents = parent_list.begin();
220 it_parents != parent_list.end(); ++it_parents) {
221 const aura::Window::Windows window_list = (*it_parents)->children();
222 // In case of |BOTH_USERS_COVER_SCREEN| the desktop might shine through
223 // if all windows fade (in or out). To avoid this we only fade the topmost
224 // covering window (in / out) and make / keep all other covering windows
225 // visible while animating. |foreground_window_found| will get set when
226 // the top fading window was found.
227 bool foreground_window_found = false;
228 // Covering windows which follow the fade direction will also fade - all
229 // others will get immediately shown / kept shown until the animation is
231 bool foreground_becomes_visible = false;
232 for (aura::Window::Windows::const_reverse_iterator it_window =
233 window_list.rbegin();
234 it_window != window_list.rend(); ++it_window) {
235 aura::Window* window = *it_window;
236 MultiUserWindowManagerChromeOS::WindowToEntryMap::const_iterator
237 it_map = owner_->window_to_entry().find(window);
238 if (it_map != owner_->window_to_entry().end()) {
239 bool should_be_visible =
240 it_map->second->show_for_user() == new_user_id_ &&
241 it_map->second->show();
242 bool is_visible = window->IsVisible();
243 ash::wm::WindowState* window_state = ash::wm::GetWindowState(window);
244 if (it_map->second->owner() == new_user_id_ &&
245 it_map->second->show_for_user() != new_user_id_ &&
246 window_state->IsMinimized()) {
247 // Pull back minimized visiting windows to the owners desktop.
248 owner_->ShowWindowForUserIntern(window, new_user_id_);
249 window_state->Unminimize();
250 } else if (should_be_visible != is_visible) {
252 int duration = animation_step == ANIMATION_STEP_FINALIZE ?
253 0 : (2 * animation_speed_ms_);
254 duration = std::max(kMinimalAnimationTimeMS, duration);
255 if (animation_step != ANIMATION_STEP_FINALIZE &&
256 screen_cover_ == BOTH_USERS_COVER_SCREEN &&
257 CoversScreen(window)) {
258 if (!foreground_window_found) {
259 foreground_window_found = true;
260 foreground_becomes_visible = should_be_visible;
261 } else if (should_be_visible != foreground_becomes_visible) {
262 // Covering windows behind the foreground window which are
263 // inverting their visibility should immediately become visible
264 // or stay visible until the animation is finished.
265 duration = kMinimalAnimationTimeMS;
266 if (!should_be_visible)
271 owner_->SetWindowVisibility(window, should_be_visible, duration);
278 // Activation and real switch are happening after the other user gets shown.
279 if (animation_step == ANIMATION_STEP_SHOW_NEW_USER) {
280 // Finally we need to restore the previously active window.
281 ash::MruWindowTracker::WindowList mru_list =
282 ash::Shell::GetInstance()->mru_window_tracker()->BuildMruWindowList();
283 if (!mru_list.empty()) {
284 aura::Window* window = mru_list[0];
285 ash::wm::WindowState* window_state = ash::wm::GetWindowState(window);
286 if (owner_->IsWindowOnDesktopOfUser(window, new_user_id_) &&
287 !window_state->IsMinimized()) {
288 aura::client::ActivationClient* client =
289 aura::client::GetActivationClient(window->GetRootWindow());
290 // Several unit tests come here without an activation client.
292 client->ActivateWindow(window);
296 // This is called directly here to make sure notification_blocker will see
297 // the new window status.
298 owner_->notification_blocker()->ActiveUserChanged(new_user_id_);
302 UserSwichAnimatorChromeOS::TransitioningScreenCover
303 UserSwichAnimatorChromeOS::GetScreenCover() {
304 TransitioningScreenCover cover = NO_USER_COVERS_SCREEN;
305 for (MultiUserWindowManagerChromeOS::WindowToEntryMap::const_iterator it_map =
306 owner_->window_to_entry().begin();
307 it_map != owner_->window_to_entry().end();
309 aura::Window* window = it_map->first;
310 if (window->IsVisible() && CoversScreen(window)) {
311 if (cover == NEW_USER_COVERS_SCREEN)
312 return BOTH_USERS_COVER_SCREEN;
314 cover = OLD_USER_COVERS_SCREEN;
315 } else if (owner_->IsWindowOnDesktopOfUser(window, new_user_id_) &&
316 CoversScreen(window)) {
317 if (cover == OLD_USER_COVERS_SCREEN)
318 return BOTH_USERS_COVER_SCREEN;
320 cover = NEW_USER_COVERS_SCREEN;
326 } // namespace chrome