| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/multi_user/user_switch_animator.h" |
| |
| #include "ash/multi_user/multi_user_window_manager.h" |
| #include "ash/shell.h" |
| #include "ash/wallpaper/wallpaper_controller.h" |
| #include "ash/wm/mru_window_tracker.h" |
| #include "ash/wm/window_positioner.h" |
| #include "base/bind.h" |
| #include "ui/aura/client/aura_constants.h" |
| #include "ui/compositor/layer_animation_observer.h" |
| #include "ui/compositor/layer_tree_owner.h" |
| #include "ui/compositor/scoped_layer_animation_settings.h" |
| #include "ui/display/display.h" |
| #include "ui/wm/core/window_util.h" |
| #include "ui/wm/public/activation_client.h" |
| |
| namespace ash { |
| namespace { |
| |
| // The minimal possible animation time for animations which should happen |
| // "instantly". |
| constexpr base::TimeDelta kMinimalAnimationTime = |
| base::TimeDelta::FromMilliseconds(1); |
| |
| // logic while the user gets switched. |
| class UserChangeActionDisabler { |
| public: |
| UserChangeActionDisabler() { |
| WindowPositioner::DisableAutoPositioning(true); |
| Shell::Get()->mru_window_tracker()->SetIgnoreActivations(true); |
| } |
| |
| ~UserChangeActionDisabler() { |
| WindowPositioner::DisableAutoPositioning(false); |
| Shell::Get()->mru_window_tracker()->SetIgnoreActivations(false); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(UserChangeActionDisabler); |
| }; |
| |
| // Defines an animation watcher for the 'hide' animation of the first maximized |
| // window we encounter while looping through the old user's windows. This is |
| // to observe the end of the animation so that we can destruct the old detached |
| // layer of the window. |
| class MaximizedWindowAnimationWatcher : public ui::ImplicitAnimationObserver { |
| public: |
| explicit MaximizedWindowAnimationWatcher( |
| std::unique_ptr<ui::LayerTreeOwner> old_layer) |
| : old_layer_(std::move(old_layer)) {} |
| |
| // ui::ImplicitAnimationObserver: |
| void OnImplicitAnimationsCompleted() override { delete this; } |
| |
| private: |
| std::unique_ptr<ui::LayerTreeOwner> old_layer_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MaximizedWindowAnimationWatcher); |
| }; |
| |
| // Modifies the given |window_list| such that the most-recently used window (if |
| // any, and if it exists in |window_list|) will be the last window in the list. |
| void PutMruWindowLast(std::vector<aura::Window*>* window_list) { |
| DCHECK(window_list); |
| auto it = std::find_if( |
| window_list->begin(), window_list->end(), |
| [](aura::Window* window) { return wm::IsActiveWindow(window); }); |
| if (it == window_list->end()) |
| return; |
| // Move the active window to the end of the list. |
| aura::Window* active_window = *it; |
| window_list->erase(it); |
| window_list->push_back(active_window); |
| } |
| |
| } // namespace |
| |
| UserSwitchAnimator::UserSwitchAnimator( |
| MultiUserWindowManager* owner, |
| mojom::WallpaperUserInfoPtr wallpaper_user_info, |
| base::TimeDelta animation_speed) |
| : owner_(owner), |
| wallpaper_user_info_(std::move(wallpaper_user_info)), |
| new_account_id_(wallpaper_user_info_->account_id), |
| animation_speed_(animation_speed), |
| animation_step_(ANIMATION_STEP_HIDE_OLD_USER), |
| screen_cover_(GetScreenCover(NULL)), |
| windows_by_account_id_() { |
| BuildUserToWindowsListMap(); |
| AdvanceUserTransitionAnimation(); |
| |
| if (animation_speed_.is_zero()) { |
| FinalizeAnimation(); |
| } else { |
| user_changed_animation_timer_.reset(new base::RepeatingTimer()); |
| user_changed_animation_timer_->Start( |
| FROM_HERE, animation_speed_, |
| base::BindRepeating(&UserSwitchAnimator::AdvanceUserTransitionAnimation, |
| base::Unretained(this))); |
| } |
| } |
| |
| UserSwitchAnimator::~UserSwitchAnimator() { |
| FinalizeAnimation(); |
| } |
| |
| // static |
| bool UserSwitchAnimator::CoversScreen(aura::Window* window) { |
| // Full screen covers the screen naturally. Since a normal window can have the |
| // same size as the work area, we only compare the bounds against the work |
| // area. |
| if (wm::WindowStateIs(window, ui::SHOW_STATE_FULLSCREEN)) |
| return true; |
| gfx::Rect bounds = window->GetBoundsInScreen(); |
| gfx::Rect work_area = |
| display::Screen::GetScreen()->GetDisplayNearestWindow(window).work_area(); |
| bounds.Intersect(work_area); |
| return work_area == bounds; |
| } |
| |
| void UserSwitchAnimator::AdvanceUserTransitionAnimation() { |
| DCHECK_NE(animation_step_, ANIMATION_STEP_ENDED); |
| |
| TransitionWallpaper(animation_step_); |
| TransitionUserShelf(animation_step_); |
| TransitionWindows(animation_step_); |
| |
| // Advance to the next step. |
| switch (animation_step_) { |
| case ANIMATION_STEP_HIDE_OLD_USER: |
| animation_step_ = ANIMATION_STEP_SHOW_NEW_USER; |
| break; |
| case ANIMATION_STEP_SHOW_NEW_USER: |
| animation_step_ = ANIMATION_STEP_FINALIZE; |
| break; |
| case ANIMATION_STEP_FINALIZE: |
| user_changed_animation_timer_.reset(); |
| animation_step_ = ANIMATION_STEP_ENDED; |
| if (owner_->client_) |
| owner_->client_->OnDidSwitchActiveAccount(); |
| break; |
| case ANIMATION_STEP_ENDED: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| void UserSwitchAnimator::CancelAnimation() { |
| animation_step_ = ANIMATION_STEP_ENDED; |
| } |
| |
| void UserSwitchAnimator::FinalizeAnimation() { |
| user_changed_animation_timer_.reset(); |
| while (ANIMATION_STEP_ENDED != animation_step_) |
| AdvanceUserTransitionAnimation(); |
| } |
| |
| void UserSwitchAnimator::TransitionWallpaper(AnimationStep animation_step) { |
| WallpaperController* wallpaper_controller = |
| Shell::Get()->wallpaper_controller(); |
| |
| // Handle the wallpaper switch. |
| if (animation_step == ANIMATION_STEP_HIDE_OLD_USER) { |
| // Set the wallpaper cross dissolve animation duration to our complete |
| // animation cycle for a fade in and fade out. |
| base::TimeDelta duration = |
| animation_speed_ * (NO_USER_COVERS_SCREEN == screen_cover_ ? 2 : 0); |
| wallpaper_controller->SetAnimationDuration( |
| duration > kMinimalAnimationTime ? duration : kMinimalAnimationTime); |
| if (screen_cover_ != NEW_USER_COVERS_SCREEN) { |
| DCHECK(wallpaper_user_info_); |
| wallpaper_controller->ShowUserWallpaper(std::move(wallpaper_user_info_)); |
| wallpaper_user_id_for_test_ = |
| (NO_USER_COVERS_SCREEN == screen_cover_ ? "->" : "") + |
| new_account_id_.Serialize(); |
| } |
| } else if (animation_step == ANIMATION_STEP_FINALIZE) { |
| // Revert the wallpaper cross dissolve animation duration back to the |
| // default. |
| if (screen_cover_ == NEW_USER_COVERS_SCREEN) { |
| DCHECK(wallpaper_user_info_); |
| wallpaper_controller->ShowUserWallpaper(std::move(wallpaper_user_info_)); |
| } |
| |
| // Coming here the wallpaper user id is the final result. No matter how we |
| // got here. |
| wallpaper_user_id_for_test_ = new_account_id_.Serialize(); |
| wallpaper_controller->SetAnimationDuration(base::TimeDelta()); |
| } |
| } |
| |
| void UserSwitchAnimator::TransitionUserShelf(AnimationStep animation_step) { |
| if (animation_step != ANIMATION_STEP_SHOW_NEW_USER) |
| return; |
| |
| if (owner_->client_) |
| owner_->client_->OnTransitionUserShelfToNewAccount(); |
| } |
| |
| void UserSwitchAnimator::TransitionWindows(AnimationStep animation_step) { |
| // Disable the window position manager and the MRU window tracker temporarily. |
| UserChangeActionDisabler disabler; |
| |
| // Animation duration. |
| base::TimeDelta duration = base::TimeDelta::FromMilliseconds( |
| std::max(kMinimalAnimationTime.InMilliseconds(), |
| 2 * animation_speed_.InMilliseconds())); |
| |
| switch (animation_step) { |
| case ANIMATION_STEP_HIDE_OLD_USER: { |
| // Hide the old users. |
| for (auto& user_pair : windows_by_account_id_) { |
| auto& show_for_account_id = user_pair.first; |
| if (show_for_account_id == new_account_id_) |
| continue; |
| |
| bool found_foreground_maximized_window = false; |
| |
| // We hide the windows such that the MRU window is the last one to be |
| // hidden, at which point all other windows have already been hidden, |
| // and hence the FocusController will not be able to find a next |
| // activateable window to restore focus to, and so we don't change |
| // window order (crbug.com/424307). |
| PutMruWindowLast(&(user_pair.second)); |
| for (auto* window : user_pair.second) { |
| // Minimized visiting windows (minimized windows with an owner |
| // different than that of the for_show_account_id) should retrun to |
| // their |
| // original owners' desktops. |
| MultiUserWindowManager::WindowToEntryMap::const_iterator itr = |
| owner_->window_to_entry().find(window); |
| DCHECK(itr != owner_->window_to_entry().end()); |
| if (show_for_account_id != itr->second->owner() && |
| wm::WindowStateIs(window, ui::SHOW_STATE_MINIMIZED)) { |
| owner_->ShowWindowForUserIntern(window, itr->second->owner()); |
| wm::Unminimize(window); |
| continue; |
| } |
| |
| if (!found_foreground_maximized_window && CoversScreen(window) && |
| screen_cover_ == BOTH_USERS_COVER_SCREEN) { |
| // Maximized windows should be hidden, but visually kept visible |
| // in order to prevent showing the background while the animation is |
| // in progress. Therefore we detach the old layer and recreate fresh |
| // ones. The old layers will be destructed at the animation step |
| // |ANIMATION_STEP_FINALIZE|. |
| // old_layers_.push_back(wm::RecreateLayers(window)); |
| // We only want to do this for the first (foreground) maximized |
| // window we encounter. |
| found_foreground_maximized_window = true; |
| std::unique_ptr<ui::LayerTreeOwner> old_layer = |
| wm::RecreateLayers(window); |
| window->layer()->parent()->StackAtBottom(old_layer->root()); |
| ui::ScopedLayerAnimationSettings settings( |
| window->layer()->GetAnimator()); |
| settings.AddObserver( |
| new MaximizedWindowAnimationWatcher(std::move(old_layer))); |
| // Call SetWindowVisibility() within the scope of |settings| so that |
| // MaximizedWindowAnimationWatcher is notified when the animation |
| // completes. |
| owner_->SetWindowVisibility(window, false, duration); |
| } else { |
| owner_->SetWindowVisibility(window, false, duration); |
| } |
| } |
| } |
| |
| // Show new user. |
| auto new_user_itr = windows_by_account_id_.find(new_account_id_); |
| if (new_user_itr == windows_by_account_id_.end()) |
| return; |
| |
| for (auto* window : new_user_itr->second) { |
| auto entry = owner_->window_to_entry().find(window); |
| DCHECK(entry != owner_->window_to_entry().end()); |
| |
| if (entry->second->show()) |
| owner_->SetWindowVisibility(window, true, duration); |
| } |
| |
| break; |
| } |
| case ANIMATION_STEP_SHOW_NEW_USER: { |
| // In order to make the animation look better, we had to move the code |
| // that shows the new user to the previous step. Hence, we do nothing |
| // here. |
| break; |
| } |
| case ANIMATION_STEP_FINALIZE: { |
| // Reactivate the MRU window of the new user. |
| aura::Window::Windows mru_list = |
| Shell::Get()->mru_window_tracker()->BuildMruWindowList(); |
| if (!mru_list.empty()) { |
| aura::Window* window = mru_list[0]; |
| if (owner_->IsWindowOnDesktopOfUser(window, new_account_id_) && |
| !wm::WindowStateIs(window, ui::SHOW_STATE_MINIMIZED)) { |
| // Several unit tests come here without an activation client. |
| wm::ActivationClient* client = |
| wm::GetActivationClient(window->GetRootWindow()); |
| if (client) |
| client->ActivateWindow(window); |
| } |
| } |
| |
| break; |
| } |
| case ANIMATION_STEP_ENDED: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| UserSwitchAnimator::TransitioningScreenCover UserSwitchAnimator::GetScreenCover( |
| aura::Window* root_window) { |
| TransitioningScreenCover cover = NO_USER_COVERS_SCREEN; |
| for (auto& pair : owner_->window_to_entry()) { |
| aura::Window* window = pair.first; |
| if (root_window && window->GetRootWindow() != root_window) |
| continue; |
| if (window->IsVisible() && CoversScreen(window)) { |
| if (cover == NEW_USER_COVERS_SCREEN) |
| return BOTH_USERS_COVER_SCREEN; |
| else |
| cover = OLD_USER_COVERS_SCREEN; |
| } else if (owner_->IsWindowOnDesktopOfUser(window, new_account_id_) && |
| CoversScreen(window)) { |
| if (cover == OLD_USER_COVERS_SCREEN) |
| return BOTH_USERS_COVER_SCREEN; |
| else |
| cover = NEW_USER_COVERS_SCREEN; |
| } |
| } |
| return cover; |
| } |
| |
| void UserSwitchAnimator::BuildUserToWindowsListMap() { |
| // This is to be called only at the time this animation is constructed. |
| DCHECK(windows_by_account_id_.empty()); |
| |
| // For each unique parent window, we enumerate its children windows, and |
| // for each child if it's in the |window_to_entry()| map, we add it to the |
| // |windows_by_account_id_| map. |
| // This gives us a list of windows per each user that is in the same order |
| // they were created in their parent windows. |
| std::set<aura::Window*> parent_windows; |
| auto& window_to_entry_map = owner_->window_to_entry(); |
| for (auto& window_entry_pair : window_to_entry_map) { |
| aura::Window* parent_window = window_entry_pair.first->parent(); |
| if (parent_windows.find(parent_window) == parent_windows.end()) { |
| parent_windows.insert(parent_window); |
| for (auto* child_window : parent_window->children()) { |
| auto itr = window_to_entry_map.find(child_window); |
| if (itr != window_to_entry_map.end()) { |
| windows_by_account_id_[itr->second->show_for_user()].push_back( |
| child_window); |
| } |
| } |
| } |
| } |
| } |
| |
| } // namespace ash |