diff options
author | binjin@chromium.org <binjin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-01-08 12:26:12 +0000 |
---|---|---|
committer | binjin@chromium.org <binjin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-01-08 12:26:12 +0000 |
commit | d31967e44d58801f374af4dee33ecb30d0670ec4 (patch) | |
tree | 06704302d141becff95b84cc54858a688f19db2b /ash | |
parent | d1634c263b79db2688c9c8b2babda6a895c908c0 (diff) | |
download | chromium_src-d31967e44d58801f374af4dee33ecb30d0670ec4.zip chromium_src-d31967e44d58801f374af4dee33ecb30d0670ec4.tar.gz chromium_src-d31967e44d58801f374af4dee33ecb30d0670ec4.tar.bz2 |
Implements the dialog view for logout confirmation dialog in public sessions.
Implements ash::internal::LogoutConfirmationDialogView which is supposed
to be used by ash::internal::LogoutButtonTray.
BUG=223846
TEST=new unit test
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=242286
Review URL: https://codereview.chromium.org/40053002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@243530 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ash')
-rw-r--r-- | ash/ash.gyp | 3 | ||||
-rw-r--r-- | ash/ash_strings.grd | 13 | ||||
-rw-r--r-- | ash/system/logout_button/logout_button_observer.h | 16 | ||||
-rw-r--r-- | ash/system/logout_button/logout_button_tray.cc | 89 | ||||
-rw-r--r-- | ash/system/logout_button/logout_button_tray.h | 29 | ||||
-rw-r--r-- | ash/system/logout_button/logout_button_tray_unittest.cc | 344 | ||||
-rw-r--r-- | ash/system/logout_button/logout_confirmation_dialog_view.cc | 138 | ||||
-rw-r--r-- | ash/system/logout_button/logout_confirmation_dialog_view.h | 77 | ||||
-rw-r--r-- | ash/system/tray/system_tray_notifier.cc | 7 | ||||
-rw-r--r-- | ash/system/tray/system_tray_notifier.h | 2 |
10 files changed, 705 insertions, 13 deletions
diff --git a/ash/ash.gyp b/ash/ash.gyp index 36047b1..8e946d9 100644 --- a/ash/ash.gyp +++ b/ash/ash.gyp @@ -332,6 +332,8 @@ 'system/logout_button/logout_button_observer.h', 'system/logout_button/logout_button_tray.cc', 'system/logout_button/logout_button_tray.h', + 'system/logout_button/logout_confirmation_dialog_view.cc', + 'system/logout_button/logout_confirmation_dialog_view.h', 'system/monitor/tray_monitor.cc', 'system/monitor/tray_monitor.h', 'system/session_length_limit/session_length_limit_observer.h', @@ -828,6 +830,7 @@ 'system/chromeos/screen_security/screen_tray_item_unittest.cc', 'system/chromeos/tray_display_unittest.cc', 'system/date/date_view_unittest.cc', + 'system/logout_button/logout_button_tray_unittest.cc', 'system/session_length_limit/tray_session_length_limit_unittest.cc', 'system/tray/system_tray_unittest.cc', 'system/tray/tray_details_view_unittest.cc', diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd index e839ab5..dc8ef3e 100644 --- a/ash/ash_strings.grd +++ b/ash/ash_strings.grd @@ -585,6 +585,19 @@ Press Shift + Alt to switch. Press Control Shift Q twice to quit. </message> + <message name="IDS_ASH_LOGOUT_CONFIRMATION_TITLE" desc="Dialog title for the logout confirmation dialog."> + Exiting Session + </message> + <message name="IDS_ASH_LOGOUT_CONFIRMATION_WARNING" desc="The text for the logout confirmation dialog."> + You will automatically be signed out in <ph name="LOGOUT_TIME_LEFT">$1<ex>5 seconds</ex></ph>. + </message> + <message name="IDS_ASH_LOGOUT_CONFIRMATION_WARNING_NOW" desc="The text for the logout confirmation dialog with no time left."> + You will be signed out now. + </message> + <message name="IDS_ASH_LOGOUT_CONFIRMATION_BUTTON" desc="The text for okay button of the logout confirmation dialog."> + Sign out now + </message> + <!-- ChromeOS-specific strings --> <if expr="pp_ifdef('chromeos')"> <part file="ash_chromeos_strings.grdp" /> diff --git a/ash/system/logout_button/logout_button_observer.h b/ash/system/logout_button/logout_button_observer.h index 9234c7d..913ab85 100644 --- a/ash/system/logout_button/logout_button_observer.h +++ b/ash/system/logout_button/logout_button_observer.h @@ -5,16 +5,24 @@ #ifndef ASH_SYSTEM_LOGOUT_BUTTON_LOGOUT_BUTTON_OBSERVER_H_ #define ASH_SYSTEM_LOGOUT_BUTTON_LOGOUT_BUTTON_OBSERVER_H_ +#include "ash/ash_export.h" +#include "base/time/time.h" + namespace ash { -// Observer for the value of the kShowLogoutButtonInTray pref that determines -// whether a logout button should be shown in the system tray during a session. -class LogoutButtonObserver { +class ASH_EXPORT LogoutButtonObserver { public: virtual ~LogoutButtonObserver() {} - // Called when the value of the kShowLogoutButtonInTray pref changes. + // Called when the value of the kShowLogoutButtonInTray pref changes, which + // determines whether a logout button should be shown in the system tray + // during a session. virtual void OnShowLogoutButtonInTrayChanged(bool show) = 0; + + // Called when the value of the kLogoutDialogDurationMs pref changes. + // |duration| is the duration for which the logout confirmation dialog is + // shown after the user has pressed the logout button. + virtual void OnLogoutDialogDurationChanged(base::TimeDelta duration) = 0; }; } // namespace ash diff --git a/ash/system/logout_button/logout_button_tray.cc b/ash/system/logout_button/logout_button_tray.cc index e99b340..c5e3a3b 100644 --- a/ash/system/logout_button/logout_button_tray.cc +++ b/ash/system/logout_button/logout_button_tray.cc @@ -24,7 +24,6 @@ #include "ui/views/painter.h" namespace ash { - namespace internal { namespace { @@ -64,6 +63,21 @@ class LogoutButton : public views::LabelButton { DISALLOW_COPY_AND_ASSIGN(LogoutButton); }; +class LogoutConfirmationDialogDelegate + : public LogoutConfirmationDialogView::Delegate { + + public: + LogoutConfirmationDialogDelegate() {} + virtual ~LogoutConfirmationDialogDelegate() {} + + virtual void LogoutCurrentUser() OVERRIDE; + virtual base::TimeTicks GetCurrentTime() const OVERRIDE; + virtual void ShowDialog(views::DialogDelegate* dialog) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(LogoutConfirmationDialogDelegate); +}; + } // namespace LogoutButton::LogoutButton(views::ButtonListener* listener) @@ -95,20 +109,62 @@ LogoutButton::LogoutButton(views::ButtonListener* listener) LogoutButton::~LogoutButton() { } +void LogoutConfirmationDialogDelegate::LogoutCurrentUser() { + Shell::GetInstance()->system_tray_delegate()->SignOut(); +} + +base::TimeTicks LogoutConfirmationDialogDelegate::GetCurrentTime() const { + return base::TimeTicks::Now(); +} + +void LogoutConfirmationDialogDelegate::ShowDialog( + views::DialogDelegate *dialog) { + views::DialogDelegate::CreateDialogWidget( + dialog, ash::Shell::GetPrimaryRootWindow(), NULL); + dialog->GetWidget()->Show(); +} + LogoutButtonTray::LogoutButtonTray(StatusAreaWidget* status_area_widget) : TrayBackgroundView(status_area_widget), button_(NULL), login_status_(user::LOGGED_IN_NONE), - show_logout_button_in_tray_(false) { + show_logout_button_in_tray_(false), + confirmation_dialog_(NULL), + confirmation_delegate_(new LogoutConfirmationDialogDelegate) { button_ = new LogoutButton(this); tray_container()->AddChildView(button_); tray_container()->set_border(NULL); - Shell::GetInstance()->system_tray_notifier()->AddLogoutButtonObserver(this); + // The Shell may not exist in some unit tests. + if (Shell::HasInstance()) { + Shell::GetInstance()->system_tray_notifier()-> + AddLogoutButtonObserver(this); + } } LogoutButtonTray::~LogoutButtonTray() { - Shell::GetInstance()->system_tray_notifier()-> - RemoveLogoutButtonObserver(this); + EnsureConfirmationDialogIsClosed(); + // The Shell may not exist in some unit tests. + if (Shell::HasInstance()) { + Shell::GetInstance()->system_tray_notifier()-> + RemoveLogoutButtonObserver(this); + } +} + +bool LogoutButtonTray::IsConfirmationDialogShowing() const { + return confirmation_dialog_ != NULL; +} + +void LogoutButtonTray::EnsureConfirmationDialogIsShowing() { + if (!confirmation_dialog_) { + confirmation_dialog_ = new LogoutConfirmationDialogView( + this, confirmation_delegate_.get()); + confirmation_dialog_->Show(dialog_duration_); + } +} + +void LogoutButtonTray::EnsureConfirmationDialogIsClosed() { + if (confirmation_dialog_) + confirmation_dialog_->Close(); } void LogoutButtonTray::SetShelfAlignment(ShelfAlignment alignment) { @@ -133,10 +189,20 @@ void LogoutButtonTray::OnShowLogoutButtonInTrayChanged(bool show) { UpdateVisibility(); } +void LogoutButtonTray::OnLogoutDialogDurationChanged(base::TimeDelta duration) { + dialog_duration_ = duration; + if (confirmation_dialog_) + confirmation_dialog_->UpdateDialogDuration(dialog_duration_); +} + void LogoutButtonTray::ButtonPressed(views::Button* sender, const ui::Event& event) { DCHECK_EQ(sender, button_); - Shell::GetInstance()->system_tray_delegate()->SignOut(); + // Sign out immediately if |dialog_duration_| is non-positive. + if (dialog_duration_ <= base::TimeDelta()) + confirmation_delegate_->LogoutCurrentUser(); + else + EnsureConfirmationDialogIsShowing(); } void LogoutButtonTray::UpdateAfterLoginStatusChange( @@ -149,10 +215,21 @@ void LogoutButtonTray::UpdateAfterLoginStatusChange( UpdateVisibility(); } +void LogoutButtonTray::ReleaseConfirmationDialog() { + confirmation_dialog_ = NULL; +} + +void LogoutButtonTray::SetDelegateForTest( + scoped_ptr<LogoutConfirmationDialogView::Delegate> delegate) { + confirmation_delegate_ = delegate.Pass(); +} + void LogoutButtonTray::UpdateVisibility() { SetVisible(show_logout_button_in_tray_ && login_status_ != user::LOGGED_IN_NONE && login_status_ != user::LOGGED_IN_LOCKED); + if (!show_logout_button_in_tray_) + EnsureConfirmationDialogIsClosed(); } } // namespace internal diff --git a/ash/system/logout_button/logout_button_tray.h b/ash/system/logout_button/logout_button_tray.h index 5683e5e2..5755835 100644 --- a/ash/system/logout_button/logout_button_tray.h +++ b/ash/system/logout_button/logout_button_tray.h @@ -5,11 +5,15 @@ #ifndef ASH_SYSTEM_LOGOUT_BUTTON_LOGOUT_BUTTON_TRAY_H_ #define ASH_SYSTEM_LOGOUT_BUTTON_LOGOUT_BUTTON_TRAY_H_ +#include "ash/ash_export.h" #include "ash/system/logout_button/logout_button_observer.h" +#include "ash/system/logout_button/logout_confirmation_dialog_view.h" #include "ash/system/tray/tray_background_view.h" #include "ash/system/user/login_status.h" #include "base/basictypes.h" #include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" #include "ui/views/controls/button/button.h" namespace views { @@ -23,9 +27,9 @@ class StatusAreaWidget; // Adds a logout button to the launcher's status area if enabled by the // kShowLogoutButtonInTray pref. -class LogoutButtonTray : public TrayBackgroundView, - public LogoutButtonObserver, - public views::ButtonListener { +class ASH_EXPORT LogoutButtonTray : public TrayBackgroundView, + public LogoutButtonObserver, + public views::ButtonListener { public: explicit LogoutButtonTray(StatusAreaWidget* status_area_widget); virtual ~LogoutButtonTray(); @@ -39,6 +43,7 @@ class LogoutButtonTray : public TrayBackgroundView, // LogoutButtonObserver: virtual void OnShowLogoutButtonInTrayChanged(bool show) OVERRIDE; + virtual void OnLogoutDialogDurationChanged(base::TimeDelta duration) OVERRIDE; // views::ButtonListener: virtual void ButtonPressed(views::Button* sender, @@ -46,12 +51,30 @@ class LogoutButtonTray : public TrayBackgroundView, void UpdateAfterLoginStatusChange(user::LoginStatus login_status); + // Nullify the pointer to confirmation dialog, this is expected to be called + // when the confirmation dialog is going to be destroyed. + // Note: the confirmation dialog is not owned by LogoutButtonTray. + void ReleaseConfirmationDialog(); + + void SetDelegateForTest( + scoped_ptr<LogoutConfirmationDialogView::Delegate> delegate); + private: + friend class LogoutConfirmationDialogTest; + + bool IsConfirmationDialogShowing() const; + void EnsureConfirmationDialogIsShowing(); + void EnsureConfirmationDialogIsClosed(); + void UpdateVisibility(); views::LabelButton* button_; // Not owned. user::LoginStatus login_status_; bool show_logout_button_in_tray_; + base::TimeDelta dialog_duration_; + + LogoutConfirmationDialogView* confirmation_dialog_; // Not owned. + scoped_ptr<LogoutConfirmationDialogView::Delegate> confirmation_delegate_; DISALLOW_COPY_AND_ASSIGN(LogoutButtonTray); }; diff --git a/ash/system/logout_button/logout_button_tray_unittest.cc b/ash/system/logout_button/logout_button_tray_unittest.cc new file mode 100644 index 0000000..796af17 --- /dev/null +++ b/ash/system/logout_button/logout_button_tray_unittest.cc @@ -0,0 +1,344 @@ +// 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 "ash/system/logout_button/logout_button_tray.h" + +#include <queue> +#include <utility> +#include <vector> + +#include "ash/system/status_area_widget.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/single_thread_task_runner.h" +#include "base/thread_task_runner_handle.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/events/event.h" +#include "ui/events/keycodes/keyboard_codes.h" +#include "ui/views/controls/button/label_button.h" + +namespace ash { +namespace internal { + +class LogoutConfirmationDialogTest; + +namespace { + +// A SingleThreadTaskRunner that mocks the current time and allows it to be +// fast-forwarded. +// TODO: crbug.com/329911 +// Move shared copies of this class to one place. +class MockTimeSingleThreadTaskRunner : public base::SingleThreadTaskRunner { + public: + MockTimeSingleThreadTaskRunner(); + + // base::SingleThreadTaskRunner: + virtual bool RunsTasksOnCurrentThread() const OVERRIDE; + virtual bool PostDelayedTask(const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) OVERRIDE; + virtual bool PostNonNestableDelayedTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) OVERRIDE; + + const base::TimeTicks& GetCurrentTime() const; + + void FastForwardBy(base::TimeDelta delta); + void FastForwardUntilNoTasksRemain(); + + private: + // Strict weak temporal ordering of tasks. + class TemporalOrder { + public: + bool operator()( + const std::pair<base::TimeTicks, base::Closure>& first_task, + const std::pair<base::TimeTicks, base::Closure>& second_task) const; + }; + + virtual ~MockTimeSingleThreadTaskRunner(); + + base::TimeTicks now_; + std::priority_queue<std::pair<base::TimeTicks, base::Closure>, + std::vector<std::pair<base::TimeTicks, base::Closure> >, + TemporalOrder> tasks_; +}; + +MockTimeSingleThreadTaskRunner::MockTimeSingleThreadTaskRunner() { +} + +bool MockTimeSingleThreadTaskRunner::RunsTasksOnCurrentThread() const { + return true; +} + +bool MockTimeSingleThreadTaskRunner::PostDelayedTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) { + tasks_.push(std::pair<base::TimeTicks, base::Closure>(now_ + delay, task)); + return true; +} + +bool MockTimeSingleThreadTaskRunner::PostNonNestableDelayedTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) { + NOTREACHED(); + return false; +} + +const base::TimeTicks& MockTimeSingleThreadTaskRunner::GetCurrentTime() const { + return now_; +} + +void MockTimeSingleThreadTaskRunner::FastForwardBy(base::TimeDelta delta) { + const base::TimeTicks latest = now_ + delta; + while (!tasks_.empty() && tasks_.top().first <= latest) { + now_ = tasks_.top().first; + base::Closure task = tasks_.top().second; + tasks_.pop(); + task.Run(); + } + now_ = latest; +} + +void MockTimeSingleThreadTaskRunner::FastForwardUntilNoTasksRemain() { + while (!tasks_.empty()) { + now_ = tasks_.top().first; + base::Closure task = tasks_.top().second; + tasks_.pop(); + task.Run(); + } +} + +bool MockTimeSingleThreadTaskRunner::TemporalOrder::operator()( + const std::pair<base::TimeTicks, base::Closure>& first_task, + const std::pair<base::TimeTicks, base::Closure>& second_task) const { + return first_task.first > second_task.first; +} + +MockTimeSingleThreadTaskRunner::~MockTimeSingleThreadTaskRunner() { +} + +} // namespace + +class MockLogoutConfirmationDelegate + : public LogoutConfirmationDialogView::Delegate { + public: + MockLogoutConfirmationDelegate( + scoped_refptr<MockTimeSingleThreadTaskRunner> runner, + LogoutConfirmationDialogTest* tester); + + // LogoutConfirmationDialogView::Delegate: + virtual void LogoutCurrentUser() OVERRIDE; + virtual base::TimeTicks GetCurrentTime() const OVERRIDE; + virtual void ShowDialog(views::DialogDelegate* dialog) OVERRIDE; + + bool logout_called() const { return logout_called_; } + + private: + bool logout_called_; + + scoped_refptr<MockTimeSingleThreadTaskRunner> runner_; + scoped_ptr<views::DialogDelegate> dialog_ownership_holder_; + LogoutConfirmationDialogTest* tester_; + + DISALLOW_COPY_AND_ASSIGN(MockLogoutConfirmationDelegate); +}; + +class LogoutConfirmationDialogTest : public testing::Test { + public: + LogoutConfirmationDialogTest(); + virtual ~LogoutConfirmationDialogTest(); + + // testing::Test: + virtual void SetUp() OVERRIDE; + + void ChangeDialogDuration(base::TimeDelta duration); + + void CloseDialog(); + bool IsDialogShowing(); + void PressButton(); + void PressDialogButtonYes(); + + protected: + scoped_ptr<LogoutButtonTray> logout_button_; + scoped_refptr<MockTimeSingleThreadTaskRunner> runner_; + base::ThreadTaskRunnerHandle runner_handle_; + MockLogoutConfirmationDelegate* delegate_; + + private: + DISALLOW_COPY_AND_ASSIGN(LogoutConfirmationDialogTest); +}; + +MockLogoutConfirmationDelegate::MockLogoutConfirmationDelegate( + scoped_refptr<MockTimeSingleThreadTaskRunner> runner, + LogoutConfirmationDialogTest* tester) + : logout_called_(false), + runner_(runner), + tester_(tester) { +} + +void MockLogoutConfirmationDelegate::LogoutCurrentUser() { + logout_called_ = true; + tester_->CloseDialog(); +} + +base::TimeTicks MockLogoutConfirmationDelegate::GetCurrentTime() const { + return runner_->GetCurrentTime(); +} + +void MockLogoutConfirmationDelegate::ShowDialog(views::DialogDelegate* dialog) { + // Simulate the ownership passing of dialog in tests. + dialog_ownership_holder_.reset(dialog); +} + +LogoutConfirmationDialogTest::LogoutConfirmationDialogTest() + : runner_(new MockTimeSingleThreadTaskRunner), + runner_handle_(runner_) { +} + +LogoutConfirmationDialogTest::~LogoutConfirmationDialogTest() { +} + +void LogoutConfirmationDialogTest::SetUp() { + logout_button_.reset(new LogoutButtonTray(NULL)); + delegate_ = new MockLogoutConfirmationDelegate(runner_, this); + logout_button_->SetDelegateForTest( + scoped_ptr<LogoutConfirmationDialogView::Delegate>(delegate_)); + ChangeDialogDuration(base::TimeDelta::FromSeconds(20)); +} + +void LogoutConfirmationDialogTest::ChangeDialogDuration( + base::TimeDelta duration) { + logout_button_->OnLogoutDialogDurationChanged(duration); +} + +void LogoutConfirmationDialogTest::CloseDialog() { + if (logout_button_->confirmation_dialog_) + logout_button_->confirmation_dialog_->OnClosed(); +} + +bool LogoutConfirmationDialogTest::IsDialogShowing() { + return logout_button_->IsConfirmationDialogShowing(); +} + +void LogoutConfirmationDialogTest::PressButton() { + const ui::TranslatedKeyEvent faked_event( + false, + static_cast<ui::KeyboardCode>(0), + 0); + logout_button_->ButtonPressed( + reinterpret_cast<views::Button*>(logout_button_->button_), faked_event); +} + +void LogoutConfirmationDialogTest::PressDialogButtonYes() { + logout_button_->confirmation_dialog_->Accept(); + // |confirmation_dialog_| might already be destroyed, if not, manually call + // OnClosed() to simulate real browser environment behavior. + if (logout_button_->confirmation_dialog_) + logout_button_->confirmation_dialog_->OnClosed(); +} + +TEST_F(LogoutConfirmationDialogTest, NoClickWithDefaultValue) { + PressButton(); + + // Verify that the dialog is showing immediately after the logout button was + // pressed. + runner_->FastForwardBy(base::TimeDelta::FromSeconds(0)); + EXPECT_TRUE(IsDialogShowing()); + EXPECT_FALSE(delegate_->logout_called()); + + // Verify that the dialog is still showing after 19 seconds since the logout + // button was pressed. + runner_->FastForwardBy(base::TimeDelta::FromSeconds(19)); + EXPECT_TRUE(IsDialogShowing()); + EXPECT_FALSE(delegate_->logout_called()); + + // Verify that the dialog is closed after 21 seconds since the logout button + // was pressed. + runner_->FastForwardBy(base::TimeDelta::FromSeconds(2)); + EXPECT_FALSE(IsDialogShowing()); + EXPECT_TRUE(delegate_->logout_called()); +} + +TEST_F(LogoutConfirmationDialogTest, ZeroPreferenceValue) { + ChangeDialogDuration(base::TimeDelta::FromSeconds(0)); + + EXPECT_FALSE(IsDialogShowing()); + + PressButton(); + + // Verify that user was logged out immediately after the logout button was + // pressed. + runner_->FastForwardBy(base::TimeDelta::FromSeconds(0)); + EXPECT_FALSE(IsDialogShowing()); + EXPECT_TRUE(delegate_->logout_called()); + + runner_->FastForwardUntilNoTasksRemain(); + EXPECT_FALSE(IsDialogShowing()); +} + +TEST_F(LogoutConfirmationDialogTest, OnTheFlyDialogDurationChange) { + ChangeDialogDuration(base::TimeDelta::FromSeconds(5)); + + EXPECT_FALSE(IsDialogShowing()); + + PressButton(); + + // Verify that the dialog is showing immediately after the logout button was + // pressed. + runner_->FastForwardBy(base::TimeDelta::FromSeconds(0)); + EXPECT_TRUE(IsDialogShowing()); + EXPECT_FALSE(delegate_->logout_called()); + + // Verify that the dialog is still showing after 3 seconds since the logout + // button was pressed. + runner_->FastForwardBy(base::TimeDelta::FromSeconds(3)); + EXPECT_TRUE(IsDialogShowing()); + EXPECT_FALSE(delegate_->logout_called()); + + // And at this point we change the dialog duration preference. + ChangeDialogDuration(base::TimeDelta::FromSeconds(10)); + + // Verify that the dialog is still showing after 9 seconds since the logout + // button was pressed, with dialog duration preference changed. + runner_->FastForwardBy(base::TimeDelta::FromSeconds(6)); + EXPECT_TRUE(IsDialogShowing()); + EXPECT_FALSE(delegate_->logout_called()); + + // Verify that the dialog is closed after 11 seconds since the logout button + // was pressed. + runner_->FastForwardBy(base::TimeDelta::FromSeconds(2)); + EXPECT_FALSE(IsDialogShowing()); + EXPECT_TRUE(delegate_->logout_called()); +} + +TEST_F(LogoutConfirmationDialogTest, UserClickedButton) { + PressButton(); + + // Verify that the dialog is showing immediately after the logout button was + // pressed. + runner_->FastForwardBy(base::TimeDelta::FromSeconds(0)); + EXPECT_TRUE(IsDialogShowing()); + EXPECT_FALSE(delegate_->logout_called()); + + // Verify that the dialog is still showing after 3 seconds since the logout + // button was pressed. + runner_->FastForwardBy(base::TimeDelta::FromSeconds(3)); + EXPECT_TRUE(IsDialogShowing()); + EXPECT_FALSE(delegate_->logout_called()); + + // And at this point we click the accept button. + PressDialogButtonYes(); + + // Verify that the user was logged out immediately after the accept button + // was clicked. + runner_->FastForwardBy(base::TimeDelta::FromSeconds(0)); + EXPECT_TRUE(delegate_->logout_called()); +} + +} // namespace internal +} // namespace ash diff --git a/ash/system/logout_button/logout_confirmation_dialog_view.cc b/ash/system/logout_button/logout_confirmation_dialog_view.cc new file mode 100644 index 0000000..3b5df9b --- /dev/null +++ b/ash/system/logout_button/logout_confirmation_dialog_view.cc @@ -0,0 +1,138 @@ +// 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 "ash/system/logout_button/logout_confirmation_dialog_view.h" + +#include "ash/shell.h" +#include "ash/system/logout_button/logout_button_tray.h" +#include "ash/system/tray/system_tray_delegate.h" +#include "ash/system/tray/tray_constants.h" +#include "base/location.h" +#include "grit/ash_strings.h" +#include "ui/aura/root_window.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/l10n/time_format.h" +#include "ui/base/ui_base_types.h" +#include "ui/gfx/text_constants.h" +#include "ui/views/border.h" +#include "ui/views/controls/label.h" +#include "ui/views/layout/fill_layout.h" +#include "ui/views/widget/widget.h" + +namespace ash { +namespace internal { + +namespace { + +const int kCountdownUpdateIntervalMs = 1000; // 1 second. + +inline int Round(double x) { + return static_cast<int>(x + 0.5); +} + +} // namespace + +LogoutConfirmationDialogView::LogoutConfirmationDialogView( + LogoutButtonTray* owner, Delegate* delegate) : owner_(owner), + delegate_(delegate) { + text_label_ = new views::Label; + text_label_->set_border( + views::Border::CreateEmptyBorder(0, kTrayPopupPaddingHorizontal, + 0, kTrayPopupPaddingHorizontal)); + text_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); + text_label_->SetMultiLine(true); + + SetLayoutManager(new views::FillLayout()); + + AddChildView(text_label_); +} + +LogoutConfirmationDialogView::~LogoutConfirmationDialogView() { +} + +bool LogoutConfirmationDialogView::Accept() { + LogoutCurrentUser(); + return true; +} + +ui::ModalType LogoutConfirmationDialogView::GetModalType() const { + return ui::MODAL_TYPE_SYSTEM; +} + +base::string16 LogoutConfirmationDialogView::GetWindowTitle() const { + return l10n_util::GetStringUTF16(IDS_ASH_LOGOUT_CONFIRMATION_TITLE); +} + +base::string16 LogoutConfirmationDialogView::GetDialogButtonLabel( + ui::DialogButton button) const { + if (button == ui::DIALOG_BUTTON_OK) + return l10n_util::GetStringUTF16(IDS_ASH_LOGOUT_CONFIRMATION_BUTTON); + return views::DialogDelegateView::GetDialogButtonLabel(button); +} + +void LogoutConfirmationDialogView::OnClosed() { + owner_->ReleaseConfirmationDialog(); + owner_ = NULL; + timer_.Stop(); + // Nullify the delegate to prevent future activities of the dialog. + delegate_ = NULL; +} + +void LogoutConfirmationDialogView::DeleteDelegate() { + if (owner_) + owner_->ReleaseConfirmationDialog(); + delete this; +} + +void LogoutConfirmationDialogView::Show(base::TimeDelta duration) { + if (!delegate_) + return; + countdown_start_time_ = delegate_->GetCurrentTime(); + duration_ = duration; + + UpdateCountdown(); + + delegate_->ShowDialog(this); + + timer_.Start(FROM_HERE, + base::TimeDelta::FromMilliseconds(kCountdownUpdateIntervalMs), + this, + &LogoutConfirmationDialogView::UpdateCountdown); +} + +void LogoutConfirmationDialogView::UpdateDialogDuration( + base::TimeDelta duration) { + duration_ = duration; + UpdateCountdown(); +} + +void LogoutConfirmationDialogView::LogoutCurrentUser() { + if (!delegate_) + return; + delegate_->LogoutCurrentUser(); +} + +void LogoutConfirmationDialogView::UpdateCountdown() { + if (!delegate_) + return; + const base::TimeDelta time_remaining = countdown_start_time_ + + duration_ - delegate_->GetCurrentTime(); + // Round the remaining time to nearest second, and use this value for + // the countdown display and actual enforcement. + int seconds_remaining = Round(time_remaining.InSecondsF()); + if (seconds_remaining > 0) { + text_label_->SetText(l10n_util::GetStringFUTF16( + IDS_ASH_LOGOUT_CONFIRMATION_WARNING, + ui::TimeFormat::TimeDurationLong(base::TimeDelta::FromSeconds( + seconds_remaining)))); + } else { + text_label_->SetText(l10n_util::GetStringUTF16( + IDS_ASH_LOGOUT_CONFIRMATION_WARNING_NOW)); + timer_.Stop(); + LogoutCurrentUser(); + } +} + +} // namespace internal +} // namespace ash diff --git a/ash/system/logout_button/logout_confirmation_dialog_view.h b/ash/system/logout_button/logout_confirmation_dialog_view.h new file mode 100644 index 0000000..3329eee --- /dev/null +++ b/ash/system/logout_button/logout_confirmation_dialog_view.h @@ -0,0 +1,77 @@ +// 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. + +#ifndef ASH_SYSTEM_LOGOUT_BUTTON_LOGOUT_CONFIRMATION_DIALOG_VIEW_H_ +#define ASH_SYSTEM_LOGOUT_BUTTON_LOGOUT_CONFIRMATION_DIALOG_VIEW_H_ + +#include "ash/ash_export.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/time/time.h" +#include "base/timer/timer.h" +#include "ui/views/window/dialog_delegate.h" + +namespace views { +class Label; +} + +namespace ash { +namespace internal { + +class LogoutButtonTray; + +// This class implements dialog view for logout confirmation. +class ASH_EXPORT LogoutConfirmationDialogView + : public views::DialogDelegateView { + public: + class ASH_EXPORT Delegate { + public: + virtual ~Delegate() {} + + virtual void LogoutCurrentUser() = 0; + virtual base::TimeTicks GetCurrentTime() const = 0; + virtual void ShowDialog(views::DialogDelegate* dialog) = 0; + }; + + LogoutConfirmationDialogView(LogoutButtonTray* owner, Delegate* delegate); + virtual ~LogoutConfirmationDialogView(); + + // views::DialogDelegateView: + virtual bool Accept() OVERRIDE; + virtual ui::ModalType GetModalType() const OVERRIDE; + virtual base::string16 GetWindowTitle() const OVERRIDE; + virtual base::string16 + GetDialogButtonLabel(ui::DialogButton button) const OVERRIDE; + virtual void OnClosed() OVERRIDE; + virtual void DeleteDelegate() OVERRIDE; + + void Show(base::TimeDelta duration); + + // The duration of the confirmation dialog can be changed while the dialog + // is still showing. This method is only expected to be called when the + // preference for confirmation dialog duration has changed. + void UpdateDialogDuration(base::TimeDelta duration); + + private: + void LogoutCurrentUser(); + void UpdateCountdown(); + + views::Label* text_label_; + + base::TimeTicks countdown_start_time_; + base::TimeDelta duration_; + + base::RepeatingTimer<LogoutConfirmationDialogView> timer_; + + LogoutButtonTray* owner_; + + Delegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(LogoutConfirmationDialogView); +}; + +} // namespace internal +} // namespace ash + +#endif // ASH_SYSTEM_LOGOUT_BUTTON_LOGOUT_CONFIRMATION_DIALOG_VIEW_H_ diff --git a/ash/system/tray/system_tray_notifier.cc b/ash/system/tray/system_tray_notifier.cc index e2035ad..95b6789 100644 --- a/ash/system/tray/system_tray_notifier.cc +++ b/ash/system/tray/system_tray_notifier.cc @@ -232,6 +232,13 @@ void SystemTrayNotifier::NotifyShowLoginButtonChanged(bool show_login_button) { OnShowLogoutButtonInTrayChanged(show_login_button)); } +void SystemTrayNotifier::NotifyLogoutDialogDurationChanged( + base::TimeDelta duration) { + FOR_EACH_OBSERVER(LogoutButtonObserver, + logout_button_observers_, + OnLogoutDialogDurationChanged(duration)); +} + void SystemTrayNotifier::NotifyLocaleChanged( LocaleObserver::Delegate* delegate, const std::string& cur_locale, diff --git a/ash/system/tray/system_tray_notifier.h b/ash/system/tray/system_tray_notifier.h index 3d7fb0e..6124040 100644 --- a/ash/system/tray/system_tray_notifier.h +++ b/ash/system/tray/system_tray_notifier.h @@ -22,6 +22,7 @@ #include "ash/system/user/update_observer.h" #include "ash/system/user/user_observer.h" #include "base/observer_list.h" +#include "base/time/time.h" #if defined(OS_CHROMEOS) #include "ash/system/chromeos/enterprise/enterprise_domain_observer.h" @@ -103,6 +104,7 @@ public: void NotifyDriveJobUpdated(const DriveOperationStatus& status); void NotifyRefreshIME(bool show_message); void NotifyShowLoginButtonChanged(bool show_login_button); + void NotifyLogoutDialogDurationChanged(base::TimeDelta duration); void NotifyLocaleChanged(LocaleObserver::Delegate* delegate, const std::string& cur_locale, const std::string& from_locale, |