blob: 621b24a7fd7a47e937f2678ce88982e12400afdc [file] [log] [blame]
// Copyright (c) 2012 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/wm/power_button_controller.h"
#include "ash/ash_switches.h"
#include "ash/shell.h"
#include "ash/shell_delegate.h"
#include "ash/shell_window_ids.h"
#include "ash/wm/cursor_manager.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/time.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/aura/env.h"
#include "ui/aura/root_window.h"
#include "ui/aura/shared/compound_event_filter.h"
#include "ui/aura/window.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_element.h"
#include "ui/compositor/layer_animation_sequence.h"
#include "ui/compositor/layer_animator.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/size.h"
#include "ui/gfx/transform.h"
namespace ash {
namespace {
// Amount of time that the power button needs to be held before we lock the
// screen.
const int kLockTimeoutMs = 400;
// Amount of time that the power button needs to be held before we shut down.
const int kShutdownTimeoutMs = 400;
// Amount of time to wait for our lock requests to be honored before giving up.
const int kLockFailTimeoutMs = 4000;
// When the button has been held continuously from the unlocked state, amount of
// time that we wait after the screen locker window is shown before starting the
// pre-shutdown animation.
const int kLockToShutdownTimeoutMs = 150;
// Amount of time taken to scale the snapshot of the screen down to a
// slightly-smaller size once the user starts holding the power button. Used
// for both the pre-lock and pre-shutdown animations.
const int kSlowCloseAnimMs = 400;
// Amount of time taken to scale the snapshot of the screen back to its original
// size when the button is released.
const int kUndoSlowCloseAnimMs = 100;
// Amount of time taken to scale the snapshot down to a point in the center of
// the screen once the screen has been locked or we've been notified that the
// system is shutting down.
const int kFastCloseAnimMs = 150;
// Amount of time taken to make the lock window fade in when the screen is
// locked.
const int kLockFadeInAnimMs = 200;
// Additional time (beyond kFastCloseAnimMs) to wait after starting the
// fast-close shutdown animation before actually requesting shutdown, to give
// the animation time to finish.
const int kShutdownRequestDelayMs = 50;
// Slightly-smaller size that we scale the screen down to for the pre-lock and
// pre-shutdown states.
const float kSlowCloseSizeRatio = 0.95f;
// Returns the transform that should be applied to containers for the slow-close
// animation.
ui::Transform GetSlowCloseTransform() {
gfx::Size root_size = Shell::GetPrimaryRootWindow()->bounds().size();
ui::Transform transform;
transform.SetScale(kSlowCloseSizeRatio, kSlowCloseSizeRatio);
transform.ConcatTranslate(
floor(0.5 * (1.0 - kSlowCloseSizeRatio) * root_size.width() + 0.5),
floor(0.5 * (1.0 - kSlowCloseSizeRatio) * root_size.height() + 0.5));
return transform;
}
// Returns the transform that should be applied to containers for the fast-close
// animation.
ui::Transform GetFastCloseTransform() {
gfx::Size root_size = Shell::GetPrimaryRootWindow()->bounds().size();
ui::Transform transform;
transform.SetScale(0.0, 0.0);
transform.ConcatTranslate(floor(0.5 * root_size.width() + 0.5),
floor(0.5 * root_size.height() + 0.5));
return transform;
}
// Slowly shrinks |window| to a slightly-smaller size.
void StartSlowCloseAnimationForWindow(aura::Window* window) {
ui::LayerAnimator* animator = window->layer()->GetAnimator();
animator->set_preemption_strategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
animator->StartAnimation(
new ui::LayerAnimationSequence(
ui::LayerAnimationElement::CreateTransformElement(
GetSlowCloseTransform(),
base::TimeDelta::FromMilliseconds(kSlowCloseAnimMs))));
}
// Quickly undoes the effects of the slow-close animation on |window|.
void StartUndoSlowCloseAnimationForWindow(aura::Window* window) {
ui::LayerAnimator* animator = window->layer()->GetAnimator();
animator->set_preemption_strategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
animator->StartAnimation(
new ui::LayerAnimationSequence(
ui::LayerAnimationElement::CreateTransformElement(
ui::Transform(),
base::TimeDelta::FromMilliseconds(kUndoSlowCloseAnimMs))));
}
// Quickly shrinks |window| down to a point in the center of the screen and
// fades it out to 0 opacity.
void StartFastCloseAnimationForWindow(aura::Window* window) {
ui::LayerAnimator* animator = window->layer()->GetAnimator();
animator->set_preemption_strategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
animator->StartAnimation(
new ui::LayerAnimationSequence(
ui::LayerAnimationElement::CreateTransformElement(
GetFastCloseTransform(),
base::TimeDelta::FromMilliseconds(kFastCloseAnimMs))));
animator->StartAnimation(
new ui::LayerAnimationSequence(
ui::LayerAnimationElement::CreateOpacityElement(
0.0, base::TimeDelta::FromMilliseconds(kFastCloseAnimMs))));
}
// Fades |window| in to full opacity.
void FadeInWindow(aura::Window* window) {
ui::LayerAnimator* animator = window->layer()->GetAnimator();
animator->set_preemption_strategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
animator->StartAnimation(
new ui::LayerAnimationSequence(
ui::LayerAnimationElement::CreateOpacityElement(
1.0, base::TimeDelta::FromMilliseconds(kLockFadeInAnimMs))));
}
// Makes |window| fully transparent instantaneously.
void HideWindow(aura::Window* window) {
window->layer()->SetOpacity(0.0);
}
// Restores |window| to its original position and scale and full opacity
// instantaneously.
void RestoreWindow(aura::Window* window) {
window->layer()->SetTransform(ui::Transform());
window->layer()->SetOpacity(1.0);
}
// Fills |containers| with the containers described by |container_mask|.
void GetContainers(int container_mask, aura::Window::Windows* containers) {
aura::RootWindow* root_window = Shell::GetPrimaryRootWindow();
containers->clear();
if (container_mask & PowerButtonController::DESKTOP_BACKGROUND) {
containers->push_back(Shell::GetContainer(
root_window,
internal::kShellWindowId_DesktopBackgroundContainer));
}
if (container_mask & PowerButtonController::NON_LOCK_SCREEN_CONTAINERS) {
containers->push_back(Shell::GetContainer(
root_window,
internal::kShellWindowId_NonLockScreenContainersContainer));
}
if (container_mask & PowerButtonController::LOCK_SCREEN_BACKGROUND) {
containers->push_back(Shell::GetContainer(
root_window,
internal::kShellWindowId_LockScreenBackgroundContainer));
}
if (container_mask & PowerButtonController::LOCK_SCREEN_CONTAINERS) {
containers->push_back(Shell::GetContainer(
root_window,
internal::kShellWindowId_LockScreenContainersContainer));
}
if (container_mask & PowerButtonController::LOCK_SCREEN_RELATED_CONTAINERS) {
containers->push_back(Shell::GetContainer(
root_window,
internal::kShellWindowId_LockScreenRelatedContainersContainer));
}
}
// Apply animation |type| to all containers described by |container_mask|.
void StartAnimation(int container_mask,
PowerButtonController::AnimationType type) {
aura::Window::Windows containers;
GetContainers(container_mask, &containers);
for (aura::Window::Windows::const_iterator it = containers.begin();
it != containers.end(); ++it) {
aura::Window* window = *it;
switch (type) {
case PowerButtonController::ANIMATION_SLOW_CLOSE:
StartSlowCloseAnimationForWindow(window);
break;
case PowerButtonController::ANIMATION_UNDO_SLOW_CLOSE:
StartUndoSlowCloseAnimationForWindow(window);
break;
case PowerButtonController::ANIMATION_FAST_CLOSE:
StartFastCloseAnimationForWindow(window);
break;
case PowerButtonController::ANIMATION_FADE_IN:
FadeInWindow(window);
break;
case PowerButtonController::ANIMATION_HIDE:
HideWindow(window);
break;
case PowerButtonController::ANIMATION_RESTORE:
RestoreWindow(window);
break;
default:
NOTREACHED() << "Unhandled animation type " << type;
}
}
}
} // namespace
bool PowerButtonController::TestApi::ContainersAreAnimated(
int container_mask, AnimationType type) const {
aura::Window::Windows containers;
GetContainers(container_mask, &containers);
for (aura::Window::Windows::const_iterator it = containers.begin();
it != containers.end(); ++it) {
aura::Window* window = *it;
ui::Layer* layer = window->layer();
switch (type) {
case PowerButtonController::ANIMATION_SLOW_CLOSE:
if (layer->GetTargetTransform() != GetSlowCloseTransform())
return false;
break;
case PowerButtonController::ANIMATION_UNDO_SLOW_CLOSE:
if (layer->GetTargetTransform() != ui::Transform())
return false;
break;
case PowerButtonController::ANIMATION_FAST_CLOSE:
if (layer->GetTargetTransform() != GetFastCloseTransform() ||
layer->GetTargetOpacity() > 0.0001)
return false;
break;
case PowerButtonController::ANIMATION_FADE_IN:
if (layer->GetTargetOpacity() < 0.9999)
return false;
break;
case PowerButtonController::ANIMATION_HIDE:
if (layer->GetTargetOpacity() > 0.0001)
return false;
break;
case PowerButtonController::ANIMATION_RESTORE:
if (layer->opacity() < 0.9999 || layer->transform() != ui::Transform())
return false;
break;
default:
NOTREACHED() << "Unhandled animation type " << type;
return false;
}
}
return true;
}
bool PowerButtonController::TestApi::BlackLayerIsVisible() const {
return controller_->black_layer_.get() &&
controller_->black_layer_->visible();
}
gfx::Rect PowerButtonController::TestApi::GetBlackLayerBounds() const {
ui::Layer* layer = controller_->black_layer_.get();
return layer ? layer->bounds() : gfx::Rect();
}
// static
int PowerButtonController::GetAllContainersMask() {
return PowerButtonController::DESKTOP_BACKGROUND |
PowerButtonController::NON_LOCK_SCREEN_CONTAINERS |
GetAllLockScreenContainersMask();
}
// static
int PowerButtonController::GetAllLockScreenContainersMask() {
return PowerButtonController::LOCK_SCREEN_BACKGROUND |
PowerButtonController::LOCK_SCREEN_CONTAINERS |
PowerButtonController::LOCK_SCREEN_RELATED_CONTAINERS;
}
PowerButtonController::PowerButtonController()
: login_status_(user::LOGGED_IN_NONE),
unlocked_login_status_(user::LOGGED_IN_NONE),
power_button_down_(false),
lock_button_down_(false),
screen_is_off_(false),
shutting_down_(false),
has_legacy_power_button_(
CommandLine::ForCurrentProcess()->HasSwitch(
switches::kAuraLegacyPowerButton)) {
Shell::GetPrimaryRootWindow()->AddRootWindowObserver(this);
}
PowerButtonController::~PowerButtonController() {
Shell::GetPrimaryRootWindow()->RemoveRootWindowObserver(this);
}
void PowerButtonController::OnLoginStateChanged(user::LoginStatus status) {
login_status_ = status;
unlocked_login_status_ = user::LOGGED_IN_NONE;
}
void PowerButtonController::OnAppTerminating() {
// If we hear that Chrome is exiting but didn't request it ourselves, all we
// can really hope for is that we'll have time to clear the screen.
if (!shutting_down_) {
shutting_down_ = true;
Shell* shell = ash::Shell::GetInstance();
shell->cursor_manager()->ShowCursor(false);
ShowBlackLayer();
StartAnimation(GetAllContainersMask(), ANIMATION_HIDE);
}
}
void PowerButtonController::OnLockStateChanged(bool locked) {
if (shutting_down_ || (login_status_ == user::LOGGED_IN_LOCKED) == locked)
return;
if (!locked && login_status_ == user::LOGGED_IN_LOCKED) {
login_status_ = unlocked_login_status_;
unlocked_login_status_ = user::LOGGED_IN_NONE;
} else {
unlocked_login_status_ = login_status_;
login_status_ = user::LOGGED_IN_LOCKED;
}
if (locked) {
StartAnimation(LOCK_SCREEN_CONTAINERS, ANIMATION_FADE_IN);
lock_timer_.Stop();
lock_fail_timer_.Stop();
if (!has_legacy_power_button_ && power_button_down_) {
lock_to_shutdown_timer_.Stop();
lock_to_shutdown_timer_.Start(
FROM_HERE,
base::TimeDelta::FromMilliseconds(kLockToShutdownTimeoutMs),
this, &PowerButtonController::OnLockToShutdownTimeout);
}
} else {
StartAnimation(DESKTOP_BACKGROUND | NON_LOCK_SCREEN_CONTAINERS,
ANIMATION_RESTORE);
HideBlackLayer();
}
}
void PowerButtonController::OnScreenBrightnessChanged(double percent) {
screen_is_off_ = percent <= 0.001;
}
void PowerButtonController::OnStartingLock() {
if (shutting_down_ || login_status_ == user::LOGGED_IN_LOCKED)
return;
// Ensure that the black layer is visible -- if the screen was locked via
// the wrench menu, we won't have already shown the black background
// as part of the slow-close animation.
ShowBlackLayer();
StartAnimation(NON_LOCK_SCREEN_CONTAINERS, ANIMATION_FAST_CLOSE);
// Hide the screen locker containers so we can make them fade in later.
StartAnimation(LOCK_SCREEN_CONTAINERS, ANIMATION_HIDE);
}
void PowerButtonController::OnPowerButtonEvent(
bool down, const base::TimeTicks& timestamp) {
power_button_down_ = down;
if (shutting_down_)
return;
// Avoid starting the lock/shutdown sequence if the power button is pressed
// while the screen is off (http://crbug.com/128451).
if (screen_is_off_)
return;
if (has_legacy_power_button_) {
// If power button releases won't get reported correctly because we're not
// running on official hardware, just lock the screen or shut down
// immediately.
if (down) {
ShowBlackLayer();
if (LoggedInAsNonGuest() && login_status_ != user::LOGGED_IN_LOCKED) {
StartAnimation(NON_LOCK_SCREEN_CONTAINERS, ANIMATION_SLOW_CLOSE);
OnLockTimeout();
} else {
OnShutdownTimeout();
}
}
} else { // !has_legacy_power_button_
if (down) {
// If we already have a pending request to lock the screen, wait.
if (lock_fail_timer_.IsRunning())
return;
if (LoggedInAsNonGuest() && login_status_ != user::LOGGED_IN_LOCKED)
StartLockTimer();
else
StartShutdownTimer();
} else { // Button is up.
if (lock_timer_.IsRunning() || shutdown_timer_.IsRunning()) {
if (login_status_ == user::LOGGED_IN_LOCKED) {
// If we've already started shutdown transition at lock screen
// desktop background needs to be restored immediately.
StartAnimation(DESKTOP_BACKGROUND, ANIMATION_RESTORE);
}
StartAnimation(
(login_status_ == user::LOGGED_IN_LOCKED) ?
GetAllLockScreenContainersMask() : GetAllContainersMask(),
ANIMATION_UNDO_SLOW_CLOSE);
}
// Drop the black layer after the undo animation finishes.
if (lock_timer_.IsRunning() ||
(shutdown_timer_.IsRunning() && !LoggedInAsNonGuest())) {
hide_black_layer_timer_.Stop();
hide_black_layer_timer_.Start(
FROM_HERE,
base::TimeDelta::FromMilliseconds(kUndoSlowCloseAnimMs),
this, &PowerButtonController::HideBlackLayer);
}
lock_timer_.Stop();
shutdown_timer_.Stop();
lock_to_shutdown_timer_.Stop();
}
}
}
void PowerButtonController::OnLockButtonEvent(
bool down, const base::TimeTicks& timestamp) {
lock_button_down_ = down;
if (shutting_down_ || !LoggedInAsNonGuest())
return;
// Bail if we're already locked or are in the process of locking. Also give
// the power button precedence over the lock button (we don't expect both
// buttons to be present, so this is just making sure that we don't do
// something completely stupid if that assumption changes later).
if (login_status_ == user::LOGGED_IN_LOCKED ||
lock_fail_timer_.IsRunning() || power_button_down_)
return;
if (down) {
StartLockTimer();
} else {
if (lock_timer_.IsRunning()) {
StartAnimation(NON_LOCK_SCREEN_CONTAINERS, ANIMATION_UNDO_SLOW_CLOSE);
hide_black_layer_timer_.Stop();
hide_black_layer_timer_.Start(
FROM_HERE,
base::TimeDelta::FromMilliseconds(kUndoSlowCloseAnimMs),
this, &PowerButtonController::HideBlackLayer);
lock_timer_.Stop();
}
}
}
void PowerButtonController::RequestShutdown() {
if (!shutting_down_)
StartShutdownAnimationAndRequestShutdown();
}
void PowerButtonController::OnRootWindowResized(const aura::RootWindow* root,
const gfx::Size& new_size) {
if (black_layer_.get())
black_layer_->SetBounds(gfx::Rect(root->bounds().size()));
}
void PowerButtonController::OnRootWindowHostCloseRequested(
const aura::RootWindow*) {
if(Shell::GetInstance() && Shell::GetInstance()->delegate())
Shell::GetInstance()->delegate()->Exit();
}
bool PowerButtonController::LoggedInAsNonGuest() const {
if (login_status_ == user::LOGGED_IN_NONE)
return false;
if (login_status_ == user::LOGGED_IN_GUEST)
return false;
// TODO(mukai): think about kiosk mode.
return true;
}
void PowerButtonController::OnLockTimeout() {
delegate_->RequestLockScreen();
lock_fail_timer_.Start(
FROM_HERE,
base::TimeDelta::FromMilliseconds(kLockFailTimeoutMs),
this, &PowerButtonController::OnLockFailTimeout);
}
void PowerButtonController::OnLockFailTimeout() {
DCHECK_NE(login_status_, user::LOGGED_IN_LOCKED);
LOG(ERROR) << "Screen lock request timed out";
StartAnimation(NON_LOCK_SCREEN_CONTAINERS, ANIMATION_RESTORE);
HideBlackLayer();
}
void PowerButtonController::OnLockToShutdownTimeout() {
DCHECK_EQ(login_status_, user::LOGGED_IN_LOCKED);
StartShutdownTimer();
}
void PowerButtonController::OnShutdownTimeout() {
if (!shutting_down_)
StartShutdownAnimationAndRequestShutdown();
}
void PowerButtonController::OnRealShutdownTimeout() {
DCHECK(shutting_down_);
delegate_->RequestShutdown();
}
void PowerButtonController::StartLockTimer() {
ShowBlackLayer();
StartAnimation(NON_LOCK_SCREEN_CONTAINERS, ANIMATION_SLOW_CLOSE);
lock_timer_.Stop();
lock_timer_.Start(FROM_HERE,
base::TimeDelta::FromMilliseconds(kSlowCloseAnimMs),
this, &PowerButtonController::OnLockTimeout);
}
void PowerButtonController::StartShutdownTimer() {
ShowBlackLayer();
StartAnimation(GetAllContainersMask(), ANIMATION_SLOW_CLOSE);
shutdown_timer_.Stop();
shutdown_timer_.Start(
FROM_HERE,
base::TimeDelta::FromMilliseconds(kShutdownTimeoutMs),
this, &PowerButtonController::OnShutdownTimeout);
}
void PowerButtonController::StartShutdownAnimationAndRequestShutdown() {
DCHECK(!shutting_down_);
shutting_down_ = true;
Shell* shell = ash::Shell::GetInstance();
shell->cursor_manager()->ShowCursor(false);
ShowBlackLayer();
if (login_status_ != user::LOGGED_IN_NONE) {
// Hide the other containers before starting the animation.
// ANIMATION_FAST_CLOSE will make the screen locker windows partially
// transparent, and we don't want the other windows to show through.
StartAnimation(NON_LOCK_SCREEN_CONTAINERS, ANIMATION_HIDE);
StartAnimation(GetAllLockScreenContainersMask(), ANIMATION_FAST_CLOSE);
} else {
StartAnimation(GetAllContainersMask(), ANIMATION_FAST_CLOSE);
}
real_shutdown_timer_.Start(
FROM_HERE,
base::TimeDelta::FromMilliseconds(
kFastCloseAnimMs + kShutdownRequestDelayMs),
this, &PowerButtonController::OnRealShutdownTimeout);
}
void PowerButtonController::ShowBlackLayer() {
if (hide_black_layer_timer_.IsRunning())
hide_black_layer_timer_.Stop();
if (!black_layer_.get()) {
black_layer_.reset(new ui::Layer(ui::LAYER_SOLID_COLOR));
black_layer_->SetColor(SK_ColorBLACK);
ui::Layer* root_layer = Shell::GetPrimaryRootWindow()->layer();
black_layer_->SetBounds(root_layer->bounds());
root_layer->Add(black_layer_.get());
root_layer->StackAtBottom(black_layer_.get());
}
black_layer_->SetVisible(true);
}
void PowerButtonController::HideBlackLayer() {
black_layer_.reset();
}
} // namespace ash