blob: 0d16305eec54d68f921a50c32a0c1d6c95a14c26 [file] [log] [blame]
// 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/chromeos/arc/input_method_manager/arc_input_method_manager_service.h"
#include <algorithm>
#include <utility>
#include "ash/public/cpp/ash_pref_names.h"
#include "ash/public/cpp/keyboard/keyboard_switches.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/metrics/histogram_macros.h"
#include "base/stl_util.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_split.h"
#include "chrome/browser/chromeos/arc/arc_util.h"
#include "chrome/browser/chromeos/arc/input_method_manager/arc_input_method_manager_bridge_impl.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h"
#include "chrome/browser/ui/ash/tablet_mode_client.h"
#include "chrome/browser/ui/ash/tablet_mode_client_observer.h"
#include "chrome/common/pref_names.h"
#include "components/arc/arc_browser_context_keyed_service_factory_base.h"
#include "components/arc/common/ime_struct_traits.h"
#include "components/crx_file/id_util.h"
#include "components/prefs/pref_service.h"
#include "ui/base/ime/chromeos/component_extension_ime_manager.h"
#include "ui/base/ime/chromeos/extension_ime_util.h"
#include "ui/base/ime/chromeos/input_method_util.h"
#include "ui/base/ime/ime_bridge.h"
#include "ui/base/ime/input_method_observer.h"
namespace arc {
namespace {
// The Android IME id of the pre-installed IME to proxy Chrome OS IME's actions
// to inside the container.
// Please refer to ArcImeService for the implementation details.
constexpr char kChromeOSIMEIdInArcContainer[] =
"org.chromium.arc.ime/.ArcInputMethodService";
// The name of the proxy IME extension that is used when registering ARC IMEs to
// InputMethodManager.
constexpr char kArcIMEProxyExtensionName[] =
"org.chromium.arc.inputmethod.proxy";
void SwitchImeToCallback(const std::string& ime_id,
const std::string& component_id,
bool success) {
if (success)
return;
// TODO(yhanana): We should prevent InputMethodManager from changing current
// input method until this callback is called with true and once it's done the
// IME switching code below can be removed.
LOG(ERROR) << "Switch the active IME to \"" << ime_id << "\"(component_id=\""
<< component_id << "\") failed";
auto* imm = chromeos::input_method::InputMethodManager::Get();
if (imm && imm->GetActiveIMEState()) {
for (const auto& id : imm->GetActiveIMEState()->GetActiveInputMethodIds()) {
if (!chromeos::extension_ime_util::IsArcIME(id)) {
imm->GetActiveIMEState()->ChangeInputMethod(id,
false /* show_message */);
return;
}
}
}
NOTREACHED() << "There is no enabled non-ARC IME.";
}
void SetKeyboardDisabled(bool disabled) {
if (disabled) {
ChromeKeyboardControllerClient::Get()->SetEnableFlag(
keyboard::KeyboardEnableFlag::kAndroidDisabled);
} else {
ChromeKeyboardControllerClient::Get()->ClearEnableFlag(
keyboard::KeyboardEnableFlag::kAndroidDisabled);
}
}
// Singleton factory for ArcInputMethodManagerService
class ArcInputMethodManagerServiceFactory
: public internal::ArcBrowserContextKeyedServiceFactoryBase<
ArcInputMethodManagerService,
ArcInputMethodManagerServiceFactory> {
public:
// Factory name used by ArcBrowserContextKeyedServiceFactoryBase
static constexpr const char* kName = "ArcInputMethodManagerServiceFactory";
static ArcInputMethodManagerServiceFactory* GetInstance() {
return base::Singleton<ArcInputMethodManagerServiceFactory>::get();
}
private:
friend base::DefaultSingletonTraits<ArcInputMethodManagerServiceFactory>;
ArcInputMethodManagerServiceFactory() = default;
~ArcInputMethodManagerServiceFactory() override = default;
};
} // namespace
class ArcInputMethodManagerService::InputMethodEngineObserver
: public input_method::InputMethodEngineBase::Observer {
public:
explicit InputMethodEngineObserver(ArcInputMethodManagerService* owner)
: owner_(owner) {}
~InputMethodEngineObserver() override = default;
// input_method::InputMethodEngineBase::Observer overrides:
void OnActivate(const std::string& engine_id) override {
owner_->is_arc_ime_active_ = true;
// TODO(yhanada): Remove this line after we migrate to SPM completely.
owner_->OnInputContextHandlerChanged();
}
void OnFocus(
const ui::IMEEngineHandlerInterface::InputContext& context) override {
owner_->Focus(context.id);
}
void OnBlur(int context_id) override { owner_->Blur(); }
void OnKeyEvent(
const std::string& engine_id,
const input_method::InputMethodEngineBase::KeyboardEvent& event,
ui::IMEEngineHandlerInterface::KeyEventDoneCallback key_data) override {
if (event.key_code == ui::VKEY_BROWSER_BACK &&
owner_->IsVirtualKeyboardShown()) {
// Back button on the shelf is pressed.
owner_->SendHideVirtualKeyboard();
std::move(key_data).Run(true);
return;
}
std::move(key_data).Run(false);
}
void OnReset(const std::string& engine_id) override {}
void OnDeactivated(const std::string& engine_id) override {
owner_->is_arc_ime_active_ = false;
// TODO(yhanada): Remove this line after we migrate to SPM completely.
owner_->OnInputContextHandlerChanged();
}
void OnCompositionBoundsChanged(
const std::vector<gfx::Rect>& bounds) override {}
bool IsInterestedInKeyEvent() const override { return true; }
void OnSurroundingTextChanged(const std::string& engine_id,
const std::string& text,
int cursor_pos,
int anchor_pos,
int offset_pos) override {
owner_->UpdateTextInputState();
}
void OnInputContextUpdate(
const ui::IMEEngineHandlerInterface::InputContext& context) override {
owner_->UpdateTextInputState();
}
void OnCandidateClicked(
const std::string& component_id,
int candidate_id,
input_method::InputMethodEngineBase::MouseButtonEvent button) override {}
void OnMenuItemActivated(const std::string& component_id,
const std::string& menu_id) override {}
void OnScreenProjectionChanged(bool is_projected) override {}
private:
ArcInputMethodManagerService* const owner_;
DISALLOW_COPY_AND_ASSIGN(InputMethodEngineObserver);
};
class ArcInputMethodManagerService::InputMethodObserver
: public ui::InputMethodObserver {
public:
explicit InputMethodObserver(ArcInputMethodManagerService* owner)
: owner_(owner) {}
~InputMethodObserver() override = default;
// ui::InputMethodObserver overrides:
void OnFocus() override {}
void OnBlur() override {}
void OnCaretBoundsChanged(const ui::TextInputClient* client) override {}
void OnTextInputStateChanged(const ui::TextInputClient* client) override {}
void OnInputMethodDestroyed(const ui::InputMethod* input_method) override {
owner_->input_method_ = nullptr;
}
void OnShowVirtualKeyboardIfEnabled() override {
owner_->SendShowVirtualKeyboard();
}
private:
ArcInputMethodManagerService* const owner_;
DISALLOW_COPY_AND_ASSIGN(InputMethodObserver);
};
class ArcInputMethodManagerService::TabletModeObserver
: public TabletModeClientObserver {
public:
explicit TabletModeObserver(ArcInputMethodManagerService* owner)
: owner_(owner) {}
~TabletModeObserver() override = default;
void OnTabletModeToggled(bool enabled) override {
owner_->UpdateArcIMEAllowed();
owner_->NotifyInputMethodManagerObservers(enabled);
}
private:
ArcInputMethodManagerService* owner_;
DISALLOW_COPY_AND_ASSIGN(TabletModeObserver);
};
// static
ArcInputMethodManagerService*
ArcInputMethodManagerService::GetForBrowserContext(
content::BrowserContext* context) {
return ArcInputMethodManagerServiceFactory::GetForBrowserContext(context);
}
// static
ArcInputMethodManagerService*
ArcInputMethodManagerService::GetForBrowserContextForTesting(
content::BrowserContext* context) {
return ArcInputMethodManagerServiceFactory::GetForBrowserContextForTesting(
context);
}
// static
BrowserContextKeyedServiceFactory* ArcInputMethodManagerService::GetFactory() {
return ArcInputMethodManagerServiceFactory::GetInstance();
}
ArcInputMethodManagerService::ArcInputMethodManagerService(
content::BrowserContext* context,
ArcBridgeService* bridge_service)
: profile_(Profile::FromBrowserContext(context)),
imm_bridge_(
std::make_unique<ArcInputMethodManagerBridgeImpl>(this,
bridge_service)),
is_virtual_keyboard_shown_(false),
is_updating_imm_entry_(false),
proxy_ime_extension_id_(
crx_file::id_util::GenerateId(kArcIMEProxyExtensionName)),
proxy_ime_engine_(std::make_unique<chromeos::InputMethodEngine>()),
tablet_mode_observer_(std::make_unique<TabletModeObserver>(this)),
input_method_observer_(std::make_unique<InputMethodObserver>(this)) {
auto* imm = chromeos::input_method::InputMethodManager::Get();
imm->AddObserver(this);
imm->AddImeMenuObserver(this);
proxy_ime_engine_->Initialize(
std::make_unique<InputMethodEngineObserver>(this),
proxy_ime_extension_id_.c_str(), profile_);
// TabletModeClient should be already created here because it's created in
// PreProfileInit() and this service is created in PostProfileInit().
DCHECK(TabletModeClient::Get());
TabletModeClient::Get()->AddObserver(tablet_mode_observer_.get());
chromeos::AccessibilityManager* accessibility_manager =
chromeos::AccessibilityManager::Get();
if (accessibility_manager) {
// accessibility_status_subscription_ ensures the callback is removed when
// ArcInputMethodManagerService is destroyed, so it's safe to use
// base::Unretained(this) here.
accessibility_status_subscription_ =
accessibility_manager->RegisterCallback(base::BindRepeating(
&ArcInputMethodManagerService::OnAccessibilityStatusChanged,
base::Unretained(this)));
}
DCHECK(ui::IMEBridge::Get());
ui::IMEBridge::Get()->AddObserver(this);
}
ArcInputMethodManagerService::~ArcInputMethodManagerService() = default;
void ArcInputMethodManagerService::SetInputMethodManagerBridgeForTesting(
std::unique_ptr<ArcInputMethodManagerBridge> test_bridge) {
imm_bridge_ = std::move(test_bridge);
}
void ArcInputMethodManagerService::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void ArcInputMethodManagerService::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
void ArcInputMethodManagerService::Shutdown() {
// Remove any Arc IME entry from preferences before shutting down.
// IME states (installed/enabled/disabled) are stored in Android's settings,
// that will be restored after Arc container starts next time.
RemoveArcIMEFromPrefs();
profile_->GetPrefs()->CommitPendingWrite();
if (input_method_) {
input_method_->RemoveObserver(input_method_observer_.get());
input_method_ = nullptr;
}
if (ui::IMEBridge::Get())
ui::IMEBridge::Get()->RemoveObserver(this);
if (TabletModeClient::Get())
TabletModeClient::Get()->RemoveObserver(tablet_mode_observer_.get());
auto* imm = chromeos::input_method::InputMethodManager::Get();
imm->RemoveImeMenuObserver(this);
imm->RemoveObserver(this);
}
void ArcInputMethodManagerService::OnActiveImeChanged(
const std::string& ime_id) {
if (ime_id == kChromeOSIMEIdInArcContainer) {
// Chrome OS Keyboard is selected in Android side.
auto* imm = chromeos::input_method::InputMethodManager::Get();
// Create a list of active Chrome OS IMEs.
auto active_imes = imm->GetActiveIMEState()->GetActiveInputMethodIds();
base::EraseIf(active_imes, chromeos::extension_ime_util::IsArcIME);
DCHECK(!active_imes.empty());
imm->GetActiveIMEState()->ChangeInputMethod(active_imes[0],
false /* show_message */);
return;
}
// an ARC IME is selected.
auto* imm = chromeos::input_method::InputMethodManager::Get();
imm->GetActiveIMEState()->ChangeInputMethod(
chromeos::extension_ime_util::GetArcInputMethodID(proxy_ime_extension_id_,
ime_id),
false /* show_message */);
}
void ArcInputMethodManagerService::OnImeDisabled(const std::string& ime_id) {
const std::string component_id =
chromeos::extension_ime_util::GetArcInputMethodID(proxy_ime_extension_id_,
ime_id);
// Remove the IME from the prefs to disable it.
const std::string active_ime_ids =
profile_->GetPrefs()->GetString(prefs::kLanguageEnabledImes);
std::vector<base::StringPiece> active_ime_list = base::SplitStringPiece(
active_ime_ids, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
base::Erase(active_ime_list, component_id);
profile_->GetPrefs()->SetString(prefs::kLanguageEnabledImes,
base::JoinString(active_ime_list, ","));
// Note: Since this is not about uninstalling the IME, this method does not
// modify InputMethodManager::State.
}
void ArcInputMethodManagerService::OnImeInfoChanged(
std::vector<mojom::ImeInfoPtr> ime_info_array) {
using chromeos::input_method::InputMethodDescriptor;
using chromeos::input_method::InputMethodDescriptors;
using chromeos::input_method::InputMethodManager;
base::AutoReset<bool> in_updating(&is_updating_imm_entry_, true);
scoped_refptr<InputMethodManager::State> state =
InputMethodManager::Get()->GetActiveIMEState();
const std::string active_ime_id = state->GetCurrentInputMethod().id();
// Remove the old registered entry.
state->RemoveInputMethodExtension(proxy_ime_extension_id_);
// Convert ime_info_array to InputMethodDescriptors.
InputMethodDescriptors descriptors;
std::vector<std::string> enabled_input_method_ids;
ime_ids_allowed_in_clamshell_mode_.clear();
for (const auto& ime_info : ime_info_array) {
const InputMethodDescriptor& descriptor =
BuildInputMethodDescriptor(ime_info.get());
descriptors.push_back(descriptor);
if (ime_info->enabled)
enabled_input_method_ids.push_back(descriptor.id());
if (ime_info->is_allowed_in_clamshell_mode)
ime_ids_allowed_in_clamshell_mode_.insert(descriptor.id());
}
if (descriptors.empty()) {
// If no ARC IME is installed, remove ARC IME entry from preferences.
RemoveArcIMEFromPrefs();
return;
}
// Add the proxy IME entry to InputMethodManager if any ARC IME is installed.
state->AddInputMethodExtension(proxy_ime_extension_id_, descriptors,
proxy_ime_engine_.get());
// Enable IMEs that are already enabled in the container.
const std::string active_ime_ids =
profile_->GetPrefs()->GetString(prefs::kLanguageEnabledImes);
std::vector<std::string> active_ime_list = base::SplitString(
active_ime_ids, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
// TODO(crbug.com/845079): We should keep the order of the IMEs as same as in
// chrome://settings
for (const auto& input_method_id : enabled_input_method_ids) {
if (!base::Contains(active_ime_list, input_method_id))
active_ime_list.push_back(input_method_id);
}
// Disable IMEs that are already disable in the container.
base::EraseIf(active_ime_list, [&enabled_input_method_ids](const auto& id) {
return chromeos::extension_ime_util::IsArcIME(id) &&
!base::Contains(enabled_input_method_ids, id);
});
profile_->GetPrefs()->SetString(prefs::kLanguageEnabledImes,
base::JoinString(active_ime_list, ","));
// Refresh allowed IME list.
UpdateArcIMEAllowed();
InputMethodManager::Get()->GetActiveIMEState()->ChangeInputMethod(
active_ime_id, false);
is_updating_imm_entry_ = false;
// Call ImeMenuListChanged() here to notify the latest state.
ImeMenuListChanged();
// If the active input method is changed, call InputMethodChanged() here.
if (active_ime_id != state->GetCurrentInputMethod().id())
InputMethodChanged(InputMethodManager::Get(), nullptr, false);
UMA_HISTOGRAM_COUNTS_100("Arc.ImeCount", descriptors.size());
}
void ArcInputMethodManagerService::OnConnectionClosed() {
// Remove all ARC IMEs from the list and prefs.
const bool opted_out = !arc::IsArcPlayStoreEnabledForProfile(profile_);
VLOG(1) << "Lost InputMethodManagerInstance. Reason="
<< (opted_out ? "opt-out" : "unknown");
// TODO(yhanada): Handle prefs better. For example, when this method is called
// because of the container crash (rather then opt-out), we might not want to
// modify the preference at all.
OnImeInfoChanged({});
}
void ArcInputMethodManagerService::ImeMenuListChanged() {
// Ignore ime menu list change while updating the old entry in
// |OnImeInfoChanged| not to expose temporary state to ARC++ container.
if (is_updating_imm_entry_)
return;
auto* manager = chromeos::input_method::InputMethodManager::Get();
auto new_active_ime_ids =
manager->GetActiveIMEState()->GetActiveInputMethodIds();
// Filter out non ARC IME ids.
std::set<std::string> new_arc_active_ime_ids;
std::copy_if(
new_active_ime_ids.begin(), new_active_ime_ids.end(),
std::inserter(new_arc_active_ime_ids, new_arc_active_ime_ids.end()),
[](const auto& id) {
return chromeos::extension_ime_util::IsArcIME(id);
});
// TODO(yhanada|yusukes): Instead of observing ImeMenuListChanged(), it's
// probably better to just observe the pref (and not disabling ones still
// in the prefs.) See also the comment below in the second for-loop.
std::set<std::string> active_ime_ids_on_prefs;
{
const std::string active_ime_ids =
profile_->GetPrefs()->GetString(prefs::kLanguageEnabledImes);
std::vector<base::StringPiece> active_ime_list = base::SplitStringPiece(
active_ime_ids, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
for (const auto& id : active_ime_list)
active_ime_ids_on_prefs.insert(id.as_string());
}
for (const auto& id : new_arc_active_ime_ids) {
// Enable the IME which is not currently enabled.
if (!active_arc_ime_ids_.count(id))
EnableIme(id, true /* enable */);
}
for (const auto& id : active_arc_ime_ids_) {
if (!new_arc_active_ime_ids.count(id) &&
!active_ime_ids_on_prefs.count(id)) {
// This path is taken in the following two cases:
// 1) The device is in tablet mode, and the user disabled the IME via
// chrome://settings.
// 2) The device was just switched to laptop mode, and this service
// disallowed Android IMEs.
// In the former case, |active_ime_ids_on_prefs| doesn't have the IME,
// but in the latter case, the set still has it. Here, disable the IME
// only for the former case so that the temporary deactivation of the
// IME on laptop mode wouldn't be propagated to the container. Otherwise,
// the IME confirmation dialog will be shown again next time when you
// use the IME in tablet mode.
// TODO(yhanada|yusukes): Only observe the prefs and remove the hack.
EnableIme(id, false /* enable */);
}
}
active_arc_ime_ids_.swap(new_arc_active_ime_ids);
}
void ArcInputMethodManagerService::InputMethodChanged(
chromeos::input_method::InputMethodManager* manager,
Profile* /* profile */,
bool /* show_message */) {
// Ignore input method change while updating the entry in |OnImeInfoChanged|
// not to expose temporary state to ARC++ container.
if (is_updating_imm_entry_)
return;
scoped_refptr<chromeos::input_method::InputMethodManager::State> state =
manager->GetActiveIMEState();
if (!state)
return;
SwitchImeTo(state->GetCurrentInputMethod().id());
if (chromeos::extension_ime_util::IsArcIME(
state->GetCurrentInputMethod().id())) {
// Disable fallback virtual keyboard while Android IME is activated.
SetKeyboardDisabled(true);
} else {
// Stop overriding virtual keyboard availability.
SetKeyboardDisabled(false);
}
}
void ArcInputMethodManagerService::OnInputContextHandlerChanged() {
if (ui::IMEBridge::Get()->GetInputContextHandler() == nullptr) {
if (input_method_)
input_method_->RemoveObserver(input_method_observer_.get());
input_method_ = nullptr;
return;
}
if (input_method_)
input_method_->RemoveObserver(input_method_observer_.get());
input_method_ =
ui::IMEBridge::Get()->GetInputContextHandler()->GetInputMethod();
if (input_method_)
input_method_->AddObserver(input_method_observer_.get());
}
void ArcInputMethodManagerService::OnAccessibilityStatusChanged(
const chromeos::AccessibilityStatusEventDetails& event_details) {
if (event_details.notification_type !=
chromeos::ACCESSIBILITY_TOGGLE_VIRTUAL_KEYBOARD) {
// This class is not interested in a11y events except toggling virtual
// keyboard event.
return;
}
UpdateArcIMEAllowed();
}
InputConnectionImpl*
ArcInputMethodManagerService::GetInputConnectionForTesting() {
return active_connection_.get();
}
void ArcInputMethodManagerService::EnableIme(const std::string& ime_id,
bool enable) {
auto component_id =
chromeos::extension_ime_util::GetComponentIDByInputMethodID(ime_id);
// TODO(yhanada): Disable the IME in Chrome OS side if it fails.
imm_bridge_->SendEnableIme(
component_id, enable,
base::BindOnce(
[](const std::string& ime_id, bool enable, bool success) {
if (!success) {
LOG(ERROR) << (enable ? "Enabling" : "Disabling") << " \""
<< ime_id << "\" failed";
}
},
ime_id, enable));
}
void ArcInputMethodManagerService::SwitchImeTo(const std::string& ime_id) {
namespace ceiu = chromeos::extension_ime_util;
std::string component_id = ceiu::GetComponentIDByInputMethodID(ime_id);
if (!ceiu::IsArcIME(ime_id))
component_id = kChromeOSIMEIdInArcContainer;
imm_bridge_->SendSwitchImeTo(
component_id, base::BindOnce(&SwitchImeToCallback, ime_id, component_id));
}
void ArcInputMethodManagerService::Focus(int context_id) {
if (!is_arc_ime_active_)
return;
DCHECK(!active_connection_);
active_connection_ = std::make_unique<InputConnectionImpl>(
proxy_ime_engine_.get(), imm_bridge_.get(), context_id);
mojom::InputConnectionPtr connection_ptr;
active_connection_->Bind(&connection_ptr);
imm_bridge_->SendFocus(std::move(connection_ptr),
active_connection_->GetTextInputState(false));
}
void ArcInputMethodManagerService::Blur() {
active_connection_.reset();
is_virtual_keyboard_shown_ = false;
}
void ArcInputMethodManagerService::UpdateTextInputState() {
if (!is_arc_ime_active_ || !active_connection_)
return;
active_connection_->UpdateTextInputState(
false /* is_input_state_update_requested */);
}
chromeos::input_method::InputMethodDescriptor
ArcInputMethodManagerService::BuildInputMethodDescriptor(
const mojom::ImeInfo* info) {
// We don't care too much about |layouts| at this point since the feature is
// for tablet mode.
const std::vector<std::string> layouts{"us"};
// Set the fake language so that the IME is shown in the special section in
// chrome://settings.
const std::vector<std::string> languages{
chromeos::extension_ime_util::kArcImeLanguage};
const std::string display_name = info->display_name;
const std::string& input_method_id =
chromeos::extension_ime_util::GetArcInputMethodID(proxy_ime_extension_id_,
info->ime_id);
// TODO(yhanada): Set the indicator string after the UI spec is finalized.
return chromeos::input_method::InputMethodDescriptor(
input_method_id, display_name, std::string() /* indicator */, layouts,
languages, false /* is_login_keyboard */, GURL(info->settings_url),
GURL() /* input_view_url */);
}
void ArcInputMethodManagerService::RemoveArcIMEFromPrefs() {
RemoveArcIMEFromPref(prefs::kLanguageEnabledImes);
RemoveArcIMEFromPref(prefs::kLanguagePreloadEngines);
PrefService* prefs = profile_->GetPrefs();
if (chromeos::extension_ime_util::IsArcIME(
prefs->GetString(prefs::kLanguageCurrentInputMethod))) {
prefs->SetString(prefs::kLanguageCurrentInputMethod, std::string());
}
if (chromeos::extension_ime_util::IsArcIME(
prefs->GetString(prefs::kLanguagePreviousInputMethod))) {
prefs->SetString(prefs::kLanguagePreviousInputMethod, std::string());
}
}
void ArcInputMethodManagerService::RemoveArcIMEFromPref(const char* pref_name) {
const std::string ime_ids = profile_->GetPrefs()->GetString(pref_name);
std::vector<base::StringPiece> ime_id_list = base::SplitStringPiece(
ime_ids, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
base::EraseIf(ime_id_list, [](base::StringPiece id) {
return chromeos::extension_ime_util::IsArcIME(id.as_string());
});
profile_->GetPrefs()->SetString(pref_name,
base::JoinString(ime_id_list, ","));
}
void ArcInputMethodManagerService::UpdateArcIMEAllowed() {
const bool allowed = ShouldArcIMEAllowed();
auto* manager = chromeos::input_method::InputMethodManager::Get();
std::set<std::string> allowed_method_ids_set;
{
std::vector<std::string> allowed_method_ids =
manager->GetActiveIMEState()->GetAllowedInputMethods();
allowed_method_ids_set = std::set<std::string>(allowed_method_ids.begin(),
allowed_method_ids.end());
}
chromeos::input_method::InputMethodDescriptors installed_imes;
if (manager->GetComponentExtensionIMEManager()) {
installed_imes = manager->GetComponentExtensionIMEManager()
->GetAllIMEAsInputMethodDescriptor();
}
{
chromeos::input_method::InputMethodDescriptors installed_extensions;
manager->GetActiveIMEState()->GetInputMethodExtensions(
&installed_extensions);
installed_imes.insert(installed_imes.end(), installed_extensions.begin(),
installed_extensions.end());
}
std::vector<std::string> ime_ids_to_enable;
if (allowed) {
if (!allowed_method_ids_set.empty()) {
// Some IMEs are not allowed now. Add ARC IMEs to
// |allowed_method_ids_set|.
for (const auto& desc : installed_imes) {
if (chromeos::extension_ime_util::IsArcIME(desc.id()))
allowed_method_ids_set.insert(desc.id());
}
}
// Re-enable ARC IMEs that were auto-disabled when toggling to laptop mode.
const std::string active_ime_ids =
profile_->GetPrefs()->GetString(prefs::kLanguageEnabledImes);
std::vector<base::StringPiece> active_ime_list = base::SplitStringPiece(
active_ime_ids, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
for (const auto& id : active_ime_list) {
if (chromeos::extension_ime_util::IsArcIME(id.as_string()))
ime_ids_to_enable.push_back(id.as_string());
}
} else {
// Disallow Arc IMEs.
if (allowed_method_ids_set.empty()) {
// Currently there is no restriction. Add all IMEs except ARC IMEs to
// |allowed_method_ids_set|.
for (const auto& desc : installed_imes) {
if (!chromeos::extension_ime_util::IsArcIME(desc.id()) ||
ime_ids_allowed_in_clamshell_mode_.count(desc.id())) {
allowed_method_ids_set.insert(desc.id());
}
}
} else {
// Remove ARC IMEs from |allowed_method_ids_set|.
base::EraseIf(allowed_method_ids_set, [this](const std::string& id) {
return chromeos::extension_ime_util::IsArcIME(id) &&
!ime_ids_allowed_in_clamshell_mode_.count(id);
});
// Add back IMEs allowed in clamshell mode.
for (const auto& ime_id : ime_ids_allowed_in_clamshell_mode_)
allowed_method_ids_set.insert(ime_id);
}
DCHECK(!allowed_method_ids_set.empty());
}
manager->GetActiveIMEState()->SetAllowedInputMethods(
std::vector<std::string>(allowed_method_ids_set.begin(),
allowed_method_ids_set.end()),
false /* enable_allowed_input_methods */);
// This has to be called after SetAllowedInputMethods() because enabling an
// IME that is disallowed always fails.
for (const auto& id : ime_ids_to_enable)
manager->GetActiveIMEState()->EnableInputMethod(id);
}
bool ArcInputMethodManagerService::ShouldArcIMEAllowed() const {
const bool is_command_line_flag_enabled =
base::CommandLine::ForCurrentProcess()->HasSwitch(
keyboard::switches::kEnableVirtualKeyboard);
const bool is_normal_vk_enabled =
!profile_->GetPrefs()->GetBoolean(
ash::prefs::kAccessibilityVirtualKeyboardEnabled) &&
TabletModeClient::Get()->tablet_mode_enabled();
return is_command_line_flag_enabled || is_normal_vk_enabled;
}
void ArcInputMethodManagerService::NotifyInputMethodManagerObservers(
bool is_tablet_mode) {
// Togging the mode may enable or disable all the ARC IMEs. To dynamically
// reflect the potential state changes to chrome://settings, notify the
// manager's observers here.
// TODO(yusukes): This is a temporary workaround for supporting ARC IMEs
// and supports neither Chrome OS extensions nor state changes enforced by
// the policy. The better way to do this is to add a dedicated event to
// language_settings_private.idl and send the new event to the JS side
// instead.
auto* manager = chromeos::input_method::InputMethodManager::Get();
if (!manager)
return;
if (is_tablet_mode)
manager->NotifyInputMethodExtensionRemoved(proxy_ime_extension_id_);
else
manager->NotifyInputMethodExtensionAdded(proxy_ime_extension_id_);
}
bool ArcInputMethodManagerService::IsVirtualKeyboardShown() const {
return is_virtual_keyboard_shown_;
}
void ArcInputMethodManagerService::SendShowVirtualKeyboard() {
if (!is_arc_ime_active_)
return;
imm_bridge_->SendShowVirtualKeyboard();
// TODO(yhanada): Should observe IME window size changes.
is_virtual_keyboard_shown_ = true;
NotifyVirtualKeyboardVisibilityChange(true);
}
void ArcInputMethodManagerService::SendHideVirtualKeyboard() {
if (!is_arc_ime_active_)
return;
imm_bridge_->SendHideVirtualKeyboard();
// TODO(yhanada): Should observe IME window size changes.
is_virtual_keyboard_shown_ = false;
NotifyVirtualKeyboardVisibilityChange(false);
}
void ArcInputMethodManagerService::NotifyVirtualKeyboardVisibilityChange(
bool visible) {
for (auto& observer : observers_)
observer.OnAndroidVirtualKeyboardVisibilityChanged(visible);
}
} // namespace arc