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. 4 5#include "chrome/browser/ui/ash/multi_user/user_switch_animator_chromeos.h" 6 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" 20 21namespace chrome { 22 23namespace { 24 25// The minimal possible animation time for animations which should happen 26// "instantly". 27const int kMinimalAnimationTimeMS = 1; 28 29// logic while the user gets switched. 30class UserChangeActionDisabler { 31 public: 32 UserChangeActionDisabler() { 33 ash::WindowPositioner::DisableAutoPositioning(true); 34 ash::Shell::GetInstance()->mru_window_tracker()->SetIgnoreActivations(true); 35 } 36 37 ~UserChangeActionDisabler() { 38 ash::WindowPositioner::DisableAutoPositioning(false); 39 ash::Shell::GetInstance()->mru_window_tracker()->SetIgnoreActivations( 40 false); 41 } 42 private: 43 44 DISALLOW_COPY_AND_ASSIGN(UserChangeActionDisabler); 45}; 46 47} // namespace 48 49UserSwichAnimatorChromeOS::UserSwichAnimatorChromeOS( 50 MultiUserWindowManagerChromeOS* owner, 51 const std::string& new_user_id, 52 int animation_speed_ms) 53 : owner_(owner), 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(); 59 60 if (!animation_speed_ms_) { 61 FinalizeAnimation(); 62 } else { 63 user_changed_animation_timer_.reset(new base::Timer( 64 FROM_HERE, 65 base::TimeDelta::FromMilliseconds(animation_speed_ms_), 66 base::Bind( 67 &UserSwichAnimatorChromeOS::AdvanceUserTransitionAnimation, 68 base::Unretained(this)), 69 true)); 70 user_changed_animation_timer_->Reset(); 71 } 72} 73 74UserSwichAnimatorChromeOS::~UserSwichAnimatorChromeOS() { 75 FinalizeAnimation(); 76} 77 78// static 79bool UserSwichAnimatorChromeOS::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 82 // area. 83 if (ash::wm::GetWindowState(window)->IsFullscreen()) 84 return true; 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; 90} 91 92void UserSwichAnimatorChromeOS::AdvanceUserTransitionAnimation() { 93 DCHECK_NE(animation_step_, ANIMATION_STEP_ENDED); 94 95 TransitionWallpaper(animation_step_); 96 TransitionUserShelf(animation_step_); 97 TransitionWindows(animation_step_); 98 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; 103 break; 104 case ANIMATION_STEP_SHOW_NEW_USER: 105 animation_step_ = ANIMATION_STEP_FINALIZE; 106 break; 107 case ANIMATION_STEP_FINALIZE: 108 user_changed_animation_timer_.reset(); 109 animation_step_ = ANIMATION_STEP_ENDED; 110 break; 111 case ANIMATION_STEP_ENDED: 112 NOTREACHED(); 113 break; 114 } 115} 116 117void UserSwichAnimatorChromeOS::CancelAnimation() { 118 animation_step_ = ANIMATION_STEP_ENDED; 119} 120 121void UserSwichAnimatorChromeOS::FinalizeAnimation() { 122 user_changed_animation_timer_.reset(); 123 while (ANIMATION_STEP_ENDED != animation_step_) 124 AdvanceUserTransitionAnimation(); 125} 126 127void UserSwichAnimatorChromeOS::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. 135 int duration = 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_); 141 wallpaper_user_id_ = 142 (NO_USER_COVERS_SCREEN == screen_cover_ ? "->" : "") + 143 new_user_id_; 144 } 145 } else if (animation_step == ANIMATION_STEP_FINALIZE) { 146 // Revert the wallpaper cross dissolve animation duration back to the 147 // default. 148 if (screen_cover_ == NEW_USER_COVERS_SCREEN) 149 chromeos::WallpaperManager::Get()->SetUserWallpaperNow(new_user_id_); 150 151 // Coming here the wallpaper user id is the final result. No matter how we 152 // got here. 153 wallpaper_user_id_ = new_user_id_; 154 wallpaper_delegate->SetAnimationDurationOverride(0); 155 } 156} 157 158void UserSwichAnimatorChromeOS::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); 176 } 177 // We kicked off the shelf animation above and the override can be 178 // removed. 179 duration_override = 0; 180 } 181 182 if (!animation_speed_ms_ || animation_step == ANIMATION_STEP_FINALIZE) 183 return; 184 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( 192 duration_override); 193 } 194 195 if (animation_step != ANIMATION_STEP_HIDE_OLD_USER) 196 return; 197 198 // For each root window hide the shelf. 199 aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows(); 200 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); 216 } else { 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); 222 } 223 } 224} 225 226void UserSwichAnimatorChromeOS::TransitionWindows( 227 AnimationStep animation_step) { 228 // Disable the window position manager and the MRU window tracker temporarily. 229 UserChangeActionDisabler disabler; 230 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. 238 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); 247 } 248 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 260 // finished. 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) { 281 bool animate = true; 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) 297 animate = false; 298 } 299 } 300 if (animate) 301 owner_->SetWindowVisibility(window, should_be_visible, duration); 302 } 303 } 304 } 305 } 306 } 307 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. 321 if (client) 322 client->ActivateWindow(window); 323 } 324 } 325 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_); 329 } 330} 331 332UserSwichAnimatorChromeOS::TransitioningScreenCover 333UserSwichAnimatorChromeOS::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(); 338 ++it_map) { 339 aura::Window* window = it_map->first; 340 if (root_window && window->GetRootWindow() != root_window) 341 continue; 342 if (window->IsVisible() && CoversScreen(window)) { 343 if (cover == NEW_USER_COVERS_SCREEN) 344 return BOTH_USERS_COVER_SCREEN; 345 else 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; 351 else 352 cover = NEW_USER_COVERS_SCREEN; 353 } 354 } 355 return cover; 356} 357 358} // namespace chrome 359