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/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/users/wallpaper/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" 19 20 namespace chrome { 21 22 namespace { 23 24 // The minimal possible animation time for animations which should happen 25 // "instantly". 26 const int kMinimalAnimationTimeMS = 1; 27 28 // logic while the user gets switched. 29 class UserChangeActionDisabler { 30 public: 31 UserChangeActionDisabler() { 32 ash::WindowPositioner::DisableAutoPositioning(true); 33 ash::Shell::GetInstance()->mru_window_tracker()->SetIgnoreActivations(true); 34 } 35 36 ~UserChangeActionDisabler() { 37 ash::WindowPositioner::DisableAutoPositioning(false); 38 ash::Shell::GetInstance()->mru_window_tracker()->SetIgnoreActivations( 39 false); 40 } 41 private: 42 43 DISALLOW_COPY_AND_ASSIGN(UserChangeActionDisabler); 44 }; 45 46 } // namespace 47 48 UserSwichAnimatorChromeOS::UserSwichAnimatorChromeOS( 49 MultiUserWindowManagerChromeOS* owner, 50 const std::string& new_user_id, 51 int animation_speed_ms) 52 : owner_(owner), 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(); 58 59 if (!animation_speed_ms_) { 60 FinalizeAnimation(); 61 } else { 62 user_changed_animation_timer_.reset(new base::Timer( 63 FROM_HERE, 64 base::TimeDelta::FromMilliseconds(animation_speed_ms_), 65 base::Bind( 66 &UserSwichAnimatorChromeOS::AdvanceUserTransitionAnimation, 67 base::Unretained(this)), 68 true)); 69 user_changed_animation_timer_->Reset(); 70 } 71 } 72 73 UserSwichAnimatorChromeOS::~UserSwichAnimatorChromeOS() { 74 FinalizeAnimation(); 75 } 76 77 // static 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 81 // area. 82 if (ash::wm::GetWindowState(window)->IsFullscreen()) 83 return true; 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; 89 } 90 91 void UserSwichAnimatorChromeOS::AdvanceUserTransitionAnimation() { 92 DCHECK_NE(animation_step_, ANIMATION_STEP_ENDED); 93 94 TransitionWallpaper(animation_step_); 95 TransitionUserShelf(animation_step_); 96 TransitionWindows(animation_step_); 97 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; 102 break; 103 case ANIMATION_STEP_SHOW_NEW_USER: 104 animation_step_ = ANIMATION_STEP_FINALIZE; 105 break; 106 case ANIMATION_STEP_FINALIZE: 107 user_changed_animation_timer_.reset(); 108 animation_step_ = ANIMATION_STEP_ENDED; 109 break; 110 case ANIMATION_STEP_ENDED: 111 NOTREACHED(); 112 break; 113 } 114 } 115 116 void UserSwichAnimatorChromeOS::CancelAnimation() { 117 animation_step_ = ANIMATION_STEP_ENDED; 118 } 119 120 void UserSwichAnimatorChromeOS::FinalizeAnimation() { 121 user_changed_animation_timer_.reset(); 122 while (ANIMATION_STEP_ENDED != animation_step_) 123 AdvanceUserTransitionAnimation(); 124 } 125 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. 134 int duration = 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_); 140 wallpaper_user_id_ = 141 (NO_USER_COVERS_SCREEN == screen_cover_ ? "->" : "") + 142 new_user_id_; 143 } 144 } else if (animation_step == ANIMATION_STEP_FINALIZE) { 145 // Revert the wallpaper cross dissolve animation duration back to the 146 // default. 147 if (screen_cover_ == NEW_USER_COVERS_SCREEN) 148 chromeos::WallpaperManager::Get()->SetUserWallpaperNow(new_user_id_); 149 150 // Coming here the wallpaper user id is the final result. No matter how we 151 // got here. 152 wallpaper_user_id_ = new_user_id_; 153 wallpaper_delegate->SetAnimationDurationOverride(0); 154 } 155 } 156 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; 169 } 170 171 if (!animation_speed_ms_ || animation_step == ANIMATION_STEP_FINALIZE) 172 return; 173 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( 179 duration_override); 180 } 181 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); 192 } 193 } 194 } 195 196 void UserSwichAnimatorChromeOS::TransitionWindows( 197 AnimationStep animation_step) { 198 // Disable the window position manager and the MRU window tracker temporarily. 199 UserChangeActionDisabler disabler; 200 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. 208 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); 217 } 218 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 230 // finished. 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) { 251 bool animate = true; 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) 267 animate = false; 268 } 269 } 270 if (animate) 271 owner_->SetWindowVisibility(window, should_be_visible, duration); 272 } 273 } 274 } 275 } 276 } 277 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. 291 if (client) 292 client->ActivateWindow(window); 293 } 294 } 295 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_); 299 } 300 } 301 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(); 308 ++it_map) { 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; 313 else 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; 319 else 320 cover = NEW_USER_COVERS_SCREEN; 321 } 322 } 323 return cover; 324 } 325 326 } // namespace chrome 327