power_button_screenshot_controller.cc 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. // Copyright 2017 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.
  4. #include "ash/system/power/power_button_screenshot_controller.h"
  5. #include "ash/accelerators/accelerator_controller_impl.h"
  6. #include "ash/public/cpp/window_properties.h"
  7. #include "ash/shell.h"
  8. #include "ash/system/power/power_button_controller.h"
  9. #include "ash/wm/tablet_mode/tablet_mode_controller.h"
  10. #include "ash/wm/window_util.h"
  11. #include "base/bind.h"
  12. #include "base/metrics/histogram_macros.h"
  13. #include "base/metrics/user_metrics.h"
  14. #include "base/time/tick_clock.h"
  15. #include "ui/events/event.h"
  16. namespace ash {
  17. namespace {
  18. bool IsTabletMode() {
  19. return Shell::Get()->tablet_mode_controller()->InTabletMode();
  20. }
  21. bool VolumeKeyMaybeUsedByApp() {
  22. aura::Window* active = window_util::GetActiveWindow();
  23. return active && active->GetProperty(ash::kCanConsumeSystemKeysKey);
  24. }
  25. } // namespace
  26. constexpr base::TimeDelta
  27. PowerButtonScreenshotController::kScreenshotChordDelay;
  28. PowerButtonScreenshotController::PowerButtonScreenshotController(
  29. const base::TickClock* tick_clock)
  30. : tick_clock_(tick_clock) {
  31. DCHECK(tick_clock_);
  32. // Using prepend to make sure this event handler is put in front of
  33. // AcceleratorFilter. See Shell::Init().
  34. Shell::Get()->AddPreTargetHandler(this, ui::EventTarget::Priority::kSystem);
  35. }
  36. PowerButtonScreenshotController::~PowerButtonScreenshotController() {
  37. Shell::Get()->RemovePreTargetHandler(this);
  38. }
  39. bool PowerButtonScreenshotController::OnPowerButtonEvent(
  40. bool down,
  41. const base::TimeTicks& timestamp) {
  42. if (!IsTabletMode())
  43. return false;
  44. power_button_pressed_ = down;
  45. if (power_button_pressed_) {
  46. volume_down_timer_.Stop();
  47. volume_up_timer_.Stop();
  48. power_button_pressed_time_ = tick_clock_->NowTicks();
  49. if (InterceptScreenshotChord())
  50. return true;
  51. }
  52. if (!down)
  53. return false;
  54. // If volume key is pressed, mark power button as consumed. This invalidates
  55. // other power button's behavior when user tries to operate screenshot.
  56. return volume_down_key_pressed_ || volume_up_key_pressed_;
  57. }
  58. void PowerButtonScreenshotController::OnKeyEvent(ui::KeyEvent* event) {
  59. if (!IsTabletMode())
  60. return;
  61. ui::KeyboardCode key_code = event->key_code();
  62. if (key_code != ui::VKEY_VOLUME_DOWN && key_code != ui::VKEY_VOLUME_UP)
  63. return;
  64. bool did_consume_volume_keys =
  65. volume_down_key_pressed_ || volume_up_key_pressed_;
  66. bool volume_key_maybe_used_by_app = VolumeKeyMaybeUsedByApp();
  67. // Even if the app is requesting to consume volume key, do not give it if
  68. // 1) power button is already pressed. This should trigger screenshot.
  69. // 2) if this is already handling volume key. We need to continue processing
  70. // volume eve after power button is released, until volume keys are released.
  71. if (volume_key_maybe_used_by_app && !power_button_pressed_ &&
  72. !did_consume_volume_keys) {
  73. return;
  74. }
  75. const bool is_volume_down = key_code == ui::VKEY_VOLUME_DOWN;
  76. if (event->type() == ui::ET_KEY_PRESSED) {
  77. if (!did_consume_volume_keys) {
  78. if (is_volume_down) {
  79. volume_down_key_pressed_ = true;
  80. volume_down_key_pressed_time_ = tick_clock_->NowTicks();
  81. consume_volume_down_ = false;
  82. InterceptScreenshotChord();
  83. } else {
  84. volume_up_key_pressed_ = true;
  85. volume_up_key_pressed_time_ = tick_clock_->NowTicks();
  86. consume_volume_up_ = false;
  87. InterceptScreenshotChord();
  88. }
  89. }
  90. // Do no pass the event to the app if the events are being
  91. // processed
  92. if (consume_volume_down_ || consume_volume_up_ ||
  93. volume_key_maybe_used_by_app) {
  94. event->StopPropagation();
  95. }
  96. } else {
  97. is_volume_down ? volume_down_key_pressed_ = false
  98. : volume_up_key_pressed_ = false;
  99. }
  100. // When volume key is pressed, cancel the ongoing power button behavior.
  101. if (volume_down_key_pressed_ || volume_up_key_pressed_)
  102. Shell::Get()->power_button_controller()->CancelPowerButtonEvent();
  103. // On volume down/up key pressed while power button not pressed yet state, do
  104. // not propagate volume down/up key pressed event for chord delay time. Start
  105. // the timer to wait power button pressed for screenshot operation, and on
  106. // timeout perform the delayed volume down/up operation.
  107. if (power_button_pressed_)
  108. return;
  109. base::TimeTicks now = tick_clock_->NowTicks();
  110. if (volume_down_key_pressed_ && is_volume_down &&
  111. now <= volume_down_key_pressed_time_ + kScreenshotChordDelay) {
  112. event->StopPropagation();
  113. if (!volume_down_timer_.IsRunning()) {
  114. volume_down_timer_.Start(
  115. FROM_HERE, kScreenshotChordDelay,
  116. base::BindOnce(
  117. &PowerButtonScreenshotController::OnVolumeControlTimeout,
  118. base::Unretained(this), ui::Accelerator(*event), /*down=*/true));
  119. }
  120. } else if (volume_up_key_pressed_ && !is_volume_down &&
  121. now <= volume_up_key_pressed_time_ + kScreenshotChordDelay) {
  122. event->StopPropagation();
  123. if (!volume_up_timer_.IsRunning()) {
  124. volume_up_timer_.Start(
  125. FROM_HERE, kScreenshotChordDelay,
  126. base::BindOnce(
  127. &PowerButtonScreenshotController::OnVolumeControlTimeout,
  128. base::Unretained(this), ui::Accelerator(*event), /*down=*/false));
  129. }
  130. }
  131. }
  132. bool PowerButtonScreenshotController::InterceptScreenshotChord() {
  133. if (!power_button_pressed_ ||
  134. (!volume_down_key_pressed_ && !volume_up_key_pressed_)) {
  135. return false;
  136. }
  137. // Record the delay when power button and volume down/up key are both pressed,
  138. // which indicates user might want to use accelerator to take screenshot.
  139. // This will help us determine the best chord delay among metrics.
  140. const base::TimeDelta key_pressed_delay =
  141. volume_down_key_pressed_
  142. ? power_button_pressed_time_ - volume_down_key_pressed_time_
  143. : power_button_pressed_time_ - volume_up_key_pressed_time_;
  144. UMA_HISTOGRAM_TIMES("Ash.PowerButtonScreenshot.DelayBetweenAccelKeyPressed",
  145. key_pressed_delay.magnitude());
  146. base::TimeTicks now = tick_clock_->NowTicks();
  147. if (now > power_button_pressed_time_ + kScreenshotChordDelay)
  148. return false;
  149. consume_volume_down_ =
  150. volume_down_key_pressed_ &&
  151. now <= volume_down_key_pressed_time_ + kScreenshotChordDelay;
  152. consume_volume_up_ =
  153. volume_up_key_pressed_ &&
  154. now <= volume_up_key_pressed_time_ + kScreenshotChordDelay;
  155. if (consume_volume_down_ || consume_volume_up_) {
  156. Shell::Get()->accelerator_controller()->PerformActionIfEnabled(
  157. TAKE_SCREENSHOT, {});
  158. base::RecordAction(base::UserMetricsAction("Accel_PowerButton_Screenshot"));
  159. }
  160. return consume_volume_down_ || consume_volume_up_;
  161. }
  162. void PowerButtonScreenshotController::OnVolumeControlTimeout(
  163. const ui::Accelerator& accelerator,
  164. bool down) {
  165. Shell::Get()->accelerator_controller()->PerformActionIfEnabled(
  166. down ? VOLUME_DOWN : VOLUME_UP, accelerator);
  167. }
  168. } // namespace ash