| // 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 "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h" |
| |
| #include <memory> |
| |
| #include "ash/public/interfaces/constants.mojom.h" |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/command_line.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/stl_util.h" |
| #include "base/values.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/ui/ash/keyboard/chrome_keyboard_web_contents.h" |
| #include "chrome/common/pref_names.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/session_manager/core/session_manager.h" |
| #include "content/public/browser/web_contents.h" |
| #include "extensions/browser/api/virtual_keyboard_private/virtual_keyboard_delegate.h" |
| #include "extensions/browser/api/virtual_keyboard_private/virtual_keyboard_private_api.h" |
| #include "extensions/browser/event_router.h" |
| #include "extensions/common/api/virtual_keyboard_private.h" |
| #include "extensions/common/extension_messages.h" |
| #include "services/service_manager/public/cpp/connector.h" |
| #include "ui/base/ime/chromeos/input_method_manager.h" |
| #include "ui/base/ime/ime_bridge.h" |
| #include "ui/base/ime/input_method.h" |
| #include "ui/base/ime/text_input_client.h" |
| #include "ui/base/ui_base_features.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/keyboard/keyboard_controller.h" |
| #include "ui/keyboard/public/keyboard_switches.h" |
| #include "ui/keyboard/resources/keyboard_resource_util.h" |
| #include "ui/wm/core/coordinate_conversion.h" |
| |
| namespace virtual_keyboard_private = extensions::api::virtual_keyboard_private; |
| |
| namespace { |
| |
| static ChromeKeyboardControllerClient* g_chrome_keyboard_controller_client = |
| nullptr; |
| |
| } // namespace |
| |
| // static |
| std::unique_ptr<ChromeKeyboardControllerClient> |
| ChromeKeyboardControllerClient::Create(service_manager::Connector* connector) { |
| // Use WrapUnique to allow the constructor to be private. |
| std::unique_ptr<ChromeKeyboardControllerClient> client = |
| base::WrapUnique(new ChromeKeyboardControllerClient(connector)); |
| client->InitializePrefObserver(); |
| return client; |
| } |
| |
| // static |
| std::unique_ptr<ChromeKeyboardControllerClient> |
| ChromeKeyboardControllerClient::CreateForTest( |
| service_manager::Connector* connector) { |
| // Use WrapUnique to allow the constructor to be private. |
| return base::WrapUnique(new ChromeKeyboardControllerClient(connector)); |
| } |
| |
| // static |
| ChromeKeyboardControllerClient* ChromeKeyboardControllerClient::Get() { |
| CHECK(g_chrome_keyboard_controller_client) |
| << "ChromeKeyboardControllerClient::Get() called before Initialize()"; |
| return g_chrome_keyboard_controller_client; |
| } |
| |
| // static |
| bool ChromeKeyboardControllerClient::HasInstance() { |
| return !!g_chrome_keyboard_controller_client; |
| } |
| |
| ChromeKeyboardControllerClient::ChromeKeyboardControllerClient( |
| service_manager::Connector* connector) { |
| CHECK(!g_chrome_keyboard_controller_client); |
| g_chrome_keyboard_controller_client = this; |
| |
| if (!connector) |
| return; // May be null in tests. |
| |
| connector->BindInterface(ash::mojom::kServiceName, &keyboard_controller_ptr_); |
| |
| // Add this as a KeyboardController observer. |
| ash::mojom::KeyboardControllerObserverAssociatedPtrInfo ptr_info; |
| keyboard_controller_observer_binding_.Bind(mojo::MakeRequest(&ptr_info)); |
| keyboard_controller_ptr_->AddObserver(std::move(ptr_info)); |
| |
| // Request the initial enabled state. |
| keyboard_controller_ptr_->IsKeyboardEnabled( |
| base::BindOnce(&ChromeKeyboardControllerClient::OnKeyboardEnabledChanged, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| // Request the initial set of enable flags. |
| keyboard_controller_ptr_->GetEnableFlags(base::BindOnce( |
| &ChromeKeyboardControllerClient::OnKeyboardEnableFlagsChanged, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| // Request the initial visible state. |
| keyboard_controller_ptr_->IsKeyboardVisible(base::BindOnce( |
| &ChromeKeyboardControllerClient::OnKeyboardVisibilityChanged, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| // Request the configuration. |
| keyboard_controller_ptr_->GetKeyboardConfig( |
| base::BindOnce(&ChromeKeyboardControllerClient::OnKeyboardConfigChanged, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| ChromeKeyboardControllerClient::~ChromeKeyboardControllerClient() { |
| CHECK(g_chrome_keyboard_controller_client); |
| Shutdown(); |
| // Clear the global instance pointer last so that keyboard_contents_ and |
| // KeyboardController owned classes can remove themselves as observers. |
| g_chrome_keyboard_controller_client = nullptr; |
| } |
| |
| void ChromeKeyboardControllerClient::InitializePrefObserver() { |
| session_manager::SessionManager::Get()->AddObserver(this); |
| } |
| |
| void ChromeKeyboardControllerClient::Shutdown() { |
| if (session_manager::SessionManager::Get()) |
| session_manager::SessionManager::Get()->RemoveObserver(this); |
| pref_change_registrar_.RemoveAll(); |
| if (!::features::IsUsingWindowService() && |
| keyboard::KeyboardController::HasInstance()) { |
| // In classic Ash, keyboard::KeyboardController owns ChromeKeyboardUI which |
| // accesses this class, so make sure that the UI has been destroyed. |
| keyboard::KeyboardController::Get()->DisableKeyboard(); |
| } |
| keyboard_contents_.reset(); |
| } |
| |
| void ChromeKeyboardControllerClient::AddObserver(Observer* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void ChromeKeyboardControllerClient::RemoveObserver(Observer* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| void ChromeKeyboardControllerClient::NotifyKeyboardLoaded() { |
| DVLOG(1) << "NotifyKeyboardLoaded: " << is_keyboard_loaded_; |
| is_keyboard_loaded_ = true; |
| for (auto& observer : observers_) |
| observer.OnKeyboardLoaded(); |
| } |
| |
| keyboard::mojom::KeyboardConfig |
| ChromeKeyboardControllerClient::GetKeyboardConfig() { |
| if (!cached_keyboard_config_) { |
| // Unlikely edge case (called before the Ash mojo service replies to the |
| // initial GetKeyboardConfig request). Return the default value. |
| return keyboard::mojom::KeyboardConfig(); |
| } |
| return *cached_keyboard_config_.get(); |
| } |
| |
| void ChromeKeyboardControllerClient::SetKeyboardConfig( |
| const keyboard::mojom::KeyboardConfig& config) { |
| // Update the cache immediately. |
| cached_keyboard_config_ = keyboard::mojom::KeyboardConfig::New(config); |
| keyboard_controller_ptr_->SetKeyboardConfig(config.Clone()); |
| } |
| |
| void ChromeKeyboardControllerClient::GetKeyboardEnabled( |
| base::OnceCallback<void(bool)> callback) { |
| keyboard_controller_ptr_->IsKeyboardEnabled(std::move(callback)); |
| } |
| |
| void ChromeKeyboardControllerClient::SetEnableFlag( |
| const keyboard::mojom::KeyboardEnableFlag& flag) { |
| DVLOG(1) << "SetEnableFlag: " << flag; |
| keyboard_controller_ptr_->SetEnableFlag(flag); |
| } |
| |
| void ChromeKeyboardControllerClient::ClearEnableFlag( |
| const keyboard::mojom::KeyboardEnableFlag& flag) { |
| keyboard_controller_ptr_->ClearEnableFlag(flag); |
| } |
| |
| bool ChromeKeyboardControllerClient::IsEnableFlagSet( |
| const keyboard::mojom::KeyboardEnableFlag& flag) { |
| return base::ContainsKey(keyboard_enable_flags_, flag); |
| } |
| |
| void ChromeKeyboardControllerClient::ReloadKeyboardIfNeeded() { |
| keyboard_controller_ptr_->ReloadKeyboardIfNeeded(); |
| } |
| |
| void ChromeKeyboardControllerClient::RebuildKeyboardIfEnabled() { |
| keyboard_controller_ptr_->RebuildKeyboardIfEnabled(); |
| } |
| |
| void ChromeKeyboardControllerClient::ShowKeyboard() { |
| keyboard_controller_ptr_->ShowKeyboard(); |
| } |
| |
| void ChromeKeyboardControllerClient::HideKeyboard( |
| ash::mojom::HideReason reason) { |
| keyboard_controller_ptr_->HideKeyboard(reason); |
| } |
| |
| void ChromeKeyboardControllerClient::SetContainerType( |
| keyboard::mojom::ContainerType container_type, |
| const base::Optional<gfx::Rect>& target_bounds, |
| base::OnceCallback<void(bool)> callback) { |
| keyboard_controller_ptr_->SetContainerType(container_type, target_bounds, |
| std::move(callback)); |
| } |
| |
| void ChromeKeyboardControllerClient::SetKeyboardLocked(bool locked) { |
| keyboard_controller_ptr_->SetKeyboardLocked(locked); |
| } |
| |
| void ChromeKeyboardControllerClient::SetOccludedBounds( |
| const std::vector<gfx::Rect>& bounds) { |
| keyboard_controller_ptr_->SetOccludedBounds(bounds); |
| } |
| |
| void ChromeKeyboardControllerClient::SetHitTestBounds( |
| const std::vector<gfx::Rect>& bounds) { |
| keyboard_controller_ptr_->SetHitTestBounds(bounds); |
| } |
| |
| void ChromeKeyboardControllerClient::SetDraggableArea(const gfx::Rect& bounds) { |
| keyboard_controller_ptr_->SetDraggableArea(bounds); |
| } |
| |
| bool ChromeKeyboardControllerClient::IsKeyboardOverscrollEnabled() { |
| DCHECK(cached_keyboard_config_); |
| if (cached_keyboard_config_->overscroll_behavior != |
| keyboard::mojom::KeyboardOverscrollBehavior::kDefault) { |
| return cached_keyboard_config_->overscroll_behavior == |
| keyboard::mojom::KeyboardOverscrollBehavior::kEnabled; |
| } |
| return !base::CommandLine::ForCurrentProcess()->HasSwitch( |
| keyboard::switches::kDisableVirtualKeyboardOverscroll); |
| } |
| |
| GURL ChromeKeyboardControllerClient::GetVirtualKeyboardUrl() { |
| if (!virtual_keyboard_url_for_test_.is_empty()) |
| return virtual_keyboard_url_for_test_; |
| |
| chromeos::input_method::InputMethodManager* ime_manager = |
| chromeos::input_method::InputMethodManager::Get(); |
| if (!ime_manager || !ime_manager->GetActiveIMEState()) |
| return GURL(keyboard::kKeyboardURL); |
| |
| const GURL& input_view_url = |
| ime_manager->GetActiveIMEState()->GetInputViewUrl(); |
| if (!input_view_url.is_valid()) |
| return GURL(keyboard::kKeyboardURL); |
| |
| return input_view_url; |
| } |
| |
| aura::Window* ChromeKeyboardControllerClient::GetKeyboardWindow() const { |
| if (::features::IsUsingWindowService()) { |
| content::WebContents* contents = |
| keyboard_contents_ ? keyboard_contents_->web_contents() : nullptr; |
| return contents ? contents->GetNativeView() : nullptr; |
| } |
| return keyboard::KeyboardController::Get()->GetKeyboardWindow(); |
| } |
| |
| void ChromeKeyboardControllerClient::FlushForTesting() { |
| keyboard_controller_ptr_.FlushForTesting(); |
| } |
| |
| void ChromeKeyboardControllerClient::OnKeyboardEnableFlagsChanged( |
| const std::vector<keyboard::mojom::KeyboardEnableFlag>& flags) { |
| keyboard_enable_flags_ = |
| std::set<keyboard::mojom::KeyboardEnableFlag>(flags.begin(), flags.end()); |
| } |
| |
| void ChromeKeyboardControllerClient::OnKeyboardEnabledChanged(bool enabled) { |
| DVLOG(1) << "OnKeyboardEnabledChanged: " << enabled; |
| |
| bool was_enabled = is_keyboard_enabled_; |
| is_keyboard_enabled_ = enabled; |
| if (enabled || !was_enabled) |
| return; |
| |
| // When the keyboard becomes disabled, send the onKeyboardClosed event. |
| |
| Profile* profile = GetProfile(); |
| extensions::EventRouter* router = extensions::EventRouter::Get(profile); |
| // |router| may be null in tests. |
| if (!router || !router->HasEventListener( |
| virtual_keyboard_private::OnKeyboardClosed::kEventName)) { |
| return; |
| } |
| |
| auto event = std::make_unique<extensions::Event>( |
| extensions::events::VIRTUAL_KEYBOARD_PRIVATE_ON_KEYBOARD_CLOSED, |
| virtual_keyboard_private::OnKeyboardClosed::kEventName, |
| std::make_unique<base::ListValue>(), profile); |
| router->BroadcastEvent(std::move(event)); |
| } |
| |
| void ChromeKeyboardControllerClient::OnKeyboardConfigChanged( |
| keyboard::mojom::KeyboardConfigPtr config) { |
| // Only notify extensions after the initial config is received. |
| bool notify = !!cached_keyboard_config_; |
| cached_keyboard_config_ = std::move(config); |
| if (!notify) |
| return; |
| extensions::VirtualKeyboardAPI* api = |
| extensions::BrowserContextKeyedAPIFactory< |
| extensions::VirtualKeyboardAPI>::Get(GetProfile()); |
| api->delegate()->OnKeyboardConfigChanged(); |
| } |
| |
| void ChromeKeyboardControllerClient::OnKeyboardVisibilityChanged(bool visible) { |
| is_keyboard_visible_ = visible; |
| for (auto& observer : observers_) |
| observer.OnKeyboardVisibilityChanged(visible); |
| } |
| |
| void ChromeKeyboardControllerClient::OnKeyboardVisibleBoundsChanged( |
| const gfx::Rect& screen_bounds) { |
| DVLOG(1) << "OnKeyboardVisibleBoundsChanged: " << screen_bounds.ToString(); |
| if (keyboard_contents_) |
| keyboard_contents_->SetInitialContentsSize(screen_bounds.size()); |
| |
| if (!GetKeyboardWindow()) |
| return; |
| |
| Profile* profile = GetProfile(); |
| extensions::EventRouter* router = extensions::EventRouter::Get(profile); |
| // |router| may be null in tests. |
| if (!router || !router->HasEventListener( |
| virtual_keyboard_private::OnBoundsChanged::kEventName)) { |
| return; |
| } |
| |
| // Convert screen bounds to the frame of reference of the keyboard window. |
| gfx::Rect bounds = BoundsFromScreen(screen_bounds); |
| auto event_args = std::make_unique<base::ListValue>(); |
| auto new_bounds = std::make_unique<base::DictionaryValue>(); |
| new_bounds->SetInteger("left", bounds.x()); |
| new_bounds->SetInteger("top", bounds.y()); |
| new_bounds->SetInteger("width", bounds.width()); |
| new_bounds->SetInteger("height", bounds.height()); |
| event_args->Append(std::move(new_bounds)); |
| |
| auto event = std::make_unique<extensions::Event>( |
| extensions::events::VIRTUAL_KEYBOARD_PRIVATE_ON_BOUNDS_CHANGED, |
| virtual_keyboard_private::OnBoundsChanged::kEventName, |
| std::move(event_args), profile); |
| router->BroadcastEvent(std::move(event)); |
| } |
| |
| void ChromeKeyboardControllerClient::OnKeyboardOccludedBoundsChanged( |
| const gfx::Rect& screen_bounds) { |
| if (!GetKeyboardWindow()) |
| return; |
| gfx::Rect bounds = BoundsFromScreen(screen_bounds); |
| DVLOG(1) << "OnKeyboardOccludedBoundsChanged: " << bounds.ToString(); |
| for (auto& observer : observers_) |
| observer.OnKeyboardOccludedBoundsChanged(bounds); |
| } |
| |
| void ChromeKeyboardControllerClient::OnLoadKeyboardContentsRequested() { |
| GURL keyboard_url = GetVirtualKeyboardUrl(); |
| if (keyboard_contents_) { |
| DVLOG(1) << "OnLoadKeyboardContentsRequested: SetUrl: " << keyboard_url; |
| keyboard_contents_->SetKeyboardUrl(keyboard_url); |
| return; |
| } |
| |
| DVLOG(1) << "OnLoadKeyboardContentsRequested: Create: " << keyboard_url; |
| keyboard_contents_ = std::make_unique<ChromeKeyboardWebContents>( |
| GetProfile(), keyboard_url, |
| base::BindOnce(&ChromeKeyboardControllerClient::OnKeyboardContentsLoaded, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void ChromeKeyboardControllerClient::OnKeyboardUIDestroyed() { |
| keyboard_contents_.reset(); |
| } |
| |
| void ChromeKeyboardControllerClient::OnKeyboardContentsLoaded( |
| const base::UnguessableToken& token, |
| const gfx::Size& size) { |
| DVLOG(1) << "OnLoadKeyboardContentsRequested: " << size.ToString(); |
| NotifyKeyboardLoaded(); |
| keyboard_controller_ptr_->KeyboardContentsLoaded(token, size); |
| } |
| |
| void ChromeKeyboardControllerClient::OnSessionStateChanged() { |
| if (!session_manager::SessionManager::Get()->IsSessionStarted()) { |
| // Reset the registrar so that prefs are re-registered after a crash. |
| pref_change_registrar_.RemoveAll(); |
| return; |
| } |
| if (!pref_change_registrar_.IsEmpty()) |
| return; |
| |
| Profile* profile = ProfileManager::GetPrimaryUserProfile(); |
| pref_change_registrar_.Init(profile->GetPrefs()); |
| pref_change_registrar_.Add( |
| prefs::kTouchVirtualKeyboardEnabled, |
| base::BindRepeating( |
| &ChromeKeyboardControllerClient::SetVirtualKeyboardBehaviorFromPrefs, |
| base::Unretained(this))); |
| SetVirtualKeyboardBehaviorFromPrefs(); |
| } |
| |
| void ChromeKeyboardControllerClient::SetVirtualKeyboardBehaviorFromPrefs() { |
| using keyboard::mojom::KeyboardEnableFlag; |
| const PrefService* service = pref_change_registrar_.prefs(); |
| if (service->HasPrefPath(prefs::kTouchVirtualKeyboardEnabled)) { |
| // Since these flags are mutually exclusive, setting one clears the other. |
| SetEnableFlag(service->GetBoolean(prefs::kTouchVirtualKeyboardEnabled) |
| ? KeyboardEnableFlag::kPolicyEnabled |
| : KeyboardEnableFlag::kPolicyDisabled); |
| } else { |
| ClearEnableFlag(KeyboardEnableFlag::kPolicyDisabled); |
| ClearEnableFlag(KeyboardEnableFlag::kPolicyEnabled); |
| } |
| } |
| |
| Profile* ChromeKeyboardControllerClient::GetProfile() { |
| if (profile_for_test_) |
| return profile_for_test_; |
| |
| // Always use the active profile for generating keyboard events so that any |
| // virtual keyboard extensions associated with the active user are notified. |
| // (Note: UI and associated extensions only exist for the active user). |
| return ProfileManager::GetActiveUserProfile(); |
| } |
| |
| gfx::Rect ChromeKeyboardControllerClient::BoundsFromScreen( |
| const gfx::Rect& screen_bounds) { |
| aura::Window* keyboard_window = GetKeyboardWindow(); |
| DCHECK(keyboard_window); |
| gfx::Rect bounds(screen_bounds); |
| ::wm::ConvertRectFromScreen(keyboard_window, &bounds); |
| return bounds; |
| } |