Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / ash / multi_user / user_switch_animator_chromeos.cc
blob5c49d80e66d4e789c60654a1a2c836163472fc6e
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"
25 namespace chrome {
27 namespace {
29 // The minimal possible animation time for animations which should happen
30 // "instantly".
31 const int kMinimalAnimationTimeMS = 1;
33 // logic while the user gets switched.
34 class UserChangeActionDisabler {
35 public:
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(
44 false);
46 private:
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 {
56 public:
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 {
69 delete this;
72 void OnLayerAnimationAborted(ui::LayerAnimationSequence* sequence) override {
73 delete this;
76 void OnLayerAnimationScheduled(
77 ui::LayerAnimationSequence* sequence) override {}
79 private:
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) {
89 DCHECK(window_list);
90 auto active_window = ash::wm::GetActiveWindow();
91 if (!active_window)
92 return;
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);
101 } // namespace
103 UserSwitchAnimatorChromeOS::UserSwitchAnimatorChromeOS(
104 MultiUserWindowManagerChromeOS* owner,
105 const std::string& new_user_id,
106 int animation_speed_ms)
107 : owner_(owner),
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_) {
117 FinalizeAnimation();
118 } else {
119 user_changed_animation_timer_.reset(new base::Timer(
120 FROM_HERE,
121 base::TimeDelta::FromMilliseconds(animation_speed_ms_),
122 base::Bind(
123 &UserSwitchAnimatorChromeOS::AdvanceUserTransitionAnimation,
124 base::Unretained(this)),
125 true));
126 user_changed_animation_timer_->Reset();
130 UserSwitchAnimatorChromeOS::~UserSwitchAnimatorChromeOS() {
131 FinalizeAnimation();
134 // static
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
138 // area.
139 if (ash::wm::GetWindowState(window)->IsFullscreen())
140 return true;
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;
159 break;
160 case ANIMATION_STEP_SHOW_NEW_USER:
161 animation_step_ = ANIMATION_STEP_FINALIZE;
162 break;
163 case ANIMATION_STEP_FINALIZE:
164 user_changed_animation_timer_.reset();
165 animation_step_ = ANIMATION_STEP_ENDED;
166 break;
167 case ANIMATION_STEP_ENDED:
168 NOTREACHED();
169 break;
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.
191 int duration =
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_);
197 wallpaper_user_id_ =
198 (NO_USER_COVERS_SCREEN == screen_cover_ ? "->" : "") +
199 new_user_id_;
201 } else if (animation_step == ANIMATION_STEP_FINALIZE) {
202 // Revert the wallpaper cross dissolve animation duration back to the
203 // default.
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
208 // got here.
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
234 // removed.
235 duration_override = 0;
238 if (!animation_speed_ms_ || animation_step == ANIMATION_STEP_FINALIZE)
239 return;
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(
248 duration_override);
251 if (animation_step != ANIMATION_STEP_HIDE_OLD_USER)
252 return;
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);
272 } else {
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_)
296 continue;
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();
319 continue;
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(),
337 old_layer);
340 owner_->SetWindowVisibility(window, false, duration);
344 // Show new user.
345 auto new_user_itr = windows_by_user_id_.find(new_user_id_);
346 if (new_user_itr == windows_by_user_id_.end())
347 return;
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);
357 break;
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
362 // here.
363 break;
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());
377 if (client)
378 client->ActivateWindow(window);
380 } else {
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
383 // |nullptr|).
384 aura::Window* root_window = ash::Shell::GetPrimaryRootWindow();
385 aura::client::ActivationClient* client =
386 aura::client::GetActivationClient(root_window);
387 if (client)
388 client->ActivateWindow(nullptr);
391 owner_->notification_blocker()->ActiveUserChanged(new_user_id_);
392 break;
394 case ANIMATION_STEP_ENDED:
395 NOTREACHED();
396 break;
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();
406 ++it_map) {
407 aura::Window* window = it_map->first;
408 if (root_window && window->GetRootWindow() != root_window)
409 continue;
410 if (window->IsVisible() && CoversScreen(window)) {
411 if (cover == NEW_USER_COVERS_SCREEN)
412 return BOTH_USERS_COVER_SCREEN;
413 else
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;
419 else
420 cover = NEW_USER_COVERS_SCREEN;
423 return cover;
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(
445 child_window);
452 } // namespace chrome