chromium/chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.cc

// Copyright 2018 The Chromium Authors
// 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 <utility>
#include <vector>

#include "ash/constants/ash_features.h"
#include "ash/keyboard/ui/keyboard_ui_controller.h"
#include "ash/keyboard/ui/resources/keyboard_resource_util.h"
#include "ash/public/cpp/keyboard/keyboard_switches.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/ptr_util.h"
#include "base/trace_event/trace_event.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_change_registrar.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 "ui/base/ime/ash/ime_bridge.h"
#include "ui/base/ime/ash/input_method_manager.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/gfx/geometry/rect.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() {
  // Use WrapUnique to allow the constructor to be private.
  std::unique_ptr<ChromeKeyboardControllerClient> client =
      base::WrapUnique(new ChromeKeyboardControllerClient());
  client->InitializePrefObserver();
  return client;
}

// static
std::unique_ptr<ChromeKeyboardControllerClient>
ChromeKeyboardControllerClient::CreateForTest() {
  // Use WrapUnique to allow the constructor to be private.
  return base::WrapUnique(new ChromeKeyboardControllerClient());
}

// 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() {
  CHECK(!g_chrome_keyboard_controller_client);
  g_chrome_keyboard_controller_client = this;
}

void ChromeKeyboardControllerClient::Init(
    ash::KeyboardController* keyboard_controller) {
  DCHECK(!keyboard_controller_);
  keyboard_controller_ = keyboard_controller;

  // Add this as a KeyboardController observer.
  keyboard_controller_->AddObserver(this);

  // Request the initial enabled state.
  OnKeyboardEnabledChanged(keyboard_controller_->IsKeyboardEnabled());

  // Request the initial set of enable flags.
  OnKeyboardEnableFlagsChanged(keyboard_controller_->GetEnableFlags());

  // Request the initial visible state.
  OnKeyboardVisibilityChanged(keyboard_controller_->IsKeyboardVisible());

  // Request the configuration.
  OnKeyboardConfigChanged(keyboard_controller_->GetKeyboardConfig());
}

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 (keyboard_controller_) {
    keyboard_controller_->RemoveObserver(this);
    keyboard_controller_ = nullptr;
  }

  if (session_manager::SessionManager::Get())
    session_manager::SessionManager::Get()->RemoveObserver(this);
  pref_change_registrar_.reset();

  if (keyboard::KeyboardUIController::HasInstance()) {
    // In classic Ash, keyboard::KeyboardController owns ChromeKeyboardUI which
    // accesses this class, so make sure that the UI has been destroyed.
    keyboard::KeyboardUIController::Get()->Shutdown();
  }
  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::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::KeyboardConfig();
  }
  return *cached_keyboard_config_;
}

void ChromeKeyboardControllerClient::SetKeyboardConfig(
    const keyboard::KeyboardConfig& config) {
  // Update the cache immediately.
  cached_keyboard_config_ = config;
  keyboard_controller_->SetKeyboardConfig(config);
}

bool ChromeKeyboardControllerClient::GetKeyboardEnabled() {
  // |keyboard_controller_| may be null during shutdown.
  return keyboard_controller_ ? keyboard_controller_->IsKeyboardEnabled()
                              : false;
}

void ChromeKeyboardControllerClient::SetEnableFlag(
    const keyboard::KeyboardEnableFlag& flag) {
  DVLOG(1) << "SetEnableFlag: " << static_cast<int>(flag);
  keyboard_controller_->SetEnableFlag(flag);
}

void ChromeKeyboardControllerClient::ClearEnableFlag(
    const keyboard::KeyboardEnableFlag& flag) {
  keyboard_controller_->ClearEnableFlag(flag);
}

bool ChromeKeyboardControllerClient::IsEnableFlagSet(
    const keyboard::KeyboardEnableFlag& flag) {
  return base::Contains(keyboard_enable_flags_, flag);
}

void ChromeKeyboardControllerClient::ReloadKeyboardIfNeeded() {
  // |keyboard_controller_| may be null if the keyboard reloads during shutdown.
  if (keyboard_controller_)
    keyboard_controller_->ReloadKeyboardIfNeeded();
}

void ChromeKeyboardControllerClient::RebuildKeyboardIfEnabled() {
  keyboard_controller_->RebuildKeyboardIfEnabled();
}

void ChromeKeyboardControllerClient::ShowKeyboard() {
  keyboard_controller_->ShowKeyboard();
}

void ChromeKeyboardControllerClient::HideKeyboard(ash::HideReason reason) {
  keyboard_controller_->HideKeyboard(reason);
}

void ChromeKeyboardControllerClient::SetContainerType(
    keyboard::ContainerType container_type,
    const gfx::Rect& target_bounds,
    base::OnceCallback<void(bool)> callback) {
  keyboard_controller_->SetContainerType(container_type, target_bounds,
                                         std::move(callback));
}

void ChromeKeyboardControllerClient::SetKeyboardLocked(bool locked) {
  keyboard_controller_->SetKeyboardLocked(locked);
}

void ChromeKeyboardControllerClient::SetOccludedBounds(
    const std::vector<gfx::Rect>& bounds) {
  keyboard_controller_->SetOccludedBounds(bounds);
}

void ChromeKeyboardControllerClient::SetHitTestBounds(
    const std::vector<gfx::Rect>& bounds) {
  keyboard_controller_->SetHitTestBounds(bounds);
}

bool ChromeKeyboardControllerClient::SetAreaToRemainOnScreen(
    const gfx::Rect& bounds) {
  return keyboard_controller_->SetAreaToRemainOnScreen(bounds);
}

void ChromeKeyboardControllerClient::SetDraggableArea(const gfx::Rect& bounds) {
  keyboard_controller_->SetDraggableArea(bounds);
}

bool ChromeKeyboardControllerClient::SetWindowBoundsInScreen(
    const gfx::Rect& bounds_in_screen) {
  return keyboard_controller_->SetWindowBoundsInScreen(bounds_in_screen);
}

void ChromeKeyboardControllerClient::SetKeyboardConfigFromPref(bool enabled) {
  keyboard_controller_->SetKeyboardConfigFromPref(enabled);
}

bool ChromeKeyboardControllerClient::IsKeyboardOverscrollEnabled() {
  return keyboard_controller_->ShouldOverscroll();
}

GURL ChromeKeyboardControllerClient::GetVirtualKeyboardUrl() {
  if (!virtual_keyboard_url_for_test_.is_empty())
    return virtual_keyboard_url_for_test_;

  auto* ime_manager = ash::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 {
  return keyboard::KeyboardUIController::Get()->GetKeyboardWindow();
}

void ChromeKeyboardControllerClient::OnKeyboardEnableFlagsChanged(
    const std::set<keyboard::KeyboardEnableFlag>& flags) {
  keyboard_enable_flags_ = flags;
}

void ChromeKeyboardControllerClient::OnKeyboardEnabledChanged(bool enabled) {
  DVLOG(1) << "OnKeyboardEnabledChanged: " << enabled;

  bool was_enabled = is_keyboard_enabled_;
  is_keyboard_enabled_ = enabled;

  for (auto& observer : observers_)
    observer.OnKeyboardEnabledChanged(is_keyboard_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,
      base::Value::List(), profile);
  router->BroadcastEvent(std::move(event));
}

void ChromeKeyboardControllerClient::OnKeyboardConfigChanged(
    const keyboard::KeyboardConfig& 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());

  for (auto& observer : observers_) {
    observer.OnKeyboardVisibleBoundsChanged(screen_bounds);
  }

  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);
  base::Value::List event_args;
  base::Value::Dict new_bounds;
  new_bounds.Set("left", bounds.x());
  new_bounds.Set("top", bounds.y());
  new_bounds.Set("width", bounds.width());
  new_bounds.Set("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;
  DVLOG(1) << "OnKeyboardOccludedBoundsChanged: " << screen_bounds.ToString();
  for (auto& observer : observers_)
    observer.OnKeyboardOccludedBoundsChanged(screen_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,
      /*load_callback=*/
      base::BindOnce(&ChromeKeyboardControllerClient::OnKeyboardContentsLoaded,
                     weak_ptr_factory_.GetWeakPtr()),
      /*unembed_callback=*/
      base::BindRepeating(
          &ChromeKeyboardControllerClient::OnKeyboardUIDestroyed,
          weak_ptr_factory_.GetWeakPtr()));
}

void ChromeKeyboardControllerClient::OnKeyboardUIDestroyed() {
  keyboard_contents_.reset();
}

void ChromeKeyboardControllerClient::OnKeyboardContentsLoaded() {
  DVLOG(1) << "OnKeyboardContentsLoaded";
  NotifyKeyboardLoaded();
}

void ChromeKeyboardControllerClient::OnSessionStateChanged() {
  TRACE_EVENT0("login",
               "ChromeKeyboardControllerClient::OnSessionStateChanged");
  if (base::FeatureList::IsEnabled(
          ash::features::kTouchVirtualKeyboardPolicyListenPrefsAtLogin)) {
    // We need to listen for pref changes even in login screen to control the
    // virtual keyboard behavior on the login screen.
    pref_change_registrar_.reset();
  } else {
    if (!session_manager::SessionManager::Get()->IsSessionStarted()) {
      // Reset the registrar so that prefs are re-registered after a crash.
      pref_change_registrar_.reset();
      return;
    }
    if (pref_change_registrar_) {
      return;
    }
  }

  Profile* profile = ProfileManager::GetPrimaryUserProfile();
  pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
  pref_change_registrar_->Init(profile->GetPrefs());
  pref_change_registrar_->Add(
      prefs::kTouchVirtualKeyboardEnabled,
      base::BindRepeating(
          &ChromeKeyboardControllerClient::SetTouchKeyboardEnabledFromPrefs,
          base::Unretained(this)));
  pref_change_registrar_->Add(
      prefs::kVirtualKeyboardSmartVisibilityEnabled,
      base::BindRepeating(
          &ChromeKeyboardControllerClient::SetSmartVisibilityFromPrefs,
          base::Unretained(this)));

  SetTouchKeyboardEnabledFromPrefs();
  SetSmartVisibilityFromPrefs();
}

void ChromeKeyboardControllerClient::SetTouchKeyboardEnabledFromPrefs() {
  using keyboard::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);
  }
}

void ChromeKeyboardControllerClient::SetSmartVisibilityFromPrefs() {
  const PrefService* service = pref_change_registrar_->prefs();
  if (service->HasPrefPath(prefs::kVirtualKeyboardSmartVisibilityEnabled)) {
    keyboard_controller_->SetSmartVisibilityEnabled(
        service->GetBoolean(prefs::kVirtualKeyboardSmartVisibilityEnabled));
  }
}

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;
}