blob: da07b77630d32122aff8dbeb1752bf1ae93a07a8 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/system/power/power_button_screenshot_controller.h"
#include "ash/accelerators/accelerator_controller_impl.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/shell.h"
#include "ash/system/power/power_button_controller.h"
#include "ash/wm/window_util.h"
#include "base/functional/bind.h"
#include "base/metrics/user_metrics.h"
#include "base/time/tick_clock.h"
#include "ui/display/screen.h"
#include "ui/events/event.h"
namespace ash {
namespace {
bool VolumeKeyMaybeUsedByApp() {
aura::Window* active = window_util::GetActiveWindow();
return active && active->GetProperty(ash::kCanConsumeSystemKeysKey);
}
} // namespace
constexpr base::TimeDelta
PowerButtonScreenshotController::kScreenshotChordDelay;
PowerButtonScreenshotController::PowerButtonScreenshotController(
const base::TickClock* tick_clock)
: tick_clock_(tick_clock) {
DCHECK(tick_clock_);
// Using prepend to make sure this event handler is put in front of
// AcceleratorFilter. See Shell::Init().
Shell::Get()->AddPreTargetHandler(this, ui::EventTarget::Priority::kSystem);
}
PowerButtonScreenshotController::~PowerButtonScreenshotController() {
Shell::Get()->RemovePreTargetHandler(this);
}
bool PowerButtonScreenshotController::OnPowerButtonEvent(
bool down,
const base::TimeTicks& timestamp) {
if (!display::Screen::GetScreen()->InTabletMode()) {
return false;
}
power_button_pressed_ = down;
if (power_button_pressed_) {
volume_down_timer_.Stop();
volume_up_timer_.Stop();
power_button_pressed_time_ = tick_clock_->NowTicks();
if (InterceptScreenshotChord())
return true;
}
if (!down)
return false;
// If volume key is pressed, mark power button as consumed. This invalidates
// other power button's behavior when user tries to operate screenshot.
return volume_down_key_pressed_ || volume_up_key_pressed_;
}
void PowerButtonScreenshotController::OnKeyEvent(ui::KeyEvent* event) {
if (!display::Screen::GetScreen()->InTabletMode()) {
return;
}
ui::KeyboardCode key_code = event->key_code();
if (key_code != ui::VKEY_VOLUME_DOWN && key_code != ui::VKEY_VOLUME_UP)
return;
bool did_consume_volume_keys =
volume_down_key_pressed_ || volume_up_key_pressed_;
bool volume_key_maybe_used_by_app = VolumeKeyMaybeUsedByApp();
// Even if the app is requesting to consume volume key, do not give it if
// 1) power button is already pressed. This should trigger screenshot.
// 2) if this is already handling volume key. We need to continue processing
// volume eve after power button is released, until volume keys are released.
if (volume_key_maybe_used_by_app && !power_button_pressed_ &&
!did_consume_volume_keys) {
return;
}
const bool is_volume_down = key_code == ui::VKEY_VOLUME_DOWN;
if (event->type() == ui::ET_KEY_PRESSED) {
if (!did_consume_volume_keys) {
if (is_volume_down) {
volume_down_key_pressed_ = true;
volume_down_key_pressed_time_ = tick_clock_->NowTicks();
consume_volume_down_ = false;
InterceptScreenshotChord();
} else {
volume_up_key_pressed_ = true;
volume_up_key_pressed_time_ = tick_clock_->NowTicks();
consume_volume_up_ = false;
InterceptScreenshotChord();
}
}
// Do no pass the event to the app if the events are being
// processed
if (consume_volume_down_ || consume_volume_up_ ||
volume_key_maybe_used_by_app) {
event->StopPropagation();
}
} else {
is_volume_down ? volume_down_key_pressed_ = false
: volume_up_key_pressed_ = false;
}
// When volume key is pressed, cancel the ongoing power button behavior.
if (volume_down_key_pressed_ || volume_up_key_pressed_)
Shell::Get()->power_button_controller()->CancelPowerButtonEvent();
// On volume down/up key pressed while power button not pressed yet state, do
// not propagate volume down/up key pressed event for chord delay time. Start
// the timer to wait power button pressed for screenshot operation, and on
// timeout perform the delayed volume down/up operation.
if (power_button_pressed_)
return;
base::TimeTicks now = tick_clock_->NowTicks();
if (volume_down_key_pressed_ && is_volume_down &&
now <= volume_down_key_pressed_time_ + kScreenshotChordDelay) {
event->StopPropagation();
if (!volume_down_timer_.IsRunning()) {
volume_down_timer_.Start(
FROM_HERE, kScreenshotChordDelay,
base::BindOnce(
&PowerButtonScreenshotController::OnVolumeControlTimeout,
base::Unretained(this), ui::Accelerator(*event), /*down=*/true));
}
} else if (volume_up_key_pressed_ && !is_volume_down &&
now <= volume_up_key_pressed_time_ + kScreenshotChordDelay) {
event->StopPropagation();
if (!volume_up_timer_.IsRunning()) {
volume_up_timer_.Start(
FROM_HERE, kScreenshotChordDelay,
base::BindOnce(
&PowerButtonScreenshotController::OnVolumeControlTimeout,
base::Unretained(this), ui::Accelerator(*event), /*down=*/false));
}
}
}
bool PowerButtonScreenshotController::InterceptScreenshotChord() {
if (!power_button_pressed_ ||
(!volume_down_key_pressed_ && !volume_up_key_pressed_)) {
return false;
}
base::TimeTicks now = tick_clock_->NowTicks();
if (now > power_button_pressed_time_ + kScreenshotChordDelay)
return false;
consume_volume_down_ =
volume_down_key_pressed_ &&
now <= volume_down_key_pressed_time_ + kScreenshotChordDelay;
consume_volume_up_ =
volume_up_key_pressed_ &&
now <= volume_up_key_pressed_time_ + kScreenshotChordDelay;
if (consume_volume_down_ || consume_volume_up_) {
Shell::Get()->accelerator_controller()->PerformActionIfEnabled(
AcceleratorAction::kTakeScreenshot, {});
base::RecordAction(base::UserMetricsAction("Accel_PowerButton_Screenshot"));
}
return consume_volume_down_ || consume_volume_up_;
}
void PowerButtonScreenshotController::OnVolumeControlTimeout(
const ui::Accelerator& accelerator,
bool down) {
Shell::Get()->accelerator_controller()->PerformActionIfEnabled(
down ? AcceleratorAction::kVolumeDown : AcceleratorAction::kVolumeUp,
accelerator);
}
} // namespace ash