summaryrefslogtreecommitdiffstats
path: root/ash
diff options
context:
space:
mode:
authorbinjin@chromium.org <binjin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-01-08 12:26:12 +0000
committerbinjin@chromium.org <binjin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-01-08 12:26:12 +0000
commitd31967e44d58801f374af4dee33ecb30d0670ec4 (patch)
tree06704302d141becff95b84cc54858a688f19db2b /ash
parentd1634c263b79db2688c9c8b2babda6a895c908c0 (diff)
downloadchromium_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.gyp3
-rw-r--r--ash/ash_strings.grd13
-rw-r--r--ash/system/logout_button/logout_button_observer.h16
-rw-r--r--ash/system/logout_button/logout_button_tray.cc89
-rw-r--r--ash/system/logout_button/logout_button_tray.h29
-rw-r--r--ash/system/logout_button/logout_button_tray_unittest.cc344
-rw-r--r--ash/system/logout_button/logout_confirmation_dialog_view.cc138
-rw-r--r--ash/system/logout_button/logout_confirmation_dialog_view.h77
-rw-r--r--ash/system/tray/system_tray_notifier.cc7
-rw-r--r--ash/system/tray/system_tray_notifier.h2
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,