Commit baaec481 authored by Toni Barzic's avatar Toni Barzic Committed by Commit Bot

Show an error bubble on lock screen when user attaches different base

Makes LockContentsView observe DetachableBaseHandler, and display an
error bubble when it detects that the user has attached a base
different than the one they used last.
The bubble has the same style as the auth error bubble, but is not
supposed to be dismissable by the user (so untrusted user can't remove
the error bubble while the device is unattended).

Updates LoginBubble class to support non-dismissable error bubbles -
introduces kFlagPersistent flag that can be passed to ShowErrorBubble.
If the flag is set, the bubble will not be closed on key, mouse or
gesture events.

BUG=796300

Change-Id: I9723ba61450d03bdedb395462994d0b3546e5db0
Reviewed-on: https://chromium-review.googlesource.com/941726Reviewed-by: default avatarJacob Dufault <jdufault@chromium.org>
Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Commit-Queue: Toni Barzic <tbarzic@chromium.org>
Cr-Commit-Position: refs/heads/master@{#542298}
parent 45cabf37
...@@ -298,6 +298,8 @@ component("ash") { ...@@ -298,6 +298,8 @@ component("ash") {
"login/ui/login_button.h", "login/ui/login_button.h",
"login/ui/login_data_dispatcher.cc", "login/ui/login_data_dispatcher.cc",
"login/ui/login_data_dispatcher.h", "login/ui/login_data_dispatcher.h",
"login/ui/login_detachable_base_model.cc",
"login/ui/login_detachable_base_model.h",
"login/ui/login_password_view.cc", "login/ui/login_password_view.cc",
"login/ui/login_password_view.h", "login/ui/login_password_view.h",
"login/ui/login_pin_view.cc", "login/ui/login_pin_view.cc",
...@@ -1455,6 +1457,8 @@ test("ash_unittests") { ...@@ -1455,6 +1457,8 @@ test("ash_unittests") {
"login/login_screen_controller_unittest.cc", "login/login_screen_controller_unittest.cc",
"login/mock_login_screen_client.cc", "login/mock_login_screen_client.cc",
"login/mock_login_screen_client.h", "login/mock_login_screen_client.h",
"login/ui/fake_login_detachable_base_model.cc",
"login/ui/fake_login_detachable_base_model.h",
"login/ui/lock_contents_view_unittest.cc", "login/ui/lock_contents_view_unittest.cc",
"login/ui/lock_screen_sanity_unittest.cc", "login/ui/lock_screen_sanity_unittest.cc",
"login/ui/lock_window_unittest.cc", "login/ui/lock_window_unittest.cc",
......
// Copyright 2018 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/login/ui/fake_login_detachable_base_model.h"
#include "ash/login/ui/login_data_dispatcher.h"
#include "ash/public/interfaces/user_info.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace ash {
FakeLoginDetachableBaseModel::FakeLoginDetachableBaseModel(
LoginDataDispatcher* data_dispatcher)
: data_dispatcher_(data_dispatcher) {}
FakeLoginDetachableBaseModel::~FakeLoginDetachableBaseModel() = default;
void FakeLoginDetachableBaseModel::InitLastUsedBases(
const std::map<AccountId, std::string>& last_used_bases) {
ASSERT_TRUE(last_used_bases_.empty());
last_used_bases_ = last_used_bases;
}
std::string FakeLoginDetachableBaseModel::GetLastUsedBase(
const AccountId& account_id) {
auto it = last_used_bases_.find(account_id);
if (it == last_used_bases_.end())
return "";
return it->second;
}
void FakeLoginDetachableBaseModel::SetPairingStatus(
DetachableBasePairingStatus pairing_status,
const std::string& base_id) {
ASSERT_EQ(pairing_status == DetachableBasePairingStatus::kAuthenticated,
!base_id.empty());
current_authenticated_base_ = base_id;
pairing_status_ = pairing_status;
data_dispatcher_->SetDetachableBasePairingStatus(pairing_status);
}
DetachableBasePairingStatus FakeLoginDetachableBaseModel::GetPairingStatus() {
return pairing_status_;
}
bool FakeLoginDetachableBaseModel::PairedBaseMatchesLastUsedByUser(
const mojom::UserInfo& user_info) {
EXPECT_FALSE(current_authenticated_base_.empty());
std::string last_used = GetLastUsedBase(user_info.account_id);
return last_used.empty() || last_used == current_authenticated_base_;
}
bool FakeLoginDetachableBaseModel::SetPairedBaseAsLastUsedByUser(
const mojom::UserInfo& user_info) {
if (current_authenticated_base_.empty())
return false;
last_used_bases_[user_info.account_id] = current_authenticated_base_;
return true;
}
} // namespace ash
// Copyright 2018 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_LOGIN_UI_FAKE_LOGIN_DETACHABLE_BASE_MODEL_H_
#define ASH_LOGIN_UI_FAKE_LOGIN_DETACHABLE_BASE_MODEL_H_
#include <map>
#include <string>
#include "ash/detachable_base/detachable_base_pairing_status.h"
#include "ash/login/ui/login_detachable_base_model.h"
#include "base/macros.h"
#include "components/signin/core/account_id/account_id.h"
namespace ash {
class LoginDataDispatcher;
// Fake LoginDetachableBaseModel implementation. To be used in tests to hide
// dependency on DetachableBaseHandler.
class FakeLoginDetachableBaseModel : public LoginDetachableBaseModel {
public:
// |data_dispatcher| - the dispatcher to which the pairing status changes
// should be forwarded.
explicit FakeLoginDetachableBaseModel(LoginDataDispatcher* data_dispatcher);
~FakeLoginDetachableBaseModel() override;
// Sets the initial mapping for user -> last used detachable base.
// It will assert if called while |last_used_bases_| is not empty.
void InitLastUsedBases(
const std::map<AccountId, std::string>& last_used_bases);
// Gets the last recorded base for the user with the provided account id.
// Returns empty string if the user does not have a recorded detcachable base
// usage.
std::string GetLastUsedBase(const AccountId& account_id);
// Changes current detachable base pairing status.
// |pairing_status| - the new pairing status.
// |base_id| - the authenticated base ID. It is expect to be set if and only
// if pairing_status is kAuthenticated.
void SetPairingStatus(DetachableBasePairingStatus pairing_status,
const std::string& base_id);
// LoginDetachableBaseModel:
DetachableBasePairingStatus GetPairingStatus() override;
bool PairedBaseMatchesLastUsedByUser(
const mojom::UserInfo& user_info) override;
bool SetPairedBaseAsLastUsedByUser(const mojom::UserInfo& user_info) override;
private:
LoginDataDispatcher* data_dispatcher_;
// Current pairing status.
DetachableBasePairingStatus pairing_status_ =
DetachableBasePairingStatus::kNone;
// The ID if the currently authenticated detachable base.
std::string current_authenticated_base_;
// Maps user account Id to the ID of the last used detachable base.
std::map<AccountId, std::string> last_used_bases_;
DISALLOW_COPY_AND_ASSIGN(FakeLoginDetachableBaseModel);
};
} // namespace ash
#endif // ASH_LOGIN_UI_FAKE_LOGIN_DETACHABLE_BASE_MODEL_H_
...@@ -4,8 +4,11 @@ ...@@ -4,8 +4,11 @@
#include "ash/login/ui/lock_contents_view.h" #include "ash/login/ui/lock_contents_view.h"
#include <algorithm>
#include <memory> #include <memory>
#include <utility>
#include "ash/detachable_base/detachable_base_pairing_status.h"
#include "ash/focus_cycler.h" #include "ash/focus_cycler.h"
#include "ash/ime/ime_controller.h" #include "ash/ime/ime_controller.h"
#include "ash/keyboard/keyboard_observer_register.h" #include "ash/keyboard/keyboard_observer_register.h"
...@@ -14,6 +17,7 @@ ...@@ -14,6 +17,7 @@
#include "ash/login/ui/lock_screen.h" #include "ash/login/ui/lock_screen.h"
#include "ash/login/ui/login_auth_user_view.h" #include "ash/login/ui/login_auth_user_view.h"
#include "ash/login/ui/login_bubble.h" #include "ash/login/ui/login_bubble.h"
#include "ash/login/ui/login_detachable_base_model.h"
#include "ash/login/ui/login_user_view.h" #include "ash/login/ui/login_user_view.h"
#include "ash/login/ui/non_accessible_view.h" #include "ash/login/ui/non_accessible_view.h"
#include "ash/login/ui/note_action_launch_button.h" #include "ash/login/ui/note_action_launch_button.h"
...@@ -46,6 +50,7 @@ ...@@ -46,6 +50,7 @@
#include "ui/views/focus/focus_search.h" #include "ui/views/focus/focus_search.h"
#include "ui/views/layout/box_layout.h" #include "ui/views/layout/box_layout.h"
#include "ui/views/layout/fill_layout.h" #include "ui/views/layout/fill_layout.h"
#include "ui/views/style/typography.h"
#include "ui/views/view.h" #include "ui/views/view.h"
namespace ash { namespace ash {
...@@ -196,6 +201,14 @@ LoginBubble* LockContentsView::TestApi::tooltip_bubble() const { ...@@ -196,6 +201,14 @@ LoginBubble* LockContentsView::TestApi::tooltip_bubble() const {
return view_->tooltip_bubble_.get(); return view_->tooltip_bubble_.get();
} }
LoginBubble* LockContentsView::TestApi::auth_error_bubble() const {
return view_->auth_error_bubble_.get();
}
LoginBubble* LockContentsView::TestApi::detachable_base_error_bubble() const {
return view_->detachable_base_error_bubble_.get();
}
views::View* LockContentsView::TestApi::dev_channel_info() const { views::View* LockContentsView::TestApi::dev_channel_info() const {
return view_->dev_channel_info_; return view_->dev_channel_info_;
} }
...@@ -209,9 +222,11 @@ LockContentsView::UserState::~UserState() = default; ...@@ -209,9 +222,11 @@ LockContentsView::UserState::~UserState() = default;
LockContentsView::LockContentsView( LockContentsView::LockContentsView(
mojom::TrayActionState initial_note_action_state, mojom::TrayActionState initial_note_action_state,
LoginDataDispatcher* data_dispatcher) LoginDataDispatcher* data_dispatcher,
std::unique_ptr<LoginDetachableBaseModel> detachable_base_model)
: NonAccessibleView(kLockContentsViewName), : NonAccessibleView(kLockContentsViewName),
data_dispatcher_(data_dispatcher), data_dispatcher_(data_dispatcher),
detachable_base_model_(std::move(detachable_base_model)),
display_observer_(this), display_observer_(this),
session_observer_(this), session_observer_(this),
keyboard_observer_(this) { keyboard_observer_(this) {
...@@ -219,7 +234,8 @@ LockContentsView::LockContentsView( ...@@ -219,7 +234,8 @@ LockContentsView::LockContentsView(
display_observer_.Add(display::Screen::GetScreen()); display_observer_.Add(display::Screen::GetScreen());
Shell::Get()->login_screen_controller()->AddLockScreenAppsFocusObserver(this); Shell::Get()->login_screen_controller()->AddLockScreenAppsFocusObserver(this);
Shell::Get()->system_tray_notifier()->AddSystemTrayFocusObserver(this); Shell::Get()->system_tray_notifier()->AddSystemTrayFocusObserver(this);
error_bubble_ = std::make_unique<LoginBubble>(); auth_error_bubble_ = std::make_unique<LoginBubble>();
detachable_base_error_bubble_ = std::make_unique<LoginBubble>();
tooltip_bubble_ = std::make_unique<LoginBubble>(); tooltip_bubble_ = std::make_unique<LoginBubble>();
// We reuse the focusable state on this view as a signal that focus should // We reuse the focusable state on this view as a signal that focus should
...@@ -496,6 +512,43 @@ void LockContentsView::OnPublicSessionLocalesChanged( ...@@ -496,6 +512,43 @@ void LockContentsView::OnPublicSessionLocalesChanged(
NOTIMPLEMENTED(); NOTIMPLEMENTED();
} }
void LockContentsView::OnDetachableBasePairingStatusChanged(
DetachableBasePairingStatus pairing_status) {
const mojom::UserInfoPtr& user_info =
CurrentAuthUserView()->current_user()->basic_user_info;
// If the base is not paired, or the paired base matches the last used by the
// current user, the detachable base error bubble should be hidden. Otherwise,
// the bubble should be shown.
if (pairing_status == DetachableBasePairingStatus::kNone ||
(pairing_status == DetachableBasePairingStatus::kAuthenticated &&
detachable_base_model_->PairedBaseMatchesLastUsedByUser(*user_info))) {
detachable_base_error_bubble_->Close();
return;
}
auth_error_bubble_->Close();
base::string16 error_text =
l10n_util::GetStringUTF16(IDS_ASH_LOGIN_ERROR_DETACHABLE_BASE_CHANGED);
views::Label* label =
new views::Label(error_text, views::style::CONTEXT_MESSAGE_BOX_BODY_TEXT,
views::style::STYLE_PRIMARY);
label->SetMultiLine(true);
label->SetAutoColorReadabilityEnabled(false);
label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
label->SetEnabledColor(SK_ColorWHITE);
detachable_base_error_bubble_->ShowErrorBubble(
label, CurrentAuthUserView()->password_view() /*anchor_view*/,
LoginBubble::kFlagPersistent);
// Remove the focus from the password field, to make user less likely to enter
// the password without seeing the warning about detachable base change.
if (GetWidget()->IsActive())
GetWidget()->GetFocusManager()->ClearFocus();
}
void LockContentsView::OnFocusLeavingLockScreenApps(bool reverse) { void LockContentsView::OnFocusLeavingLockScreenApps(bool reverse) {
if (!reverse || lock_screen_apps_active_) if (!reverse || lock_screen_apps_active_)
FocusNextWidget(reverse); FocusNextWidget(reverse);
...@@ -696,9 +749,20 @@ void LockContentsView::SwapActiveAuthBetweenPrimaryAndSecondary( ...@@ -696,9 +749,20 @@ void LockContentsView::SwapActiveAuthBetweenPrimaryAndSecondary(
void LockContentsView::OnAuthenticate(bool auth_success) { void LockContentsView::OnAuthenticate(bool auth_success) {
if (auth_success) { if (auth_success) {
error_bubble_->Close(); auth_error_bubble_->Close();
detachable_base_error_bubble_->Close();
// Now that the user has been authenticated, update the user's last used
// detachable base (if one is attached). This will prevent further
// detachable base change notifications from appearing for this base (until
// the user uses another detachable base).
if (detachable_base_model_->GetPairingStatus() ==
DetachableBasePairingStatus::kAuthenticated) {
detachable_base_model_->SetPairedBaseAsLastUsedByUser(
*CurrentAuthUserView()->current_user()->basic_user_info);
}
} else { } else {
ShowErrorMessage(); ShowAuthErrorMessage();
++unlock_attempt_; ++unlock_attempt_;
} }
} }
...@@ -778,6 +842,11 @@ void LockContentsView::OnAuthUserChanged() { ...@@ -778,6 +842,11 @@ void LockContentsView::OnAuthUserChanged() {
// Reset unlock attempt when the auth user changes. // Reset unlock attempt when the auth user changes.
unlock_attempt_ = 0; unlock_attempt_ = 0;
} }
// The new auth user might have different last used detachable base - make
// sure the detachable base pairing error is updated if needed.
OnDetachableBasePairingStatusChanged(
detachable_base_model_->GetPairingStatus());
} }
void LockContentsView::UpdateEasyUnlockIconForUser(const AccountId& user) { void LockContentsView::UpdateEasyUnlockIconForUser(const AccountId& user) {
...@@ -818,7 +887,7 @@ LoginAuthUserView* LockContentsView::CurrentAuthUserView() { ...@@ -818,7 +887,7 @@ LoginAuthUserView* LockContentsView::CurrentAuthUserView() {
return primary_auth_; return primary_auth_;
} }
void LockContentsView::ShowErrorMessage() { void LockContentsView::ShowAuthErrorMessage() {
base::string16 error_text = l10n_util::GetStringUTF16( base::string16 error_text = l10n_util::GetStringUTF16(
unlock_attempt_ ? IDS_ASH_LOGIN_ERROR_AUTHENTICATING_2ND_TIME unlock_attempt_ ? IDS_ASH_LOGIN_ERROR_AUTHENTICATING_2ND_TIME
: IDS_ASH_LOGIN_ERROR_AUTHENTICATING); : IDS_ASH_LOGIN_ERROR_AUTHENTICATING);
...@@ -848,8 +917,11 @@ void LockContentsView::ShowErrorMessage() { ...@@ -848,8 +917,11 @@ void LockContentsView::ShowErrorMessage() {
views::StyledLabel* label = new views::StyledLabel(error_text, this); views::StyledLabel* label = new views::StyledLabel(error_text, this);
MakeSectionBold(label, error_text, bold_start, bold_length); MakeSectionBold(label, error_text, bold_start, bold_length);
error_bubble_->ShowErrorBubble( label->set_auto_color_readability_enabled(false);
label, CurrentAuthUserView()->password_view() /*anchor_view*/);
auth_error_bubble_->ShowErrorBubble(
label, CurrentAuthUserView()->password_view() /*anchor_view*/,
LoginBubble::kFlagsNone);
} }
void LockContentsView::OnEasyUnlockIconHovered() { void LockContentsView::OnEasyUnlockIconHovered() {
......
...@@ -39,6 +39,7 @@ namespace ash { ...@@ -39,6 +39,7 @@ namespace ash {
class LoginAuthUserView; class LoginAuthUserView;
class LoginBubble; class LoginBubble;
class LoginDetachableBaseModel;
class NoteActionLaunchButton; class NoteActionLaunchButton;
class ScrollableUsersListView; class ScrollableUsersListView;
...@@ -71,14 +72,18 @@ class ASH_EXPORT LockContentsView : public NonAccessibleView, ...@@ -71,14 +72,18 @@ class ASH_EXPORT LockContentsView : public NonAccessibleView,
ScrollableUsersListView* users_list() const; ScrollableUsersListView* users_list() const;
views::View* note_action() const; views::View* note_action() const;
LoginBubble* tooltip_bubble() const; LoginBubble* tooltip_bubble() const;
LoginBubble* auth_error_bubble() const;
LoginBubble* detachable_base_error_bubble() const;
views::View* dev_channel_info() const; views::View* dev_channel_info() const;
private: private:
LockContentsView* const view_; LockContentsView* const view_;
}; };
LockContentsView(mojom::TrayActionState initial_note_action_state, LockContentsView(
LoginDataDispatcher* data_dispatcher); mojom::TrayActionState initial_note_action_state,
LoginDataDispatcher* data_dispatcher,
std::unique_ptr<LoginDetachableBaseModel> detachable_base_model);
~LockContentsView() override; ~LockContentsView() override;
// views::View: // views::View:
...@@ -111,6 +116,8 @@ class ASH_EXPORT LockContentsView : public NonAccessibleView, ...@@ -111,6 +116,8 @@ class ASH_EXPORT LockContentsView : public NonAccessibleView,
const base::ListValue& locales, const base::ListValue& locales,
const std::string& default_locale, const std::string& default_locale,
bool show_advanced_view) override; bool show_advanced_view) override;
void OnDetachableBasePairingStatusChanged(
DetachableBasePairingStatus pairing_status) override;
// SystemTrayFocusObserver: // SystemTrayFocusObserver:
void OnFocusLeavingSystemTray(bool reverse) override; void OnFocusLeavingSystemTray(bool reverse) override;
...@@ -216,7 +223,7 @@ class ASH_EXPORT LockContentsView : public NonAccessibleView, ...@@ -216,7 +223,7 @@ class ASH_EXPORT LockContentsView : public NonAccessibleView,
LoginAuthUserView* CurrentAuthUserView(); LoginAuthUserView* CurrentAuthUserView();
// Opens an error bubble to indicate authentication failure. // Opens an error bubble to indicate authentication failure.
void ShowErrorMessage(); void ShowAuthErrorMessage();
// Called when the easy unlock icon is hovered. // Called when the easy unlock icon is hovered.
void OnEasyUnlockIconHovered(); void OnEasyUnlockIconHovered();
...@@ -247,6 +254,7 @@ class ASH_EXPORT LockContentsView : public NonAccessibleView, ...@@ -247,6 +254,7 @@ class ASH_EXPORT LockContentsView : public NonAccessibleView,
std::vector<UserState> users_; std::vector<UserState> users_;
LoginDataDispatcher* const data_dispatcher_; // Unowned. LoginDataDispatcher* const data_dispatcher_; // Unowned.
std::unique_ptr<LoginDetachableBaseModel> detachable_base_model_;
LoginAuthUserView* primary_auth_ = nullptr; LoginAuthUserView* primary_auth_ = nullptr;
LoginAuthUserView* opt_secondary_auth_ = nullptr; LoginAuthUserView* opt_secondary_auth_ = nullptr;
...@@ -279,7 +287,12 @@ class ASH_EXPORT LockContentsView : public NonAccessibleView, ...@@ -279,7 +287,12 @@ class ASH_EXPORT LockContentsView : public NonAccessibleView,
keyboard::KeyboardControllerObserver> keyboard::KeyboardControllerObserver>
keyboard_observer_; keyboard_observer_;
std::unique_ptr<LoginBubble> error_bubble_; // Bubbles for displaying authentication error.
std::unique_ptr<LoginBubble> auth_error_bubble_;
// Bubble for displaying error when the user's detachable base changes.
std::unique_ptr<LoginBubble> detachable_base_error_bubble_;
std::unique_ptr<LoginBubble> tooltip_bubble_; std::unique_ptr<LoginBubble> tooltip_bubble_;
int unlock_attempt_ = 0; int unlock_attempt_ = 0;
......
This diff is collapsed.
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
#include <algorithm> #include <algorithm>
#include <memory> #include <memory>
#include <string> #include <utility>
#include "ash/ime/ime_controller.h" #include "ash/ime/ime_controller.h"
#include "ash/login/login_screen_controller.h" #include "ash/login/login_screen_controller.h"
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include "ash/login/ui/lock_contents_view.h" #include "ash/login/ui/lock_contents_view.h"
#include "ash/login/ui/lock_screen.h" #include "ash/login/ui/lock_screen.h"
#include "ash/login/ui/login_data_dispatcher.h" #include "ash/login/ui/login_data_dispatcher.h"
#include "ash/login/ui/login_detachable_base_model.h"
#include "ash/login/ui/non_accessible_view.h" #include "ash/login/ui/non_accessible_view.h"
#include "ash/shell.h" #include "ash/shell.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
...@@ -232,6 +233,10 @@ class LockDebugView::DebugDataDispatcherTransformer ...@@ -232,6 +233,10 @@ class LockDebugView::DebugDataDispatcherTransformer
const mojom::EasyUnlockIconOptionsPtr& icon) override { const mojom::EasyUnlockIconOptionsPtr& icon) override {
debug_dispatcher_.ShowEasyUnlockIcon(user, icon); debug_dispatcher_.ShowEasyUnlockIcon(user, icon);
} }
void OnDetachableBasePairingStatusChanged(
DetachableBasePairingStatus pairing_status) override {
debug_dispatcher_.SetDetachableBasePairingStatus(pairing_status);
}
private: private:
// The debug overlay UI takes ground-truth data from |root_dispatcher_|, // The debug overlay UI takes ground-truth data from |root_dispatcher_|,
...@@ -252,8 +257,10 @@ class LockDebugView::DebugDataDispatcherTransformer ...@@ -252,8 +257,10 @@ class LockDebugView::DebugDataDispatcherTransformer
DISALLOW_COPY_AND_ASSIGN(DebugDataDispatcherTransformer); DISALLOW_COPY_AND_ASSIGN(DebugDataDispatcherTransformer);
}; };
LockDebugView::LockDebugView(mojom::TrayActionState initial_note_action_state, LockDebugView::LockDebugView(
LoginDataDispatcher* data_dispatcher) mojom::TrayActionState initial_note_action_state,
LoginDataDispatcher* data_dispatcher,
std::unique_ptr<LoginDetachableBaseModel> detachable_base_model)
: debug_data_dispatcher_(std::make_unique<DebugDataDispatcherTransformer>( : debug_data_dispatcher_(std::make_unique<DebugDataDispatcherTransformer>(
initial_note_action_state, initial_note_action_state,
data_dispatcher)) { data_dispatcher)) {
...@@ -261,7 +268,8 @@ LockDebugView::LockDebugView(mojom::TrayActionState initial_note_action_state, ...@@ -261,7 +268,8 @@ LockDebugView::LockDebugView(mojom::TrayActionState initial_note_action_state,
std::make_unique<views::BoxLayout>(views::BoxLayout::kHorizontal)); std::make_unique<views::BoxLayout>(views::BoxLayout::kHorizontal));
lock_ = new LockContentsView(initial_note_action_state, lock_ = new LockContentsView(initial_note_action_state,
debug_data_dispatcher_->debug_dispatcher()); debug_data_dispatcher_->debug_dispatcher(),
std::move(detachable_base_model));
AddChildView(lock_); AddChildView(lock_);
debug_row_ = new NonAccessibleView(); debug_row_ = new NonAccessibleView();
...@@ -326,7 +334,7 @@ void LockDebugView::ButtonPressed(views::Button* sender, ...@@ -326,7 +334,7 @@ void LockDebugView::ButtonPressed(views::Button* sender,
// Iteratively adds more info to the dev channel labels to test 7 permutations // Iteratively adds more info to the dev channel labels to test 7 permutations
// and then disables the button. // and then disables the button.
if (sender == add_dev_channel_info_) { if (sender == add_dev_channel_info_) {
DCHECK(num_dev_channel_info_clicks_ < 7u); DCHECK_LT(num_dev_channel_info_clicks_, 7u);
++num_dev_channel_info_clicks_; ++num_dev_channel_info_clicks_;
if (num_dev_channel_info_clicks_ == 7u) if (num_dev_channel_info_clicks_ == 7u)
add_dev_channel_info_->SetEnabled(false); add_dev_channel_info_->SetEnabled(false);
......
...@@ -6,8 +6,10 @@ ...@@ -6,8 +6,10 @@
#define ASH_LOGIN_UI_LOCK_DEBUG_VIEW_H_ #define ASH_LOGIN_UI_LOCK_DEBUG_VIEW_H_
#include <memory> #include <memory>
#include <string>
#include <vector> #include <vector>
#include "ash/detachable_base/detachable_base_pairing_status.h"
#include "ash/login/login_screen_controller.h" #include "ash/login/login_screen_controller.h"
#include "ui/views/controls/button/button.h" #include "ui/views/controls/button/button.h"
#include "ui/views/view.h" #include "ui/views/view.h"
...@@ -19,6 +21,7 @@ class MdTextButton; ...@@ -19,6 +21,7 @@ class MdTextButton;
namespace ash { namespace ash {
class LoginDataDispatcher; class LoginDataDispatcher;
class LoginDetachableBaseModel;
class LockContentsView; class LockContentsView;
namespace mojom { namespace mojom {
...@@ -28,8 +31,10 @@ enum class TrayActionState; ...@@ -28,8 +31,10 @@ enum class TrayActionState;
// Contains the debug UI row (ie, add user, toggle PIN buttons). // Contains the debug UI row (ie, add user, toggle PIN buttons).
class LockDebugView : public views::View, public views::ButtonListener { class LockDebugView : public views::View, public views::ButtonListener {
public: public:
LockDebugView(mojom::TrayActionState initial_note_action_state, LockDebugView(
LoginDataDispatcher* data_dispatcher); mojom::TrayActionState initial_note_action_state,
LoginDataDispatcher* data_dispatcher,
std::unique_ptr<LoginDetachableBaseModel> detachable_base_model);
~LockDebugView() override; ~LockDebugView() override;
// views::View: // views::View:
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include "ash/login/ui/lock_debug_view.h" #include "ash/login/ui/lock_debug_view.h"
#include "ash/login/ui/lock_window.h" #include "ash/login/ui/lock_window.h"
#include "ash/login/ui/login_data_dispatcher.h" #include "ash/login/ui/login_data_dispatcher.h"
#include "ash/login/ui/login_detachable_base_model.h"
#include "ash/public/cpp/login_constants.h" #include "ash/public/cpp/login_constants.h"
#include "ash/public/interfaces/session_controller.mojom.h" #include "ash/public/interfaces/session_controller.mojom.h"
#include "ash/root_window_controller.h" #include "ash/root_window_controller.h"
...@@ -72,17 +73,21 @@ void LockScreen::Show(ScreenType type) { ...@@ -72,17 +73,21 @@ void LockScreen::Show(ScreenType type) {
display::Screen::GetScreen()->GetPrimaryDisplay().bounds()); display::Screen::GetScreen()->GetPrimaryDisplay().bounds());
auto data_dispatcher = std::make_unique<LoginDataDispatcher>(); auto data_dispatcher = std::make_unique<LoginDataDispatcher>();
auto detachable_base_model = LoginDetachableBaseModel::Create(
Shell::Get()->detachable_base_handler(), data_dispatcher.get());
auto initial_note_action_state = auto initial_note_action_state =
ash::Shell::Get()->tray_action()->GetLockScreenNoteState(); Shell::Get()->tray_action()->GetLockScreenNoteState();
if (base::CommandLine::ForCurrentProcess()->HasSwitch( if (base::CommandLine::ForCurrentProcess()->HasSwitch(
chromeos::switches::kShowLoginDevOverlay)) { chromeos::switches::kShowLoginDevOverlay)) {
auto* debug_view = auto* debug_view =
new LockDebugView(initial_note_action_state, data_dispatcher.get()); new LockDebugView(initial_note_action_state, data_dispatcher.get(),
std::move(detachable_base_model));
instance_->contents_view_ = debug_view->lock(); instance_->contents_view_ = debug_view->lock();
instance_->window_->SetContentsView(debug_view); instance_->window_->SetContentsView(debug_view);
} else { } else {
instance_->contents_view_ = instance_->contents_view_ =
new LockContentsView(initial_note_action_state, data_dispatcher.get()); new LockContentsView(initial_note_action_state, data_dispatcher.get(),
std::move(detachable_base_model));
instance_->window_->SetContentsView(instance_->contents_view_); instance_->window_->SetContentsView(instance_->contents_view_);
} }
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include "ash/login/login_screen_controller.h" #include "ash/login/login_screen_controller.h"
#include "ash/login/mock_login_screen_client.h" #include "ash/login/mock_login_screen_client.h"
#include "ash/login/ui/fake_login_detachable_base_model.h"
#include "ash/login/ui/lock_contents_view.h" #include "ash/login/ui/lock_contents_view.h"
#include "ash/login/ui/login_test_base.h" #include "ash/login/ui/login_test_base.h"
#include "ash/login/ui/login_test_utils.h" #include "ash/login/ui/login_test_utils.h"
...@@ -102,8 +103,9 @@ testing::AssertionResult VerifyNotFocused(views::View* view) { ...@@ -102,8 +103,9 @@ testing::AssertionResult VerifyNotFocused(views::View* view) {
// Verifies that the password input box has focus. // Verifies that the password input box has focus.
TEST_F(LockScreenSanityTest, PasswordIsInitiallyFocused) { TEST_F(LockScreenSanityTest, PasswordIsInitiallyFocused) {
// Build lock screen. // Build lock screen.
auto* contents = new LockContentsView(mojom::TrayActionState::kNotAvailable, auto* contents = new LockContentsView(
data_dispatcher()); mojom::TrayActionState::kNotAvailable, data_dispatcher(),
std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
// The lock screen requires at least one user. // The lock screen requires at least one user.
SetUserCount(1); SetUserCount(1);
...@@ -118,8 +120,9 @@ TEST_F(LockScreenSanityTest, PasswordIsInitiallyFocused) { ...@@ -118,8 +120,9 @@ TEST_F(LockScreenSanityTest, PasswordIsInitiallyFocused) {
// Verifies submitting the password invokes mojo lock screen client. // Verifies submitting the password invokes mojo lock screen client.
TEST_F(LockScreenSanityTest, PasswordSubmitCallsLoginScreenClient) { TEST_F(LockScreenSanityTest, PasswordSubmitCallsLoginScreenClient) {
// Build lock screen. // Build lock screen.
auto* contents = new LockContentsView(mojom::TrayActionState::kNotAvailable, auto* contents = new LockContentsView(
data_dispatcher()); mojom::TrayActionState::kNotAvailable, data_dispatcher(),
std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
// The lock screen requires at least one user. // The lock screen requires at least one user.
SetUserCount(1); SetUserCount(1);
...@@ -144,8 +147,9 @@ TEST_F(LockScreenSanityTest, ...@@ -144,8 +147,9 @@ TEST_F(LockScreenSanityTest,
PasswordSubmitClearsPasswordAfterFailedAuthentication) { PasswordSubmitClearsPasswordAfterFailedAuthentication) {
std::unique_ptr<MockLoginScreenClient> client = BindMockLoginScreenClient(); std::unique_ptr<MockLoginScreenClient> client = BindMockLoginScreenClient();
auto* contents = new LockContentsView(mojom::TrayActionState::kAvailable, auto* contents = new LockContentsView(
data_dispatcher()); mojom::TrayActionState::kAvailable, data_dispatcher(),
std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
SetUserCount(1); SetUserCount(1);
std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents); std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
LoginPasswordView::TestApi password_test_api = LoginPasswordView::TestApi password_test_api =
...@@ -197,8 +201,9 @@ TEST_F(LockScreenSanityTest, TabGoesFromLockToShelfAndBackToLock) { ...@@ -197,8 +201,9 @@ TEST_F(LockScreenSanityTest, TabGoesFromLockToShelfAndBackToLock) {
session_manager::SessionState::LOCKED); session_manager::SessionState::LOCKED);
// Create lock screen. // Create lock screen.
auto* lock = new LockContentsView(mojom::TrayActionState::kNotAvailable, auto* lock = new LockContentsView(
data_dispatcher()); mojom::TrayActionState::kNotAvailable, data_dispatcher(),
std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
SetUserCount(1); SetUserCount(1);
std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(lock); std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(lock);
views::View* shelf = Shelf::ForWindow(lock->GetWidget()->GetNativeWindow()) views::View* shelf = Shelf::ForWindow(lock->GetWidget()->GetNativeWindow())
...@@ -228,8 +233,9 @@ TEST_F(LockScreenSanityTest, ShiftTabGoesFromLockToStatusAreaAndBackToLock) { ...@@ -228,8 +233,9 @@ TEST_F(LockScreenSanityTest, ShiftTabGoesFromLockToStatusAreaAndBackToLock) {
GetSessionControllerClient()->SetSessionState( GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::LOCKED); session_manager::SessionState::LOCKED);
auto* lock = new LockContentsView(mojom::TrayActionState::kNotAvailable, auto* lock = new LockContentsView(
data_dispatcher()); mojom::TrayActionState::kNotAvailable, data_dispatcher(),
std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
SetUserCount(1); SetUserCount(1);
std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(lock); std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(lock);
views::View* status_area = views::View* status_area =
...@@ -258,8 +264,9 @@ TEST_F(LockScreenSanityTest, TabWithLockScreenAppActive) { ...@@ -258,8 +264,9 @@ TEST_F(LockScreenSanityTest, TabWithLockScreenAppActive) {
GetSessionControllerClient()->SetSessionState( GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::LOCKED); session_manager::SessionState::LOCKED);
auto* lock = new LockContentsView(mojom::TrayActionState::kNotAvailable, auto* lock = new LockContentsView(
data_dispatcher()); mojom::TrayActionState::kNotAvailable, data_dispatcher(),
std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
SetUserCount(1); SetUserCount(1);
std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(lock); std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(lock);
...@@ -329,8 +336,9 @@ TEST_F(LockScreenSanityTest, FocusLockScreenWhenLockScreenAppExit) { ...@@ -329,8 +336,9 @@ TEST_F(LockScreenSanityTest, FocusLockScreenWhenLockScreenAppExit) {
// Set up lock screen. // Set up lock screen.
GetSessionControllerClient()->SetSessionState( GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::LOCKED); session_manager::SessionState::LOCKED);
auto* lock = new LockContentsView(mojom::TrayActionState::kNotAvailable, auto* lock = new LockContentsView(
data_dispatcher()); mojom::TrayActionState::kNotAvailable, data_dispatcher(),
std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
SetUserCount(1); SetUserCount(1);
std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(lock); std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(lock);
......
...@@ -4,6 +4,9 @@ ...@@ -4,6 +4,9 @@
#include "ash/login/ui/login_bubble.h" #include "ash/login/ui/login_bubble.h"
#include <memory>
#include <utility>
#include "ash/ash_constants.h" #include "ash/ash_constants.h"
#include "ash/focus_cycler.h" #include "ash/focus_cycler.h"
#include "ash/login/ui/layout_util.h" #include "ash/login/ui/layout_util.h"
...@@ -86,7 +89,7 @@ views::Label* CreateLabel(const base::string16& message, SkColor color) { ...@@ -86,7 +89,7 @@ views::Label* CreateLabel(const base::string16& message, SkColor color) {
class LoginErrorBubbleView : public LoginBaseBubbleView { class LoginErrorBubbleView : public LoginBaseBubbleView {
public: public:
LoginErrorBubbleView(views::StyledLabel* label, views::View* anchor_view) LoginErrorBubbleView(views::View* content, views::View* anchor_view)
: LoginBaseBubbleView(anchor_view) { : LoginBaseBubbleView(anchor_view) {
SetLayoutManager(std::make_unique<views::BoxLayout>( SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::kVertical, gfx::Insets(), views::BoxLayout::kVertical, gfx::Insets(),
...@@ -104,8 +107,7 @@ class LoginErrorBubbleView : public LoginBaseBubbleView { ...@@ -104,8 +107,7 @@ class LoginErrorBubbleView : public LoginBaseBubbleView {
alert_view->AddChildView(alert_icon); alert_view->AddChildView(alert_icon);
AddChildView(alert_view); AddChildView(alert_view);
label->set_auto_color_readability_enabled(false); AddChildView(content);
AddChildView(label);
} }
~LoginErrorBubbleView() override = default; ~LoginErrorBubbleView() override = default;
...@@ -361,12 +363,15 @@ LoginBubble::~LoginBubble() { ...@@ -361,12 +363,15 @@ LoginBubble::~LoginBubble() {
} }
} }
void LoginBubble::ShowErrorBubble(views::StyledLabel* label, void LoginBubble::ShowErrorBubble(views::View* content,
views::View* anchor_view) { views::View* anchor_view,
uint32_t flags) {
if (bubble_view_) if (bubble_view_)
CloseImmediately(); CloseImmediately();
bubble_view_ = new LoginErrorBubbleView(label, anchor_view); flags_ = flags;
bubble_view_ = new LoginErrorBubbleView(content, anchor_view);
Show(); Show();
} }
...@@ -381,6 +386,7 @@ void LoginBubble::ShowUserMenu(const base::string16& username, ...@@ -381,6 +386,7 @@ void LoginBubble::ShowUserMenu(const base::string16& username,
if (bubble_view_) if (bubble_view_)
CloseImmediately(); CloseImmediately();
flags_ = kFlagsNone;
bubble_opener_ = bubble_opener; bubble_opener_ = bubble_opener;
bubble_view_ = bubble_view_ =
new LoginUserMenuView(this, username, email, type, is_owner, anchor_view, new LoginUserMenuView(this, username, email, type, is_owner, anchor_view,
...@@ -400,6 +406,7 @@ void LoginBubble::ShowTooltip(const base::string16& message, ...@@ -400,6 +406,7 @@ void LoginBubble::ShowTooltip(const base::string16& message,
if (bubble_view_) if (bubble_view_)
CloseImmediately(); CloseImmediately();
flags_ = kFlagsNone;
bubble_view_ = new LoginTooltipView(message, anchor_view); bubble_view_ = new LoginTooltipView(message, anchor_view);
Show(); Show();
} }
...@@ -415,6 +422,7 @@ bool LoginBubble::IsVisible() { ...@@ -415,6 +422,7 @@ bool LoginBubble::IsVisible() {
void LoginBubble::OnWidgetClosing(views::Widget* widget) { void LoginBubble::OnWidgetClosing(views::Widget* widget) {
bubble_opener_ = nullptr; bubble_opener_ = nullptr;
bubble_view_ = nullptr; bubble_view_ = nullptr;
flags_ = kFlagsNone;
widget->RemoveObserver(this); widget->RemoveObserver(this);
} }
...@@ -447,7 +455,9 @@ void LoginBubble::OnKeyEvent(ui::KeyEvent* event) { ...@@ -447,7 +455,9 @@ void LoginBubble::OnKeyEvent(ui::KeyEvent* event) {
if (bubble_view_->GetWidget()->IsActive()) if (bubble_view_->GetWidget()->IsActive())
return; return;
Close(); if (!(flags_ & kFlagPersistent)) {
Close();
}
} }
void LoginBubble::OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) { void LoginBubble::OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) {
...@@ -461,9 +471,10 @@ void LoginBubble::OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) { ...@@ -461,9 +471,10 @@ void LoginBubble::OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) {
void LoginBubble::Show() { void LoginBubble::Show() {
DCHECK(bubble_view_); DCHECK(bubble_view_);
views::BubbleDialogDelegateView::CreateBubble(bubble_view_)->Show(); views::BubbleDialogDelegateView::CreateBubble(bubble_view_)->ShowInactive();
bubble_view_->SetAlignment(views::BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE); bubble_view_->SetAlignment(views::BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE);
bubble_view_->GetWidget()->AddObserver(this); bubble_view_->GetWidget()->AddObserver(this);
bubble_view_->GetWidget()->StackAtTop();
ScheduleAnimation(true /*visible*/); ScheduleAnimation(true /*visible*/);
...@@ -498,7 +509,8 @@ void LoginBubble::ProcessPressedEvent(const ui::LocatedEvent* event) { ...@@ -498,7 +509,8 @@ void LoginBubble::ProcessPressedEvent(const ui::LocatedEvent* event) {
return; return;
} }
Close(); if (!(flags_ & kFlagPersistent))
Close();
} }
void LoginBubble::ScheduleAnimation(bool visible) { void LoginBubble::ScheduleAnimation(bool visible) {
......
...@@ -13,10 +13,6 @@ ...@@ -13,10 +13,6 @@
#include "ui/views/view.h" #include "ui/views/view.h"
#include "ui/views/widget/widget_observer.h" #include "ui/views/widget/widget_observer.h"
namespace views {
class StyledLabel;
}
namespace ash { namespace ash {
class LoginButton; class LoginButton;
...@@ -29,12 +25,20 @@ class ASH_EXPORT LoginBubble : public views::WidgetObserver, ...@@ -29,12 +25,20 @@ class ASH_EXPORT LoginBubble : public views::WidgetObserver,
public: public:
static const int kUserMenuRemoveUserButtonIdForTest; static const int kUserMenuRemoveUserButtonIdForTest;
// Flags passed to ShowErrorBubble().
static constexpr uint32_t kFlagsNone = 0;
// If set, the shown error bubble will not be closed due to an unrelated user
// action - e.g. the bubble will not be closed if the user starts typing.
static constexpr uint32_t kFlagPersistent = 1 << 0;
LoginBubble(); LoginBubble();
~LoginBubble() override; ~LoginBubble() override;
// Shows an error bubble for authentication failure. // Shows an error bubble for authentication failure.
// |anchor_view| is the anchor for placing the bubble view. // |anchor_view| is the anchor for placing the bubble view.
void ShowErrorBubble(views::StyledLabel* label, views::View* anchor_view); void ShowErrorBubble(views::View* content,
views::View* anchor_view,
uint32_t flags);
// Shows a user menu bubble. // Shows a user menu bubble.
// |anchor_view| is the anchor for placing the bubble view. // |anchor_view| is the anchor for placing the bubble view.
...@@ -91,6 +95,9 @@ class ASH_EXPORT LoginBubble : public views::WidgetObserver, ...@@ -91,6 +95,9 @@ class ASH_EXPORT LoginBubble : public views::WidgetObserver,
// Starts show/hide animation. // Starts show/hide animation.
void ScheduleAnimation(bool visible); void ScheduleAnimation(bool visible);
// Flags passed to ShowErrorBubble().
uint32_t flags_ = kFlagsNone;
LoginBaseBubbleView* bubble_view_ = nullptr; LoginBaseBubbleView* bubble_view_ = nullptr;
// A button that could open/close the bubble. // A button that could open/close the bubble.
......
...@@ -3,13 +3,16 @@ ...@@ -3,13 +3,16 @@
// found in the LICENSE file. // found in the LICENSE file.
#include <memory> #include <memory>
#include <utility>
#include "ash/login/ui/login_bubble.h" #include "ash/login/ui/login_bubble.h"
#include "ash/login/ui/login_button.h" #include "ash/login/ui/login_button.h"
#include "ash/login/ui/login_test_base.h" #include "ash/login/ui/login_test_base.h"
#include "base/strings/utf_string_conversions.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/test/event_generator.h" #include "ui/events/test/event_generator.h"
#include "ui/views/animation/test/ink_drop_host_view_test_api.h" #include "ui/views/animation/test/ink_drop_host_view_test_api.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h" #include "ui/views/layout/box_layout.h"
#include "ui/views/widget/widget.h" #include "ui/views/widget/widget.h"
...@@ -236,4 +239,95 @@ TEST_F(LoginBubbleTest, RemoveUserRequiresTwoActivations) { ...@@ -236,4 +239,95 @@ TEST_F(LoginBubbleTest, RemoveUserRequiresTwoActivations) {
EXPECT_TRUE(remove_called); EXPECT_TRUE(remove_called);
} }
TEST_F(LoginBubbleTest, ErrorBubbleKeyEventHandling) {
ui::test::EventGenerator& generator = GetEventGenerator();
EXPECT_FALSE(bubble_->IsVisible());
views::Label* error_text = new views::Label(base::ASCIIToUTF16("Error text"));
bubble_->ShowErrorBubble(error_text, container_, LoginBubble::kFlagsNone);
EXPECT_TRUE(bubble_->IsVisible());
// Verifies that key event on a view other than error closes the error bubble.
other_view_->RequestFocus();
generator.PressKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
EXPECT_FALSE(bubble_->IsVisible());
}
TEST_F(LoginBubbleTest, ErrorBubbleMouseEventHandling) {
ui::test::EventGenerator& generator = GetEventGenerator();
EXPECT_FALSE(bubble_->IsVisible());
views::Label* error_text = new views::Label(base::ASCIIToUTF16("Error text"));
bubble_->ShowErrorBubble(error_text, container_, LoginBubble::kFlagsNone);
EXPECT_TRUE(bubble_->IsVisible());
// Verifies that mouse event on the bubble itself won't close the bubble.
generator.MoveMouseTo(
bubble_->bubble_view_for_test()->GetBoundsInScreen().CenterPoint());
generator.ClickLeftButton();
EXPECT_TRUE(bubble_->IsVisible());
// Verifies that mouse event on the other view will close the bubble.
generator.MoveMouseTo(other_view_->GetBoundsInScreen().CenterPoint());
generator.ClickLeftButton();
EXPECT_FALSE(bubble_->IsVisible());
}
TEST_F(LoginBubbleTest, ErrorBubbleGestureEventHandling) {
ui::test::EventGenerator& generator = GetEventGenerator();
EXPECT_FALSE(bubble_->IsVisible());
views::Label* error_text = new views::Label(base::ASCIIToUTF16("Error text"));
bubble_->ShowErrorBubble(error_text, container_, LoginBubble::kFlagsNone);
EXPECT_TRUE(bubble_->IsVisible());
// Verifies that gesture event on the bubble itself won't close the bubble.
generator.GestureTapAt(
bubble_->bubble_view_for_test()->GetBoundsInScreen().CenterPoint());
EXPECT_TRUE(bubble_->IsVisible());
// Verifies that gesture event on the other view will close the bubble.
generator.GestureTapAt(other_view_->GetBoundsInScreen().CenterPoint());
EXPECT_FALSE(bubble_->IsVisible());
}
TEST_F(LoginBubbleTest, PersistentErrorBubbleEventHandling) {
ui::test::EventGenerator& generator = GetEventGenerator();
EXPECT_FALSE(bubble_->IsVisible());
views::Label* error_text = new views::Label(base::ASCIIToUTF16("Error text"));
bubble_->ShowErrorBubble(error_text, container_,
LoginBubble::kFlagPersistent);
EXPECT_TRUE(bubble_->IsVisible());
// Verifies that mouse event on the bubble itself won't close the bubble.
generator.MoveMouseTo(
bubble_->bubble_view_for_test()->GetBoundsInScreen().CenterPoint());
generator.ClickLeftButton();
EXPECT_TRUE(bubble_->IsVisible());
// Verifies that mouse event on the other view won't close the bubble.
generator.MoveMouseTo(other_view_->GetBoundsInScreen().CenterPoint());
generator.ClickLeftButton();
EXPECT_TRUE(bubble_->IsVisible());
// Verifies that gesture event on the bubble itself won't close the bubble.
generator.GestureTapAt(
bubble_->bubble_view_for_test()->GetBoundsInScreen().CenterPoint());
EXPECT_TRUE(bubble_->IsVisible());
// Verifies that gesture event on the other view won't close the bubble.
generator.GestureTapAt(other_view_->GetBoundsInScreen().CenterPoint());
EXPECT_TRUE(bubble_->IsVisible());
// Verifies that key event on the other view won't close the bubble.
other_view_->RequestFocus();
generator.PressKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
EXPECT_TRUE(bubble_->IsVisible());
// LoginBubble::Close should close the persistent error bubble.
bubble_->Close();
EXPECT_FALSE(bubble_->IsVisible());
}
} // namespace ash } // namespace ash
...@@ -41,6 +41,9 @@ void LoginDataDispatcher::Observer::OnPublicSessionLocalesChanged( ...@@ -41,6 +41,9 @@ void LoginDataDispatcher::Observer::OnPublicSessionLocalesChanged(
const std::string& default_locale, const std::string& default_locale,
bool show_advanced_view) {} bool show_advanced_view) {}
void LoginDataDispatcher::Observer::OnDetachableBasePairingStatusChanged(
DetachableBasePairingStatus pairing_status) {}
LoginDataDispatcher::LoginDataDispatcher() = default; LoginDataDispatcher::LoginDataDispatcher() = default;
LoginDataDispatcher::~LoginDataDispatcher() = default; LoginDataDispatcher::~LoginDataDispatcher() = default;
...@@ -111,4 +114,10 @@ void LoginDataDispatcher::SetPublicSessionLocales( ...@@ -111,4 +114,10 @@ void LoginDataDispatcher::SetPublicSessionLocales(
} }
} }
void LoginDataDispatcher::SetDetachableBasePairingStatus(
DetachableBasePairingStatus pairing_status) {
for (auto& observer : observers_)
observer.OnDetachableBasePairingStatusChanged(pairing_status);
}
} // namespace ash } // namespace ash
...@@ -5,9 +5,12 @@ ...@@ -5,9 +5,12 @@
#ifndef ASH_LOGIN_UI_LOGIN_DATA_DISPATCHER_H_ #ifndef ASH_LOGIN_UI_LOGIN_DATA_DISPATCHER_H_
#define ASH_LOGIN_UI_LOGIN_DATA_DISPATCHER_H_ #define ASH_LOGIN_UI_LOGIN_DATA_DISPATCHER_H_
#include <memory>
#include <string>
#include <vector> #include <vector>
#include "ash/ash_export.h" #include "ash/ash_export.h"
#include "ash/detachable_base/detachable_base_pairing_status.h"
#include "ash/public/interfaces/login_user_info.mojom.h" #include "ash/public/interfaces/login_user_info.mojom.h"
#include "ash/public/interfaces/tray_action.mojom.h" #include "ash/public/interfaces/tray_action.mojom.h"
#include "base/macros.h" #include "base/macros.h"
...@@ -77,6 +80,11 @@ class ASH_EXPORT LoginDataDispatcher { ...@@ -77,6 +80,11 @@ class ASH_EXPORT LoginDataDispatcher {
const base::ListValue& locales, const base::ListValue& locales,
const std::string& default_locale, const std::string& default_locale,
bool show_advanced_view); bool show_advanced_view);
// Called when the pairing status of detachable base changes - e.g. when the
// base is attached or detached.
virtual void OnDetachableBasePairingStatusChanged(
DetachableBasePairingStatus pairing_status);
}; };
LoginDataDispatcher(); LoginDataDispatcher();
...@@ -100,6 +108,8 @@ class ASH_EXPORT LoginDataDispatcher { ...@@ -100,6 +108,8 @@ class ASH_EXPORT LoginDataDispatcher {
std::unique_ptr<base::ListValue> locales, std::unique_ptr<base::ListValue> locales,
const std::string& default_locale, const std::string& default_locale,
bool show_advanced_view); bool show_advanced_view);
void SetDetachableBasePairingStatus(
DetachableBasePairingStatus pairing_status);
private: private:
base::ObserverList<Observer> observers_; base::ObserverList<Observer> observers_;
......
// Copyright 2018 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/login/ui/login_detachable_base_model.h"
#include "ash/detachable_base/detachable_base_handler.h"
#include "ash/detachable_base/detachable_base_observer.h"
#include "ash/detachable_base/detachable_base_pairing_status.h"
#include "ash/login/ui/login_data_dispatcher.h"
#include "ash/public/interfaces/user_info.mojom.h"
#include "base/macros.h"
#include "base/scoped_observer.h"
namespace ash {
namespace {
class LoginDetachableBaseModelImpl : public LoginDetachableBaseModel,
public DetachableBaseObserver {
public:
LoginDetachableBaseModelImpl(DetachableBaseHandler* detachable_base_handler,
LoginDataDispatcher* login_data_dispatcher)
: detachable_base_handler_(detachable_base_handler),
detachable_base_observer_(this),
login_data_dispatcher_(login_data_dispatcher) {
detachable_base_observer_.Add(detachable_base_handler);
}
~LoginDetachableBaseModelImpl() override = default;
// LoginDetachableBaseModel:
DetachableBasePairingStatus GetPairingStatus() override {
return detachable_base_handler_->GetPairingStatus();
}
bool PairedBaseMatchesLastUsedByUser(
const mojom::UserInfo& user_info) override {
return detachable_base_handler_->PairedBaseMatchesLastUsedByUser(user_info);
}
bool SetPairedBaseAsLastUsedByUser(
const mojom::UserInfo& user_info) override {
return detachable_base_handler_->SetPairedBaseAsLastUsedByUser(user_info);
}
// DetachableBaseObserver:
void OnDetachableBasePairingStatusChanged(
DetachableBasePairingStatus pairing_status) override {
login_data_dispatcher_->SetDetachableBasePairingStatus(pairing_status);
}
private:
DetachableBaseHandler* detachable_base_handler_;
ScopedObserver<DetachableBaseHandler, DetachableBaseObserver>
detachable_base_observer_;
LoginDataDispatcher* login_data_dispatcher_;
DISALLOW_COPY_AND_ASSIGN(LoginDetachableBaseModelImpl);
};
} // namespace
// static
std::unique_ptr<LoginDetachableBaseModel> LoginDetachableBaseModel::Create(
DetachableBaseHandler* detachable_base_handler,
LoginDataDispatcher* login_data_dispatcher) {
return std::make_unique<LoginDetachableBaseModelImpl>(detachable_base_handler,
login_data_dispatcher);
}
} // namespace ash
// Copyright 2018 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_LOGIN_UI_LOGIN_DETACHABLE_BASE_MODEL_H_
#define ASH_LOGIN_UI_LOGIN_DETACHABLE_BASE_MODEL_H_
#include <memory>
#include "ash/ash_export.h"
namespace ash {
class DetachableBaseHandler;
enum class DetachableBasePairingStatus;
class LoginDataDispatcher;
namespace mojom {
class UserInfo;
}
// Wrapper around ash::DetachableBaseHandler used by login UI. Exposed as an
// interface to ease faking the detachable base state in login UI tests, and in
// debug login view.
//
// It observes the detachable base pairing status, and informs login data
// dispatcher of pairing status changes.
// It provides methods for comparing the current base to the last based used by
// a user, and setting the last base used by the user.
class ASH_EXPORT LoginDetachableBaseModel {
public:
virtual ~LoginDetachableBaseModel() = default;
static std::unique_ptr<LoginDetachableBaseModel> Create(
DetachableBaseHandler* detachable_base_handler,
LoginDataDispatcher* login_data_dispatcher);
// Returns the current detachable base pairing status.
virtual DetachableBasePairingStatus GetPairingStatus() = 0;
// Checks if the currently paired base is different than the last base used by
// the user.
virtual bool PairedBaseMatchesLastUsedByUser(
const mojom::UserInfo& user_info) = 0;
// Sets the currently paired base as the last base used by the user.
virtual bool SetPairedBaseAsLastUsedByUser(
const mojom::UserInfo& user_info) = 0;
};
} // namespace ash
#endif // ASH_LOGIN_UI_LOGIN_DETACHABLE_BASE_MODEL_H_
...@@ -4,8 +4,12 @@ ...@@ -4,8 +4,12 @@
#include "ash/metrics/login_metrics_recorder.h" #include "ash/metrics/login_metrics_recorder.h"
#include <memory>
#include <string>
#include "ash/login/login_screen_controller.h" #include "ash/login/login_screen_controller.h"
#include "ash/login/mock_login_screen_client.h" #include "ash/login/mock_login_screen_client.h"
#include "ash/login/ui/fake_login_detachable_base_model.h"
#include "ash/login/ui/lock_contents_view.h" #include "ash/login/ui/lock_contents_view.h"
#include "ash/login/ui/lock_screen.h" #include "ash/login/ui/lock_screen.h"
#include "ash/login/ui/login_auth_user_view.h" #include "ash/login/ui/login_auth_user_view.h"
...@@ -106,8 +110,9 @@ TEST_F(LoginMetricsRecorderTest, UnlockAttempts) { ...@@ -106,8 +110,9 @@ TEST_F(LoginMetricsRecorderTest, UnlockAttempts) {
std::unique_ptr<MockLoginScreenClient> client = BindMockLoginScreenClient(); std::unique_ptr<MockLoginScreenClient> client = BindMockLoginScreenClient();
client->set_authenticate_user_callback_result(false); client->set_authenticate_user_callback_result(false);
auto* contents = new LockContentsView(mojom::TrayActionState::kNotAvailable, auto* contents = new LockContentsView(
data_dispatcher()); mojom::TrayActionState::kNotAvailable, data_dispatcher(),
std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
LockContentsView::TestApi test_api(contents); LockContentsView::TestApi test_api(contents);
SetUserCount(1); SetUserCount(1);
std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents); std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
...@@ -184,8 +189,9 @@ TEST_F(LoginMetricsRecorderTest, NoteActionButtonClick) { ...@@ -184,8 +189,9 @@ TEST_F(LoginMetricsRecorderTest, NoteActionButtonClick) {
GetSessionControllerClient()->SetSessionState( GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::LOCKED); session_manager::SessionState::LOCKED);
auto* contents = new LockContentsView(mojom::TrayActionState::kAvailable, auto* contents = new LockContentsView(
data_dispatcher()); mojom::TrayActionState::kAvailable, data_dispatcher(),
std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
SetUserCount(1); SetUserCount(1);
std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents); std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment