blob: 82ce2f822a278710fb85ccb7e5630799c905450a [file] [log] [blame]
// 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 "chrome/browser/ui/ash/multi_user/user_switch_animator_chromeos.h"
#include "ash/desktop_background/user_wallpaper_delegate.h"
#include "ash/root_window_controller.h"
#include "ash/shelf/shelf_layout_manager.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/window_positioner.h"
#include "ash/wm/window_state.h"
#include "chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h"
#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
#include "chrome/browser/ui/ash/multi_user/multi_user_notification_blocker_chromeos.h"
#include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_chromeos.h"
#include "ui/wm/public/activation_client.h"
namespace chrome {
namespace {
// The minimal possible animation time for animations which should happen
// "instantly".
const int kMinimalAnimationTimeMS = 1;
// logic while the user gets switched.
class UserChangeActionDisabler {
public:
UserChangeActionDisabler() {
ash::WindowPositioner::DisableAutoPositioning(true);
ash::Shell::GetInstance()->mru_window_tracker()->SetIgnoreActivations(true);
}
~UserChangeActionDisabler() {
ash::WindowPositioner::DisableAutoPositioning(false);
ash::Shell::GetInstance()->mru_window_tracker()->SetIgnoreActivations(
false);
}
private:
DISALLOW_COPY_AND_ASSIGN(UserChangeActionDisabler);
};
} // namespace
UserSwichAnimatorChromeOS::UserSwichAnimatorChromeOS(
MultiUserWindowManagerChromeOS* owner,
const std::string& new_user_id,
int animation_speed_ms)
: owner_(owner),
new_user_id_(new_user_id),
animation_speed_ms_(animation_speed_ms),
animation_step_(ANIMATION_STEP_HIDE_OLD_USER),
screen_cover_(GetScreenCover(NULL)) {
AdvanceUserTransitionAnimation();
if (!animation_speed_ms_) {
FinalizeAnimation();
} else {
user_changed_animation_timer_.reset(new base::Timer(
FROM_HERE,
base::TimeDelta::FromMilliseconds(animation_speed_ms_),
base::Bind(
&UserSwichAnimatorChromeOS::AdvanceUserTransitionAnimation,
base::Unretained(this)),
true));
user_changed_animation_timer_->Reset();
}
}
UserSwichAnimatorChromeOS::~UserSwichAnimatorChromeOS() {
FinalizeAnimation();
}
// static
bool UserSwichAnimatorChromeOS::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 (ash::wm::GetWindowState(window)->IsFullscreen())
return true;
gfx::Rect bounds = window->GetBoundsInRootWindow();
gfx::Rect work_area = gfx::Screen::GetScreenFor(window)->
GetDisplayNearestWindow(window).work_area();
bounds.Intersect(work_area);
return work_area == bounds;
}
void UserSwichAnimatorChromeOS::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;
break;
case ANIMATION_STEP_ENDED:
NOTREACHED();
break;
}
}
void UserSwichAnimatorChromeOS::CancelAnimation() {
animation_step_ = ANIMATION_STEP_ENDED;
}
void UserSwichAnimatorChromeOS::FinalizeAnimation() {
user_changed_animation_timer_.reset();
while (ANIMATION_STEP_ENDED != animation_step_)
AdvanceUserTransitionAnimation();
}
void UserSwichAnimatorChromeOS::TransitionWallpaper(
AnimationStep animation_step) {
// Handle the wallpaper switch.
ash::UserWallpaperDelegate* wallpaper_delegate =
ash::Shell::GetInstance()->user_wallpaper_delegate();
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.
int duration =
NO_USER_COVERS_SCREEN == screen_cover_ ? (2 * animation_speed_ms_) : 0;
wallpaper_delegate->SetAnimationDurationOverride(
std::max(duration, kMinimalAnimationTimeMS));
if (screen_cover_ != NEW_USER_COVERS_SCREEN) {
chromeos::WallpaperManager::Get()->SetUserWallpaperNow(new_user_id_);
wallpaper_user_id_ =
(NO_USER_COVERS_SCREEN == screen_cover_ ? "->" : "") +
new_user_id_;
}
} 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)
chromeos::WallpaperManager::Get()->SetUserWallpaperNow(new_user_id_);
// Coming here the wallpaper user id is the final result. No matter how we
// got here.
wallpaper_user_id_ = new_user_id_;
wallpaper_delegate->SetAnimationDurationOverride(0);
}
}
void UserSwichAnimatorChromeOS::TransitionUserShelf(
AnimationStep animation_step) {
ChromeLauncherController* chrome_launcher_controller =
ChromeLauncherController::instance();
// The shelf animation duration override.
int duration_override = animation_speed_ms_;
// Handle the shelf order of items. This is done once the old user is hidden.
if (animation_step == ANIMATION_STEP_SHOW_NEW_USER) {
// Some unit tests have no ChromeLauncherController.
if (chrome_launcher_controller)
chrome_launcher_controller->ActiveUserChanged(new_user_id_);
// Hide the black rectangle on top of each shelf again.
aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows();
for (aura::Window::Windows::const_iterator iter = root_windows.begin();
iter != root_windows.end(); ++iter) {
ash::ShelfWidget* shelf =
ash::RootWindowController::ForWindow(*iter)->shelf();
shelf->HideShelfBehindBlackBar(false, duration_override);
}
// We kicked off the shelf animation above and the override can be
// removed.
duration_override = 0;
}
if (!animation_speed_ms_ || animation_step == ANIMATION_STEP_FINALIZE)
return;
// Note: The animation duration override will be set before the old user gets
// hidden and reset after the animations for the new user got kicked off.
ash::Shell::RootWindowControllerList controller =
ash::Shell::GetInstance()->GetAllRootWindowControllers();
for (ash::Shell::RootWindowControllerList::iterator iter = controller.begin();
iter != controller.end(); ++iter) {
(*iter)->GetShelfLayoutManager()->SetAnimationDurationOverride(
duration_override);
}
if (animation_step != ANIMATION_STEP_HIDE_OLD_USER)
return;
// For each root window hide the shelf.
aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows();
for (aura::Window::Windows::const_iterator iter = root_windows.begin();
iter != root_windows.end(); ++iter) {
// Hiding the shelf will cause a resize on a maximized window.
// If the shelf is then shown for the following user in the same location,
// the window gets resized again. Since each resize can cause a considerable
// CPU usage and therefore effect jank, we should avoid hiding the shelf if
// the start and end location are the same and cover the shelf instead with
// a black rectangle on top.
if (GetScreenCover(*iter) != NO_USER_COVERS_SCREEN &&
(!chrome_launcher_controller ||
!chrome_launcher_controller->ShelfBoundsChangesProbablyWithUser(
*iter, new_user_id_))) {
ash::ShelfWidget* shelf =
ash::RootWindowController::ForWindow(*iter)->shelf();
shelf->HideShelfBehindBlackBar(true, duration_override);
} else {
// This shelf change is only part of the animation and will be updated by
// ChromeLauncherController::ActiveUserChanged() to the new users value.
// Note that the user preference will not be changed.
ash::Shell::GetInstance()->SetShelfAutoHideBehavior(
ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN, *iter);
}
}
}
void UserSwichAnimatorChromeOS::TransitionWindows(
AnimationStep animation_step) {
// Disable the window position manager and the MRU window tracker temporarily.
UserChangeActionDisabler disabler;
if (animation_step == ANIMATION_STEP_HIDE_OLD_USER ||
(animation_step == ANIMATION_STEP_FINALIZE &&
screen_cover_ == BOTH_USERS_COVER_SCREEN)) {
// We need to show/hide the windows in the same order as they were created
// in their parent window(s) to keep the layer / window hierarchy in sync.
// To achieve that we first collect all parent windows and then enumerate
// all windows in those parent windows and show or hide them accordingly.
// Create a list of all parent windows we have to check.
std::set<aura::Window*> parent_list;
for (MultiUserWindowManagerChromeOS::WindowToEntryMap::const_iterator it =
owner_->window_to_entry().begin();
it != owner_->window_to_entry().end(); ++it) {
aura::Window* parent = it->first->parent();
if (parent_list.find(parent) == parent_list.end())
parent_list.insert(parent);
}
for (std::set<aura::Window*>::iterator it_parents = parent_list.begin();
it_parents != parent_list.end(); ++it_parents) {
const aura::Window::Windows window_list = (*it_parents)->children();
// In case of |BOTH_USERS_COVER_SCREEN| the desktop might shine through
// if all windows fade (in or out). To avoid this we only fade the topmost
// covering window (in / out) and make / keep all other covering windows
// visible while animating. |foreground_window_found| will get set when
// the top fading window was found.
bool foreground_window_found = false;
// Covering windows which follow the fade direction will also fade - all
// others will get immediately shown / kept shown until the animation is
// finished.
bool foreground_becomes_visible = false;
for (aura::Window::Windows::const_reverse_iterator it_window =
window_list.rbegin();
it_window != window_list.rend(); ++it_window) {
aura::Window* window = *it_window;
MultiUserWindowManagerChromeOS::WindowToEntryMap::const_iterator
it_map = owner_->window_to_entry().find(window);
if (it_map != owner_->window_to_entry().end()) {
bool should_be_visible =
it_map->second->show_for_user() == new_user_id_ &&
it_map->second->show();
bool is_visible = window->IsVisible();
ash::wm::WindowState* window_state = ash::wm::GetWindowState(window);
if (it_map->second->owner() == new_user_id_ &&
it_map->second->show_for_user() != new_user_id_ &&
window_state->IsMinimized()) {
// Pull back minimized visiting windows to the owners desktop.
owner_->ShowWindowForUserIntern(window, new_user_id_);
window_state->Unminimize();
} else if (should_be_visible != is_visible) {
bool animate = true;
int duration = animation_step == ANIMATION_STEP_FINALIZE ?
0 : (2 * animation_speed_ms_);
duration = std::max(kMinimalAnimationTimeMS, duration);
if (animation_step != ANIMATION_STEP_FINALIZE &&
screen_cover_ == BOTH_USERS_COVER_SCREEN &&
CoversScreen(window)) {
if (!foreground_window_found) {
foreground_window_found = true;
foreground_becomes_visible = should_be_visible;
} else if (should_be_visible != foreground_becomes_visible) {
// Covering windows behind the foreground window which are
// inverting their visibility should immediately become visible
// or stay visible until the animation is finished.
duration = kMinimalAnimationTimeMS;
if (!should_be_visible)
animate = false;
}
}
if (animate)
owner_->SetWindowVisibility(window, should_be_visible, duration);
}
}
}
}
}
// Activation and real switch are happening after the other user gets shown.
if (animation_step == ANIMATION_STEP_SHOW_NEW_USER) {
// Finally we need to restore the previously active window.
ash::MruWindowTracker::WindowList mru_list =
ash::Shell::GetInstance()->mru_window_tracker()->BuildMruWindowList();
if (!mru_list.empty()) {
aura::Window* window = mru_list[0];
ash::wm::WindowState* window_state = ash::wm::GetWindowState(window);
if (owner_->IsWindowOnDesktopOfUser(window, new_user_id_) &&
!window_state->IsMinimized()) {
aura::client::ActivationClient* client =
aura::client::GetActivationClient(window->GetRootWindow());
// Several unit tests come here without an activation client.
if (client)
client->ActivateWindow(window);
}
}
// This is called directly here to make sure notification_blocker will see
// the new window status.
owner_->notification_blocker()->ActiveUserChanged(new_user_id_);
}
}
UserSwichAnimatorChromeOS::TransitioningScreenCover
UserSwichAnimatorChromeOS::GetScreenCover(aura::Window* root_window) {
TransitioningScreenCover cover = NO_USER_COVERS_SCREEN;
for (MultiUserWindowManagerChromeOS::WindowToEntryMap::const_iterator it_map =
owner_->window_to_entry().begin();
it_map != owner_->window_to_entry().end();
++it_map) {
aura::Window* window = it_map->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_user_id_) &&
CoversScreen(window)) {
if (cover == OLD_USER_COVERS_SCREEN)
return BOTH_USERS_COVER_SCREEN;
else
cover = NEW_USER_COVERS_SCREEN;
}
}
return cover;
}
} // namespace chrome