Commit 1a9cce9e authored by David Munro's avatar David Munro Committed by Commit Bot

Show a toast when using a virtual keyboard in crostini

Bug: chromium:962848
Test: autoninja -C out/Default unit_tests && ./out/Default/unit_tests
Test: Launch xclock with virtual keyboard hidden and showing
Change-Id: I358c31bddd7607de161f1c71a7be4ca18eb581f7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1746023
Auto-Submit: David Munro <davidmunro@google.com>
Commit-Queue: Nicholas Verne <nverne@chromium.org>
Reviewed-by: default avatarNicholas Verne <nverne@chromium.org>
Cr-Commit-Position: refs/heads/master@{#686299}
parent c61c3228
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include <utility> #include <utility>
#include "ash/public/cpp/app_types.h" #include "ash/public/cpp/app_types.h"
#include "ash/public/cpp/keyboard/keyboard_controller.h"
#include "ash/public/cpp/tablet_mode.h" #include "ash/public/cpp/tablet_mode.h"
#include "ash/public/cpp/toast_manager.h" #include "ash/public/cpp/toast_manager.h"
#include "base/logging.h" #include "base/logging.h"
...@@ -29,12 +30,14 @@ CrostiniUnsupportedActionNotifier::CrostiniUnsupportedActionNotifier( ...@@ -29,12 +30,14 @@ CrostiniUnsupportedActionNotifier::CrostiniUnsupportedActionNotifier(
delegate_->AddTabletModeObserver(this); delegate_->AddTabletModeObserver(this);
delegate_->AddFocusObserver(this); delegate_->AddFocusObserver(this);
delegate_->AddInputMethodObserver(this); delegate_->AddInputMethodObserver(this);
delegate_->AddKeyboardControllerObserver(this);
} }
CrostiniUnsupportedActionNotifier::~CrostiniUnsupportedActionNotifier() { CrostiniUnsupportedActionNotifier::~CrostiniUnsupportedActionNotifier() {
delegate_->RemoveTabletModeObserver(this); delegate_->RemoveTabletModeObserver(this);
delegate_->RemoveFocusObserver(this); delegate_->RemoveFocusObserver(this);
delegate_->RemoveInputMethodObserver(this); delegate_->RemoveInputMethodObserver(this);
delegate_->RemoveKeyboardControllerObserver(this);
} }
// Testing on using Debian/stretch on Eve shows Crostini supports all tested xkb // Testing on using Debian/stretch on Eve shows Crostini supports all tested xkb
...@@ -62,12 +65,20 @@ void CrostiniUnsupportedActionNotifier::InputMethodChanged( ...@@ -62,12 +65,20 @@ void CrostiniUnsupportedActionNotifier::InputMethodChanged(
ShowIMEUnsupportedNotifictionIfNeeded(); ShowIMEUnsupportedNotifictionIfNeeded();
} }
void CrostiniUnsupportedActionNotifier::OnKeyboardVisibilityChanged(
bool visible) {
if (visible) {
ShowVirtualKeyboardUnsupportedNotifictionIfNeeded();
}
}
void CrostiniUnsupportedActionNotifier:: void CrostiniUnsupportedActionNotifier::
ShowVirtualKeyboardUnsupportedNotifictionIfNeeded() { ShowVirtualKeyboardUnsupportedNotifictionIfNeeded() {
if (virtual_keyboard_unsupported_message_shown_) { if (virtual_keyboard_unsupported_message_shown_) {
return; return;
} }
if (delegate_->IsInTabletMode() && delegate_->IsFocusedWindowCrostini()) { if ((delegate_->IsInTabletMode() || delegate_->IsVirtualKeyboardVisible()) &&
delegate_->IsFocusedWindowCrostini()) {
ash::ToastData data = { ash::ToastData data = {
/*id=*/"VKUnsupportedInCrostini", /*id=*/"VKUnsupportedInCrostini",
/*text=*/ /*text=*/
...@@ -124,6 +135,10 @@ CrostiniUnsupportedActionNotifier::Delegate::GetCurrentInputMethod() { ...@@ -124,6 +135,10 @@ CrostiniUnsupportedActionNotifier::Delegate::GetCurrentInputMethod() {
->GetCurrentInputMethod(); ->GetCurrentInputMethod();
} }
bool CrostiniUnsupportedActionNotifier::Delegate::IsVirtualKeyboardVisible() {
return ash::KeyboardController::Get()->IsKeyboardVisible();
}
void CrostiniUnsupportedActionNotifier::Delegate::ShowToast( void CrostiniUnsupportedActionNotifier::Delegate::ShowToast(
const ash::ToastData& toast_data) { const ash::ToastData& toast_data) {
ash::ToastManager::Get()->Show(toast_data); ash::ToastManager::Get()->Show(toast_data);
...@@ -175,4 +190,14 @@ void CrostiniUnsupportedActionNotifier::Delegate::RemoveInputMethodObserver( ...@@ -175,4 +190,14 @@ void CrostiniUnsupportedActionNotifier::Delegate::RemoveInputMethodObserver(
chromeos::input_method::InputMethodManager::Get()->RemoveObserver(observer); chromeos::input_method::InputMethodManager::Get()->RemoveObserver(observer);
} }
void CrostiniUnsupportedActionNotifier::Delegate::AddKeyboardControllerObserver(
ash::KeyboardControllerObserver* observer) {
ash::KeyboardController::Get()->AddObserver(observer);
}
void CrostiniUnsupportedActionNotifier::Delegate::
RemoveKeyboardControllerObserver(
ash::KeyboardControllerObserver* observer) {
ash::KeyboardController::Get()->RemoveObserver(observer);
}
} // namespace crostini } // namespace crostini
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
#include "ash/public/cpp/tablet_mode_observer.h" #include "ash/public/cpp/tablet_mode_observer.h"
#include "ash/public/cpp/toast_data.h" #include "ash/public/cpp/toast_data.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
...@@ -23,7 +24,8 @@ namespace crostini { ...@@ -23,7 +24,8 @@ namespace crostini {
class CrostiniUnsupportedActionNotifier class CrostiniUnsupportedActionNotifier
: public ash::TabletModeObserver, : public ash::TabletModeObserver,
public aura::client::FocusChangeObserver, public aura::client::FocusChangeObserver,
public chromeos::input_method::InputMethodManager::Observer { public chromeos::input_method::InputMethodManager::Observer,
public ash::KeyboardControllerObserver {
public: public:
// Adapter around external integrations which we can mock out for testing, // Adapter around external integrations which we can mock out for testing,
// stateless. // stateless.
...@@ -42,6 +44,9 @@ class CrostiniUnsupportedActionNotifier ...@@ -42,6 +44,9 @@ class CrostiniUnsupportedActionNotifier
virtual chromeos::input_method::InputMethodDescriptor virtual chromeos::input_method::InputMethodDescriptor
GetCurrentInputMethod(); GetCurrentInputMethod();
// Is the current virtual keyboard visible.
virtual bool IsVirtualKeyboardVisible();
// Shows a toast to the user. // Shows a toast to the user.
virtual void ShowToast(const ash::ToastData& toast_data); virtual void ShowToast(const ash::ToastData& toast_data);
...@@ -59,6 +64,10 @@ class CrostiniUnsupportedActionNotifier ...@@ -59,6 +64,10 @@ class CrostiniUnsupportedActionNotifier
chromeos::input_method::InputMethodManager::Observer* observer); chromeos::input_method::InputMethodManager::Observer* observer);
virtual void RemoveInputMethodObserver( virtual void RemoveInputMethodObserver(
chromeos::input_method::InputMethodManager::Observer* observer); chromeos::input_method::InputMethodManager::Observer* observer);
virtual void AddKeyboardControllerObserver(
ash::KeyboardControllerObserver* observer);
virtual void RemoveKeyboardControllerObserver(
ash::KeyboardControllerObserver* observer);
}; };
CrostiniUnsupportedActionNotifier(); CrostiniUnsupportedActionNotifier();
...@@ -78,6 +87,9 @@ class CrostiniUnsupportedActionNotifier ...@@ -78,6 +87,9 @@ class CrostiniUnsupportedActionNotifier
Profile* profile, Profile* profile,
bool show_message) override; bool show_message) override;
// ash::KeyboardControllerObserver:
void OnKeyboardVisibilityChanged(bool visible) override;
Delegate* get_delegate_for_testing() { return delegate_.get(); } Delegate* get_delegate_for_testing() { return delegate_.get(); }
private: private:
...@@ -87,8 +99,8 @@ class CrostiniUnsupportedActionNotifier ...@@ -87,8 +99,8 @@ class CrostiniUnsupportedActionNotifier
void ShowVirtualKeyboardUnsupportedNotifictionIfNeeded(); void ShowVirtualKeyboardUnsupportedNotifictionIfNeeded();
// If the user is trying to use an unsupported IME with a crostini app and if // If the user is trying to use an unsupported IME with a crostini app and if
// they haven't already been notified that its not supported, notify them. // they haven't already been notified that it's not supported, notify them.
// Generally Crostini supports IMEs with 2:1 mappings betweens keys and glyphs // Generally Crostini supports IMEs with 1:1 mappings betweens keys and glyphs
// e.g. Armenian, and simple combinations like US International, but doesn't // e.g. Armenian, and simple combinations like US International, but doesn't
// support CJK, handwriting, completion, etc. // support CJK, handwriting, completion, etc.
void ShowIMEUnsupportedNotifictionIfNeeded(); void ShowIMEUnsupportedNotifictionIfNeeded();
......
...@@ -26,6 +26,7 @@ class MockDelegate : public CrostiniUnsupportedActionNotifier::Delegate { ...@@ -26,6 +26,7 @@ class MockDelegate : public CrostiniUnsupportedActionNotifier::Delegate {
public: public:
MOCK_METHOD(bool, IsInTabletMode, (), (override)); MOCK_METHOD(bool, IsInTabletMode, (), (override));
MOCK_METHOD(bool, IsFocusedWindowCrostini, (), (override)); MOCK_METHOD(bool, IsFocusedWindowCrostini, (), (override));
MOCK_METHOD(bool, IsVirtualKeyboardVisible, (), (override));
MOCK_METHOD(void, ShowToast, (const ash::ToastData& toast_data), (override)); MOCK_METHOD(void, ShowToast, (const ash::ToastData& toast_data), (override));
MOCK_METHOD(std::string, MOCK_METHOD(std::string,
GetLocalizedDisplayName, GetLocalizedDisplayName,
...@@ -56,6 +57,14 @@ class MockDelegate : public CrostiniUnsupportedActionNotifier::Delegate { ...@@ -56,6 +57,14 @@ class MockDelegate : public CrostiniUnsupportedActionNotifier::Delegate {
RemoveInputMethodObserver, RemoveInputMethodObserver,
(chromeos::input_method::InputMethodManager::Observer * observer), (chromeos::input_method::InputMethodManager::Observer * observer),
(override)); (override));
MOCK_METHOD(void,
AddKeyboardControllerObserver,
(ash::KeyboardControllerObserver * observer),
(override));
MOCK_METHOD(void,
RemoveKeyboardControllerObserver,
(ash::KeyboardControllerObserver * observer),
(override));
}; };
namespace { namespace {
...@@ -70,7 +79,7 @@ const InputMethodDescriptor ...@@ -70,7 +79,7 @@ const InputMethodDescriptor
} // namespace } // namespace
class CrostiniUnsupportedActionNotifierTest class CrostiniUnsupportedActionNotifierTest
: public testing::TestWithParam<std::tuple<bool, bool, bool>> { : public testing::TestWithParam<std::tuple<bool, bool, bool, bool>> {
public: public:
CrostiniUnsupportedActionNotifierTest() CrostiniUnsupportedActionNotifierTest()
: notifier(std::make_unique<NiceMock<MockDelegate>>()) {} : notifier(std::make_unique<NiceMock<MockDelegate>>()) {}
...@@ -87,7 +96,8 @@ class CrostiniUnsupportedActionNotifierTest ...@@ -87,7 +96,8 @@ class CrostiniUnsupportedActionNotifierTest
bool is_tablet_mode() const { return std::get<0>(GetParam()); } bool is_tablet_mode() const { return std::get<0>(GetParam()); }
bool is_crostini_focused() const { return std::get<1>(GetParam()); } bool is_crostini_focused() const { return std::get<1>(GetParam()); }
bool is_ime_unsupported() const { return std::get<2>(GetParam()); } bool is_vk_visible() const { return std::get<2>(GetParam()); }
bool is_ime_unsupported() const { return std::get<3>(GetParam()); }
InputMethodDescriptor ime_descriptor() const { InputMethodDescriptor ime_descriptor() const {
return is_ime_unsupported() ? unsupported : supported; return is_ime_unsupported() ? unsupported : supported;
} }
...@@ -100,12 +110,18 @@ class CrostiniUnsupportedActionNotifierTest ...@@ -100,12 +110,18 @@ class CrostiniUnsupportedActionNotifierTest
return data.id.compare(0, 2, "VK") == 0; return data.id.compare(0, 2, "VK") == 0;
} }
void SetExpectations(bool show_tablet_toast, bool show_ime_toast) { void SetExpectations(bool show_tablet_toast,
int num_tablet_toasts = show_tablet_toast ? 1 : 0; bool show_vk_toast,
bool show_ime_toast) {
// We show the same toast for tablet mode and manually triggered virtual
// keyboard, so showing either counts as showing both.
int num_tablet_toasts = (show_tablet_toast || show_vk_toast) ? 1 : 0;
int num_ime_toasts = show_ime_toast ? 1 : 0; int num_ime_toasts = show_ime_toast ? 1 : 0;
EXPECT_CALL(get_delegate(), IsInTabletMode) EXPECT_CALL(get_delegate(), IsInTabletMode)
.WillRepeatedly(Return(is_tablet_mode())); .WillRepeatedly(Return(is_tablet_mode()));
EXPECT_CALL(get_delegate(), IsVirtualKeyboardVisible)
.WillRepeatedly(Return(is_vk_visible()));
EXPECT_CALL(get_delegate(), IsFocusedWindowCrostini) EXPECT_CALL(get_delegate(), IsFocusedWindowCrostini)
.WillRepeatedly(Return(is_crostini_focused())); .WillRepeatedly(Return(is_crostini_focused()));
EXPECT_CALL(get_delegate(), GetCurrentInputMethod) EXPECT_CALL(get_delegate(), GetCurrentInputMethod)
...@@ -123,19 +139,39 @@ class CrostiniUnsupportedActionNotifierTest ...@@ -123,19 +139,39 @@ class CrostiniUnsupportedActionNotifierTest
TEST_P(CrostiniUnsupportedActionNotifierTest, TEST_P(CrostiniUnsupportedActionNotifierTest,
ToastShownOnceOnlyWhenEnteringTabletMode) { ToastShownOnceOnlyWhenEnteringTabletMode) {
bool show_tablet_toast = is_tablet_mode() && is_crostini_focused(); bool show_tablet_toast = is_tablet_mode() && is_crostini_focused();
// Since tablet and vk toasts are the same we can trigger the toast
// OnTabletModeStarted even if not in tablet mode.
bool show_vk_toast = is_vk_visible() && is_crostini_focused();
bool show_ime_toast = false; bool show_ime_toast = false;
SetExpectations(show_tablet_toast, show_ime_toast);
SetExpectations(show_tablet_toast, show_vk_toast, show_ime_toast);
notifier.OnTabletModeStarted(); notifier.OnTabletModeStarted();
notifier.OnTabletModeStarted(); notifier.OnTabletModeStarted();
} }
TEST_P(CrostiniUnsupportedActionNotifierTest,
ToastShownOnceOnlyWhenShowingVirtualKeyboard) {
// Since tablet and vk toasts are the same we can trigger the toast
// OnKeyboardVisibilityChanged even if the virtual keyboard isn't visible.
bool show_tablet_toast = is_tablet_mode() && is_crostini_focused();
bool show_vk_toast = is_vk_visible() && is_crostini_focused();
bool show_ime_toast = false;
SetExpectations(show_tablet_toast, show_vk_toast, show_ime_toast);
notifier.OnKeyboardVisibilityChanged(true);
notifier.OnKeyboardVisibilityChanged(false);
notifier.OnKeyboardVisibilityChanged(true);
}
TEST_P(CrostiniUnsupportedActionNotifierTest, TEST_P(CrostiniUnsupportedActionNotifierTest,
ToastShownOnceOnlyWhenChangingIME) { ToastShownOnceOnlyWhenChangingIME) {
bool show_tablet_toast = false; bool show_tablet_toast = false;
bool show_vk_toast = false;
bool show_ime_toast = is_ime_unsupported() && is_crostini_focused(); bool show_ime_toast = is_ime_unsupported() && is_crostini_focused();
SetExpectations(show_tablet_toast, show_ime_toast); SetExpectations(show_tablet_toast, show_vk_toast, show_ime_toast);
notifier.InputMethodChanged({}, {}, {}); notifier.InputMethodChanged({}, {}, {});
notifier.InputMethodChanged({}, {}, {}); notifier.InputMethodChanged({}, {}, {});
...@@ -144,9 +180,10 @@ TEST_P(CrostiniUnsupportedActionNotifierTest, ...@@ -144,9 +180,10 @@ TEST_P(CrostiniUnsupportedActionNotifierTest,
TEST_P(CrostiniUnsupportedActionNotifierTest, TEST_P(CrostiniUnsupportedActionNotifierTest,
ToastsShownOnceOnlyWhenFocusingCrostiniApp) { ToastsShownOnceOnlyWhenFocusingCrostiniApp) {
bool show_tablet_toast = is_tablet_mode() && is_crostini_focused(); bool show_tablet_toast = is_tablet_mode() && is_crostini_focused();
bool show_vk_toast = is_vk_visible() && is_crostini_focused();
bool show_ime_toast = is_ime_unsupported() && is_crostini_focused(); bool show_ime_toast = is_ime_unsupported() && is_crostini_focused();
SetExpectations(show_tablet_toast, show_ime_toast); SetExpectations(show_tablet_toast, show_vk_toast, show_ime_toast);
notifier.OnWindowFocused({}, {}); notifier.OnWindowFocused({}, {});
notifier.OnWindowFocused({}, {}); notifier.OnWindowFocused({}, {});
...@@ -154,6 +191,6 @@ TEST_P(CrostiniUnsupportedActionNotifierTest, ...@@ -154,6 +191,6 @@ TEST_P(CrostiniUnsupportedActionNotifierTest,
INSTANTIATE_TEST_CASE_P(CrostiniUnsupportedActionNotifierTestCombination, INSTANTIATE_TEST_CASE_P(CrostiniUnsupportedActionNotifierTestCombination,
CrostiniUnsupportedActionNotifierTest, CrostiniUnsupportedActionNotifierTest,
Combine(Bool(), Bool(), Bool())); Combine(Bool(), Bool(), Bool(), Bool()));
} // namespace crostini } // namespace crostini
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