diff options
author | sschmitz@chromium.org <sschmitz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-15 08:48:03 +0000 |
---|---|---|
committer | sschmitz@chromium.org <sschmitz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-15 08:48:03 +0000 |
commit | 059c8d351d5083744525c595110f730081cf5280 (patch) | |
tree | 249e08b2fbbe8ff215b17ee21eb59d902598e527 /ash/accelerators | |
parent | e9304b9fefc5deeeaa99363945ab7dd1bd6260be (diff) | |
download | chromium_src-059c8d351d5083744525c595110f730081cf5280.zip chromium_src-059c8d351d5083744525c595110f730081cf5280.tar.gz chromium_src-059c8d351d5083744525c595110f730081cf5280.tar.bz2 |
Unify and change logout/sleep/lock shortcuts
This CL implements an exit warning and requires the user to hold
the exit shortcut for a while, before actually exiting. This is
to prevent accidental exits due shortcut mistakes.
Expert user can exit quickly with a double press.
BUG=225903
R=sky@chromium.org
TEST=manual
out/Debug/ash_unittests --gtest_filter='AcceleratorControllerTest.ExitWarningHandlerTest*'
Review URL: https://chromiumcodereview.appspot.com/14587007
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@200204 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ash/accelerators')
-rw-r--r-- | ash/accelerators/accelerator_controller.cc | 5 | ||||
-rw-r--r-- | ash/accelerators/accelerator_controller.h | 8 | ||||
-rw-r--r-- | ash/accelerators/accelerator_controller_unittest.cc | 97 | ||||
-rw-r--r-- | ash/accelerators/accelerator_table.cc | 12 | ||||
-rw-r--r-- | ash/accelerators/accelerator_table.h | 3 | ||||
-rw-r--r-- | ash/accelerators/exit_warning_handler.cc | 196 | ||||
-rw-r--r-- | ash/accelerators/exit_warning_handler.h | 126 |
7 files changed, 440 insertions, 7 deletions
diff --git a/ash/accelerators/accelerator_controller.cc b/ash/accelerators/accelerator_controller.cc index 2c6ca35..daa013a 100644 --- a/ash/accelerators/accelerator_controller.cc +++ b/ash/accelerators/accelerator_controller.cc @@ -574,8 +574,9 @@ bool AcceleratorController::PerformAction(int action, case OPEN_FEEDBACK_PAGE: ash::Shell::GetInstance()->delegate()->OpenFeedbackPage(); return true; - case EXIT: - Shell::GetInstance()->delegate()->Exit(); + case EXIT_PRESSED: + case EXIT_RELEASED: + exit_warning_handler_.HandleExitKey(action == EXIT_PRESSED); return true; case NEW_INCOGNITO_WINDOW: Shell::GetInstance()->delegate()->NewWindow(true /* is_incognito */); diff --git a/ash/accelerators/accelerator_controller.h b/ash/accelerators/accelerator_controller.h index 53ed723..b1e4bfc 100644 --- a/ash/accelerators/accelerator_controller.h +++ b/ash/accelerators/accelerator_controller.h @@ -8,6 +8,7 @@ #include <map> #include <set> +#include "ash/accelerators/exit_warning_handler.h" #include "ash/ash_export.h" #include "base/basictypes.h" #include "base/compiler_specific.h" @@ -23,6 +24,7 @@ namespace ash { struct AcceleratorData; class BrightnessControlDelegate; +class ExitWarningHandler; class ImeControlDelegate; class KeyboardBrightnessControlDelegate; class ScreenshotDelegate; @@ -115,6 +117,11 @@ class ASH_EXPORT AcceleratorController : public ui::AcceleratorTarget { return &context_; } + // Provides access to the ExitWarningHandler for testing. + ExitWarningHandler* GetExitWarningHandlerForTest() { + return &exit_warning_handler_; + } + private: FRIEND_TEST_ALL_PREFIXES(AcceleratorControllerTest, GlobalAccelerators); @@ -134,6 +141,7 @@ class ASH_EXPORT AcceleratorController : public ui::AcceleratorTarget { // TODO(derat): BrightnessControlDelegate is also used by the system tray; // move it outside of this class. scoped_ptr<BrightnessControlDelegate> brightness_control_delegate_; + ExitWarningHandler exit_warning_handler_; scoped_ptr<ImeControlDelegate> ime_control_delegate_; scoped_ptr<KeyboardBrightnessControlDelegate> keyboard_brightness_control_delegate_; diff --git a/ash/accelerators/accelerator_controller_unittest.cc b/ash/accelerators/accelerator_controller_unittest.cc index 7ef53ff..292cc3e 100644 --- a/ash/accelerators/accelerator_controller_unittest.cc +++ b/ash/accelerators/accelerator_controller_unittest.cc @@ -321,6 +321,26 @@ class AcceleratorControllerTest : public test::AshTestBase { static AcceleratorController* GetController(); static bool ProcessWithContext(const ui::Accelerator& accelerator); + // Several functions to access ExitWarningHandler (as friend). + static void StubForTest(ExitWarningHandler& ewh) { + ewh.stub_timers_for_test_ = true; + } + static void SimulateTimer1Expired(ExitWarningHandler& ewh) { + ewh.Timer1Action(); + } + static void SimulateTimer2Expired(ExitWarningHandler& ewh) { + ewh.Timer2Action(); + } + static bool is_ui_shown(ExitWarningHandler& ewh) { + return !!ewh.widget_; + } + static bool is_idle(ExitWarningHandler& ewh) { + return ewh.state_ == ExitWarningHandler::IDLE; + } + static bool is_exiting(ExitWarningHandler& ewh) { + return ewh.state_ == ExitWarningHandler::EXITING; + } + private: DISALLOW_COPY_AND_ASSIGN(AcceleratorControllerTest); }; @@ -336,6 +356,72 @@ bool AcceleratorControllerTest::ProcessWithContext( return controller->Process(accelerator); } +// Quick double press of exit key => exiting +TEST_F(AcceleratorControllerTest, ExitWarningHandlerTestDoublePress) { + ExitWarningHandler ewh; + StubForTest(ewh); + EXPECT_TRUE(is_idle(ewh)); + EXPECT_FALSE(is_ui_shown(ewh)); + + ewh.HandleExitKey(true); + EXPECT_TRUE(is_ui_shown(ewh)); + ewh.HandleExitKey(false); + ewh.HandleExitKey(true); // double press + SimulateTimer1Expired(ewh); // simulate double press timer expired + SimulateTimer2Expired(ewh); // simulate long hold timer expired + ewh.HandleExitKey(false); + EXPECT_FALSE(is_ui_shown(ewh)); + EXPECT_TRUE(is_exiting(ewh)); +} + +// Long hold of exit key => exiting +TEST_F(AcceleratorControllerTest, ExitWarningHandlerTestLongHold) { + ExitWarningHandler ewh; + StubForTest(ewh); + EXPECT_TRUE(is_idle(ewh)); + EXPECT_FALSE(is_ui_shown(ewh)); + + ewh.HandleExitKey(true); + EXPECT_TRUE(is_ui_shown(ewh)); + SimulateTimer1Expired(ewh); // simulate double press timer expired + SimulateTimer2Expired(ewh); // simulate long hold timer expired + ewh.HandleExitKey(false); // release after long hold + EXPECT_FALSE(is_ui_shown(ewh)); + EXPECT_TRUE(is_exiting(ewh)); +} + +// Release of exit key before hold time limit => cancel +TEST_F(AcceleratorControllerTest, ExitWarningHandlerTestEarlyRelease) { + ExitWarningHandler ewh; + StubForTest(ewh); + EXPECT_TRUE(is_idle(ewh)); + EXPECT_FALSE(is_ui_shown(ewh)); + + ewh.HandleExitKey(true); + EXPECT_TRUE(is_ui_shown(ewh)); + SimulateTimer1Expired(ewh); // simulate double press timer expired + ewh.HandleExitKey(false); // release before long hold limit + SimulateTimer2Expired(ewh); // simulate long hold timer expired + EXPECT_FALSE(is_ui_shown(ewh)); + EXPECT_TRUE(is_idle(ewh)); +} + +// Release of exit key before double press limit => cancel. +TEST_F(AcceleratorControllerTest, ExitWarningHandlerTestQuickRelease) { + ExitWarningHandler ewh; + StubForTest(ewh); + EXPECT_TRUE(is_idle(ewh)); + EXPECT_FALSE(is_ui_shown(ewh)); + + ewh.HandleExitKey(true); + EXPECT_TRUE(is_ui_shown(ewh)); + ewh.HandleExitKey(false); // release before double press limit + SimulateTimer1Expired(ewh); // simulate double press timer expired + SimulateTimer2Expired(ewh); // simulate long hold timer expired + EXPECT_FALSE(is_ui_shown(ewh)); + EXPECT_TRUE(is_idle(ewh)); +} + TEST_F(AcceleratorControllerTest, Register) { const ui::Accelerator accelerator_a(ui::VKEY_A, ui::EF_NONE); TestTarget target; @@ -860,8 +946,19 @@ TEST_F(AcceleratorControllerTest, GlobalAccelerators) { #if !defined(OS_WIN) // Exit + ExitWarningHandler* ewh = GetController()->GetExitWarningHandlerForTest(); + ASSERT_TRUE(!!ewh); + StubForTest(*ewh); + EXPECT_TRUE(is_idle(*ewh)); + EXPECT_FALSE(is_ui_shown(*ewh)); EXPECT_TRUE(ProcessWithContext( ui::Accelerator(ui::VKEY_Q, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN))); + EXPECT_FALSE(is_idle(*ewh)); + EXPECT_TRUE(is_ui_shown(*ewh)); + SimulateTimer1Expired(*ewh); + SimulateTimer2Expired(*ewh); + EXPECT_FALSE(is_ui_shown(*ewh)); + EXPECT_TRUE(is_exiting(*ewh)); #endif // New tab diff --git a/ash/accelerators/accelerator_table.cc b/ash/accelerators/accelerator_table.cc index 62e4d81..d9a6813 100644 --- a/ash/accelerators/accelerator_table.cc +++ b/ash/accelerators/accelerator_table.cc @@ -73,7 +73,8 @@ const AcceleratorData kAcceleratorData[] = { #endif // defined(OS_CHROMEOS) { true, ui::VKEY_I, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN, OPEN_FEEDBACK_PAGE }, #if !defined(OS_WIN) - { true, ui::VKEY_Q, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN, EXIT }, + { true, ui::VKEY_Q, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN, EXIT_PRESSED }, + { false, ui::VKEY_Q, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN, EXIT_RELEASED }, #endif { true, ui::VKEY_I, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN, TOUCH_HUD_MODE_CHANGE }, @@ -276,7 +277,8 @@ const size_t kActionsAllowedAtLoginOrLockScreenLength = arraysize(kActionsAllowedAtLoginOrLockScreen); const AcceleratorAction kActionsAllowedAtLockScreen[] = { - EXIT, + EXIT_PRESSED, + EXIT_RELEASED }; const size_t kActionsAllowedAtLockScreenLength = @@ -286,7 +288,8 @@ const AcceleratorAction kActionsAllowedAtModalWindow[] = { BRIGHTNESS_DOWN, BRIGHTNESS_UP, DISABLE_CAPS_LOCK, - EXIT, + EXIT_PRESSED, + EXIT_RELEASED, KEYBOARD_BRIGHTNESS_DOWN, KEYBOARD_BRIGHTNESS_UP, MAGNIFY_SCREEN_ZOOM_IN, @@ -348,7 +351,8 @@ const AcceleratorAction kActionsAllowedInAppMode[] = { CYCLE_FORWARD_LINEAR, CYCLE_FORWARD_MRU, DISABLE_CAPS_LOCK, - EXIT, + EXIT_PRESSED, + EXIT_RELEASED, KEYBOARD_BRIGHTNESS_DOWN, KEYBOARD_BRIGHTNESS_UP, MAGNIFY_SCREEN_ZOOM_IN, // Control+F7 diff --git a/ash/accelerators/accelerator_table.h b/ash/accelerators/accelerator_table.h index ca2d2701..70e57d8d 100644 --- a/ash/accelerators/accelerator_table.h +++ b/ash/accelerators/accelerator_table.h @@ -28,7 +28,8 @@ enum AcceleratorAction { DEBUG_TOGGLE_SHOW_FPS_COUNTER, DEBUG_TOGGLE_SHOW_PAINT_RECTS, DISABLE_CAPS_LOCK, - EXIT, + EXIT_PRESSED, + EXIT_RELEASED, FOCUS_LAUNCHER, FOCUS_NEXT_PANE, FOCUS_PREVIOUS_PANE, diff --git a/ash/accelerators/exit_warning_handler.cc b/ash/accelerators/exit_warning_handler.cc new file mode 100644 index 0000000..a285560 --- /dev/null +++ b/ash/accelerators/exit_warning_handler.cc @@ -0,0 +1,196 @@ +// Copyright 2013 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/accelerators/exit_warning_handler.h" + +#include "ash/shell.h" +#include "ash/shell_delegate.h" +#include "ash/shell_window_ids.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time.h" +#include "base/timer.h" +#include "grit/ash_strings.h" +#include "ui/aura/root_window.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/font.h" +#include "ui/views/controls/label.h" +#include "ui/views/layout/fill_layout.h" +#include "ui/views/view.h" +#include "ui/views/widget/widget.h" +#include "ui/views/widget/widget_delegate.h" + +namespace ash { +namespace { + +const int64 kDoublePressTimeOutMilliseconds = 300; +const int64 kHoldTimeOutMilliseconds = 1700; +const SkColor kForegroundColor = 0xFFFFFFFF; +const SkColor kBackgroundColor = 0xE0808080; +const int kHorizontalMarginAroundText = 100; +const int kVerticalMarginAroundText = 100; + +class ExitWarningWidgetDelegateView : public views::WidgetDelegateView { + public: + ExitWarningWidgetDelegateView() : text_width_(0), width_(0), height_(0) { + text_ = l10n_util::GetStringUTF16(IDS_ASH_EXIT_WARNING_POPUP_TEXT); + ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); + font_ = rb.GetFont(ui::ResourceBundle::LargeFont); + text_width_ = font_.GetStringWidth(text_); + width_ = text_width_ + kHorizontalMarginAroundText; + height_ = font_.GetHeight() + kVerticalMarginAroundText; + views::Label* label = new views::Label; + label->SetText(text_); + label->SetHorizontalAlignment(gfx::ALIGN_CENTER); + label->SetFont(font_); + label->SetEnabledColor(kForegroundColor); + label->SetDisabledColor(kForegroundColor); + label->SetAutoColorReadabilityEnabled(false); + AddChildView(label); + SetLayoutManager(new views::FillLayout); + } + + virtual gfx::Size GetPreferredSize() OVERRIDE { + return gfx::Size(width_, height_); + } + + virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE { + canvas->FillRect(GetLocalBounds(), kBackgroundColor); + views::WidgetDelegateView::OnPaint(canvas); + } + + private: + base::string16 text_; + gfx::Font font_; + int text_width_; + int width_; + int height_; + + DISALLOW_COPY_AND_ASSIGN(ExitWarningWidgetDelegateView); +}; + +} // namespace + +ExitWarningHandler::ExitWarningHandler() + : state_(IDLE), + widget_(NULL), + stub_timers_for_test_(false) { +} + +ExitWarningHandler::~ExitWarningHandler() { + // Note: If a timer is outstanding, it is stopped in its destructor. + Hide(); +} + +void ExitWarningHandler::HandleExitKey(bool press) { + switch (state_) { + case IDLE: + if (press) { + state_ = WAIT_FOR_QUICK_RELEASE; + Show(); + StartTimers(); + } + break; + case WAIT_FOR_QUICK_RELEASE: + if (!press) + state_ = WAIT_FOR_DOUBLE_PRESS; + break; + case WAIT_FOR_DOUBLE_PRESS: + if (press) { + state_ = EXITING; + CancelTimers(); + Hide(); + Shell::GetInstance()->delegate()->Exit(); + } + break; + case WAIT_FOR_LONG_HOLD: + if (!press) + state_ = CANCELED; + break; + case CANCELED: + case EXITING: + break; + default: + NOTREACHED(); + break; + } +} + +void ExitWarningHandler::Timer1Action() { + if (state_ == WAIT_FOR_QUICK_RELEASE) + state_ = WAIT_FOR_LONG_HOLD; + else if (state_ == WAIT_FOR_DOUBLE_PRESS) + state_ = CANCELED; +} + +void ExitWarningHandler::Timer2Action() { + if (state_ == CANCELED) { + state_ = IDLE; + Hide(); + } + else if (state_ == WAIT_FOR_LONG_HOLD) { + state_ = EXITING; + Hide(); + Shell::GetInstance()->delegate()->Exit(); + } +} + +void ExitWarningHandler::StartTimers() { + if (stub_timers_for_test_) + return; + timer1_.Start(FROM_HERE, + base::TimeDelta::FromMilliseconds( + kDoublePressTimeOutMilliseconds), + this, + &ExitWarningHandler::Timer1Action); + timer2_.Start(FROM_HERE, + base::TimeDelta::FromMilliseconds(kHoldTimeOutMilliseconds), + this, + &ExitWarningHandler::Timer2Action); +} + +void ExitWarningHandler::CancelTimers() { + timer1_.Stop(); + timer2_.Stop(); +} + +void ExitWarningHandler::Show() { + if (widget_) + return; + aura::RootWindow* root_window = Shell::GetActiveRootWindow(); + ExitWarningWidgetDelegateView* delegate = new ExitWarningWidgetDelegateView; + gfx::Size rs = root_window->bounds().size(); + gfx::Size ps = delegate->GetPreferredSize(); + gfx::Rect bounds((rs.width() - ps.width()) / 2, + (rs.height() - ps.height()) / 3, + ps.width(), ps.height()); + views::Widget::InitParams params; + params.type = views::Widget::InitParams::TYPE_POPUP; + params.transient = true; + params.transparent = true; + params.accept_events = false; + params.can_activate = false; + params.keep_on_top = true; + params.remove_standard_frame = true; + params.delegate = delegate; + params.bounds = bounds; + params.parent = Shell::GetContainer( + root_window, + internal::kShellWindowId_SettingBubbleContainer); + widget_ = new views::Widget; + widget_->Init(params); + widget_->SetContentsView(delegate); + widget_->GetNativeView()->SetName("ExitWarningWindow"); + widget_->Show(); +} + +void ExitWarningHandler::Hide() { + if (!widget_) + return; + widget_->Close(); + widget_ = NULL; +} + +} // namespace ash diff --git a/ash/accelerators/exit_warning_handler.h b/ash/accelerators/exit_warning_handler.h new file mode 100644 index 0000000..5b3d718e --- /dev/null +++ b/ash/accelerators/exit_warning_handler.h @@ -0,0 +1,126 @@ +// Copyright 2013 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. + +#ifndef ASH_ACCELERATORS_EXIT_WARNING_HANDLER_H_ +#define ASH_ACCELERATORS_EXIT_WARNING_HANDLER_H_ + +#include "ash/ash_export.h" +#include "base/timer.h" + +namespace views { +class Widget; +} + +namespace ash { + +// In order to avoid accidental exits when the user presses the exit +// shortcut by mistake, we require the user to hold the shortcut for +// a period of time. During that time we show a popup informing the +// user of this. We exit only if the user holds the shortcut longer +// than this time limit. +// An expert user may quickly release and then press again (double press) +// for immediate exit. The press, release and press must happen within +// the double press time limit. +// If the user releases (without double press) before the required hold +// time, we will cancel the exit, but show the ui until the hold time limit +// has expired to avoid a short popup flash. +// +// State Transition Diagrams: +// +// T1 - double press time limit (short) +// T2 - hold to exit time limit (long) +// +// IDLE +// | Press +// WAIT_FOR_QUICK_RELEASE action: show ui & start timers +// | Release (DT < T1) +// WAIT_FOR_DOUBLE_PRESS +// | Press (DT < T1) +// EXITING action: hide ui, stop timers, exit +// +// IDLE +// | Press +// WAIT_FOR_QUICK_RELEASE action: show ui & start timers +// | T1 timer expires +// WAIT_FOR_LONG_HOLD +// | T2 Timer exipres +// EXITING action: hide ui, exit +// +// IDLE +// | Press +// WAIT_FOR_QUICK_RELEASE action: show ui & start timers +// | T1 timer expiers +// WAIT_FOR_LONG_HOLD +// | Release +// CANCELED +// | T2 timer expires +// IDLE action: hide ui +// +// IDLE +// | Press +// WAIT_FOR_QUICK_RELEASE action: show ui & start timers +// | Release (DT < T1) +// WAIT_FOR_DOUBLE_PRESS +// | T1 timer expires +// CANCELED +// | T2 timer expires +// IDLE action: hide ui +// + +class AcceleratorControllerTest; + +class ASH_EXPORT ExitWarningHandler { + public: + ExitWarningHandler(); + + ~ExitWarningHandler(); + + // Handles shortcut key press and release (Ctrl-Shift-Q). + void HandleExitKey(bool press); + + private: + friend class AcceleratorControllerTest; + + enum State { + IDLE, + WAIT_FOR_QUICK_RELEASE, + WAIT_FOR_DOUBLE_PRESS, + WAIT_FOR_LONG_HOLD, + CANCELED, + EXITING + }; + + // Performs actions (see state diagram above) when the "double key + // press" time limit is exceeded. This is the shorter of the two + // time limits. + void Timer1Action(); + + // Performs actions (see state diagram above) when the hold time + // limit is exceeded. See state diagram above. This is the longer + // of the two time limits. + void Timer2Action(); + + void StartTimers(); + void CancelTimers(); + + void Show(); + void Hide(); + + State state_; + views::Widget* widget_; // owned by |this|. + base::OneShotTimer<ExitWarningHandler> timer1_; // short; double press + base::OneShotTimer<ExitWarningHandler> timer2_; // long; hold to exit + + // Flag to suppress starting the timers for testing. For test we + // call TimerAction1() and TimerAction2() directly to simulate the + // expiration of the timers. + bool stub_timers_for_test_; + + DISALLOW_COPY_AND_ASSIGN(ExitWarningHandler); +}; + +} // namespace ash + +#endif // ASH_ACCELERATORS_EXIT_WARNING_HANDLER_H_ + |