1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/chromeos/arc/input_method_manager/arc_input_method_manager_service.h"
6
7 #include <algorithm>
8 #include <utility>
9
10 #include "ash/public/cpp/ash_pref_names.h"
11 #include "ash/public/cpp/keyboard/arc/arc_input_method_bounds_tracker.h"
12 #include "ash/public/cpp/keyboard/keyboard_switches.h"
13 #include "ash/public/cpp/tablet_mode.h"
14 #include "ash/public/cpp/tablet_mode_observer.h"
15 #include "base/bind.h"
16 #include "base/command_line.h"
17 #include "base/logging.h"
18 #include "base/memory/singleton.h"
19 #include "base/metrics/histogram_macros.h"
20 #include "base/stl_util.h"
21 #include "base/strings/string_piece.h"
22 #include "base/strings/string_split.h"
23 #include "chrome/browser/chromeos/arc/arc_util.h"
24 #include "chrome/browser/chromeos/arc/input_method_manager/arc_input_method_manager_bridge_impl.h"
25 #include "chrome/browser/profiles/profile.h"
26 #include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h"
27 #include "chrome/common/pref_names.h"
28 #include "components/arc/arc_browser_context_keyed_service_factory_base.h"
29 #include "components/arc/mojom/ime_mojom_traits.h"
30 #include "components/crx_file/id_util.h"
31 #include "components/prefs/pref_service.h"
32 #include "mojo/public/cpp/bindings/pending_remote.h"
33 #include "ui/base/ime/chromeos/component_extension_ime_manager.h"
34 #include "ui/base/ime/chromeos/extension_ime_util.h"
35 #include "ui/base/ime/chromeos/ime_bridge.h"
36 #include "ui/base/ime/chromeos/input_method_util.h"
37 #include "ui/base/ime/input_method_observer.h"
38
39 namespace arc {
40
41 namespace {
42
43 // The Android IME id of the pre-installed IME to proxy Chrome OS IME's actions
44 // to inside the container.
45 // Please refer to ArcImeService for the implementation details.
46 constexpr char kChromeOSIMEIdInArcContainer[] =
47 "org.chromium.arc.ime/.ArcInputMethodService";
48
49 // The name of the proxy IME extension that is used when registering ARC IMEs to
50 // InputMethodManager.
51 constexpr char kArcIMEProxyExtensionName[] =
52 "org.chromium.arc.inputmethod.proxy";
53
SwitchImeToCallback(const std::string & ime_id,const std::string & component_id,bool success)54 void SwitchImeToCallback(const std::string& ime_id,
55 const std::string& component_id,
56 bool success) {
57 if (success)
58 return;
59
60 // TODO(yhanana): We should prevent InputMethodManager from changing current
61 // input method until this callback is called with true and once it's done the
62 // IME switching code below can be removed.
63 LOG(ERROR) << "Switch the active IME to \"" << ime_id << "\"(component_id=\""
64 << component_id << "\") failed";
65 auto* imm = chromeos::input_method::InputMethodManager::Get();
66 if (imm && imm->GetActiveIMEState()) {
67 for (const auto& id : imm->GetActiveIMEState()->GetActiveInputMethodIds()) {
68 if (!chromeos::extension_ime_util::IsArcIME(id)) {
69 imm->GetActiveIMEState()->ChangeInputMethod(id,
70 false /* show_message */);
71 return;
72 }
73 }
74 }
75 NOTREACHED() << "There is no enabled non-ARC IME.";
76 }
77
SetKeyboardDisabled(bool disabled)78 void SetKeyboardDisabled(bool disabled) {
79 if (disabled) {
80 ChromeKeyboardControllerClient::Get()->SetEnableFlag(
81 keyboard::KeyboardEnableFlag::kAndroidDisabled);
82 } else {
83 ChromeKeyboardControllerClient::Get()->ClearEnableFlag(
84 keyboard::KeyboardEnableFlag::kAndroidDisabled);
85 }
86 }
87
88 // Singleton factory for ArcInputMethodManagerService
89 class ArcInputMethodManagerServiceFactory
90 : public internal::ArcBrowserContextKeyedServiceFactoryBase<
91 ArcInputMethodManagerService,
92 ArcInputMethodManagerServiceFactory> {
93 public:
94 // Factory name used by ArcBrowserContextKeyedServiceFactoryBase
95 static constexpr const char* kName = "ArcInputMethodManagerServiceFactory";
96
GetInstance()97 static ArcInputMethodManagerServiceFactory* GetInstance() {
98 return base::Singleton<ArcInputMethodManagerServiceFactory>::get();
99 }
100
101 private:
102 friend base::DefaultSingletonTraits<ArcInputMethodManagerServiceFactory>;
103 ArcInputMethodManagerServiceFactory() = default;
104 ~ArcInputMethodManagerServiceFactory() override = default;
105 };
106
107 class ArcInputMethodStateDelegateImpl : public ArcInputMethodState::Delegate {
108 public:
ArcInputMethodStateDelegateImpl(Profile * profile)109 explicit ArcInputMethodStateDelegateImpl(Profile* profile)
110 : profile_(profile) {}
111 ArcInputMethodStateDelegateImpl(const ArcInputMethodStateDelegateImpl&) =
112 delete;
113 ArcInputMethodStateDelegateImpl& operator=(
114 const ArcInputMethodStateDelegateImpl& state) = delete;
115 ~ArcInputMethodStateDelegateImpl() override = default;
116
117 // Returns whether ARC IMEs should be allowed now or not.
118 // It depends on tablet mode state and a11y keyboard option.
ShouldArcIMEAllowed() const119 bool ShouldArcIMEAllowed() const override {
120 const bool is_command_line_flag_enabled =
121 base::CommandLine::ForCurrentProcess()->HasSwitch(
122 keyboard::switches::kEnableVirtualKeyboard);
123 const bool is_normal_vk_enabled =
124 !profile_->GetPrefs()->GetBoolean(
125 ash::prefs::kAccessibilityVirtualKeyboardEnabled) &&
126 ash::TabletMode::Get()->InTabletMode();
127 return is_command_line_flag_enabled || is_normal_vk_enabled;
128 }
129
BuildInputMethodDescriptor(const mojom::ImeInfoPtr & info) const130 chromeos::input_method::InputMethodDescriptor BuildInputMethodDescriptor(
131 const mojom::ImeInfoPtr& info) const override {
132 // We don't care too much about |layouts| at this point since the feature is
133 // for tablet mode.
134 const std::vector<std::string> layouts{"us"};
135
136 // Set the fake language so that the IME is shown in the special section in
137 // chrome://settings.
138 const std::vector<std::string> languages{
139 chromeos::extension_ime_util::kArcImeLanguage};
140
141 const std::string display_name = info->display_name;
142
143 const std::string proxy_ime_extension_id =
144 crx_file::id_util::GenerateId(kArcIMEProxyExtensionName);
145 const std::string& input_method_id =
146 chromeos::extension_ime_util::GetArcInputMethodID(
147 proxy_ime_extension_id, info->ime_id);
148 // TODO(yhanada): Set the indicator string after the UI spec is finalized.
149 return chromeos::input_method::InputMethodDescriptor(
150 input_method_id, display_name, std::string() /* indicator */, layouts,
151 languages, false /* is_login_keyboard */, GURL(info->settings_url),
152 GURL() /* input_view_url */);
153 }
154
155 private:
156 Profile* const profile_;
157 };
158
159 } // namespace
160
161 class ArcInputMethodManagerService::ArcInputMethodBoundsObserver
162 : public ash::ArcInputMethodBoundsTracker::Observer {
163 public:
ArcInputMethodBoundsObserver(ArcInputMethodManagerService * owner)164 explicit ArcInputMethodBoundsObserver(ArcInputMethodManagerService* owner)
165 : owner_(owner) {
166 ash::ArcInputMethodBoundsTracker* tracker =
167 ash::ArcInputMethodBoundsTracker::Get();
168 if (tracker)
169 tracker->AddObserver(this);
170 }
171 ArcInputMethodBoundsObserver(const ArcInputMethodBoundsObserver&) = delete;
~ArcInputMethodBoundsObserver()172 ~ArcInputMethodBoundsObserver() override {
173 ash::ArcInputMethodBoundsTracker* tracker =
174 ash::ArcInputMethodBoundsTracker::Get();
175 if (tracker)
176 tracker->RemoveObserver(this);
177 }
178
OnArcInputMethodBoundsChanged(const gfx::Rect & bounds)179 void OnArcInputMethodBoundsChanged(const gfx::Rect& bounds) override {
180 owner_->OnArcInputMethodBoundsChanged(bounds);
181 }
182
183 private:
184 ArcInputMethodManagerService* owner_;
185 };
186
187 class ArcInputMethodManagerService::InputMethodEngineObserver
188 : public chromeos::InputMethodEngineBase::Observer {
189 public:
InputMethodEngineObserver(ArcInputMethodManagerService * owner)190 explicit InputMethodEngineObserver(ArcInputMethodManagerService* owner)
191 : owner_(owner) {}
192 ~InputMethodEngineObserver() override = default;
193
194 // chromeos::InputMethodEngineBase::Observer overrides:
OnActivate(const std::string & engine_id)195 void OnActivate(const std::string& engine_id) override {
196 owner_->is_arc_ime_active_ = true;
197 // TODO(yhanada): Remove this line after we migrate to SPM completely.
198 owner_->OnInputContextHandlerChanged();
199 }
OnFocus(const ui::IMEEngineHandlerInterface::InputContext & context)200 void OnFocus(
201 const ui::IMEEngineHandlerInterface::InputContext& context) override {
202 owner_->Focus(context.id);
203 }
OnBlur(int context_id)204 void OnBlur(int context_id) override { owner_->Blur(); }
OnKeyEvent(const std::string & engine_id,const chromeos::InputMethodEngineBase::KeyboardEvent & event,ui::IMEEngineHandlerInterface::KeyEventDoneCallback key_data)205 void OnKeyEvent(
206 const std::string& engine_id,
207 const chromeos::InputMethodEngineBase::KeyboardEvent& event,
208 ui::IMEEngineHandlerInterface::KeyEventDoneCallback key_data) override {
209 if (event.key_code == ui::VKEY_BROWSER_BACK && event.type == "keydown" &&
210 owner_->IsVirtualKeyboardShown()) {
211 // Back button on the shelf is pressed. We should consume only "keydown"
212 // events here to make sure that Android side receives "keyup" events
213 // always to prevent never-ending key repeat from happening.
214 owner_->SendHideVirtualKeyboard();
215 std::move(key_data).Run(true);
216 return;
217 }
218 std::move(key_data).Run(false);
219 }
OnReset(const std::string & engine_id)220 void OnReset(const std::string& engine_id) override {}
OnDeactivated(const std::string & engine_id)221 void OnDeactivated(const std::string& engine_id) override {
222 owner_->is_arc_ime_active_ = false;
223 // TODO(yhanada): Remove this line after we migrate to SPM completely.
224 owner_->OnInputContextHandlerChanged();
225 }
OnCompositionBoundsChanged(const std::vector<gfx::Rect> & bounds)226 void OnCompositionBoundsChanged(
227 const std::vector<gfx::Rect>& bounds) override {}
OnSurroundingTextChanged(const std::string & engine_id,const base::string16 & text,int cursor_pos,int anchor_pos,int offset_pos)228 void OnSurroundingTextChanged(const std::string& engine_id,
229 const base::string16& text,
230 int cursor_pos,
231 int anchor_pos,
232 int offset_pos) override {
233 owner_->UpdateTextInputState();
234 }
OnInputContextUpdate(const ui::IMEEngineHandlerInterface::InputContext & context)235 void OnInputContextUpdate(
236 const ui::IMEEngineHandlerInterface::InputContext& context) override {
237 owner_->UpdateTextInputState();
238 }
OnCandidateClicked(const std::string & component_id,int candidate_id,chromeos::InputMethodEngineBase::MouseButtonEvent button)239 void OnCandidateClicked(
240 const std::string& component_id,
241 int candidate_id,
242 chromeos::InputMethodEngineBase::MouseButtonEvent button) override {}
OnMenuItemActivated(const std::string & component_id,const std::string & menu_id)243 void OnMenuItemActivated(const std::string& component_id,
244 const std::string& menu_id) override {}
OnScreenProjectionChanged(bool is_projected)245 void OnScreenProjectionChanged(bool is_projected) override {}
OnSuggestionsChanged(const std::vector<std::string> & suggestions)246 void OnSuggestionsChanged(
247 const std::vector<std::string>& suggestions) override {}
OnInputMethodOptionsChanged(const std::string & engine_id)248 void OnInputMethodOptionsChanged(const std::string& engine_id) override {}
249
250 private:
251 ArcInputMethodManagerService* const owner_;
252
253 DISALLOW_COPY_AND_ASSIGN(InputMethodEngineObserver);
254 };
255
256 class ArcInputMethodManagerService::InputMethodObserver
257 : public ui::InputMethodObserver {
258 public:
InputMethodObserver(ArcInputMethodManagerService * owner)259 explicit InputMethodObserver(ArcInputMethodManagerService* owner)
260 : owner_(owner) {}
261 ~InputMethodObserver() override = default;
262
263 // ui::InputMethodObserver overrides:
OnFocus()264 void OnFocus() override {}
OnBlur()265 void OnBlur() override {}
OnCaretBoundsChanged(const ui::TextInputClient * client)266 void OnCaretBoundsChanged(const ui::TextInputClient* client) override {}
OnTextInputStateChanged(const ui::TextInputClient * client)267 void OnTextInputStateChanged(const ui::TextInputClient* client) override {}
OnInputMethodDestroyed(const ui::InputMethod * input_method)268 void OnInputMethodDestroyed(const ui::InputMethod* input_method) override {
269 owner_->input_method_ = nullptr;
270 }
OnShowVirtualKeyboardIfEnabled()271 void OnShowVirtualKeyboardIfEnabled() override {
272 owner_->SendShowVirtualKeyboard();
273 }
274
275 private:
276 ArcInputMethodManagerService* const owner_;
277
278 DISALLOW_COPY_AND_ASSIGN(InputMethodObserver);
279 };
280
281 class ArcInputMethodManagerService::TabletModeObserver
282 : public ash::TabletModeObserver {
283 public:
TabletModeObserver(ArcInputMethodManagerService * owner)284 explicit TabletModeObserver(ArcInputMethodManagerService* owner)
285 : owner_(owner) {}
286 ~TabletModeObserver() override = default;
287
288 // ash::TabletModeObserver overrides:
OnTabletModeStarted()289 void OnTabletModeStarted() override { OnTabletModeToggled(true); }
OnTabletModeEnded()290 void OnTabletModeEnded() override { OnTabletModeToggled(false); }
291
292 private:
OnTabletModeToggled(bool enabled)293 void OnTabletModeToggled(bool enabled) {
294 owner_->OnTabletModeToggled(enabled);
295 owner_->NotifyInputMethodManagerObservers(enabled);
296 }
297
298 ArcInputMethodManagerService* owner_;
299
300 DISALLOW_COPY_AND_ASSIGN(TabletModeObserver);
301 };
302
303 // static
304 ArcInputMethodManagerService*
GetForBrowserContext(content::BrowserContext * context)305 ArcInputMethodManagerService::GetForBrowserContext(
306 content::BrowserContext* context) {
307 return ArcInputMethodManagerServiceFactory::GetForBrowserContext(context);
308 }
309
310 // static
311 ArcInputMethodManagerService*
GetForBrowserContextForTesting(content::BrowserContext * context)312 ArcInputMethodManagerService::GetForBrowserContextForTesting(
313 content::BrowserContext* context) {
314 return ArcInputMethodManagerServiceFactory::GetForBrowserContextForTesting(
315 context);
316 }
317
318 // static
GetFactory()319 BrowserContextKeyedServiceFactory* ArcInputMethodManagerService::GetFactory() {
320 return ArcInputMethodManagerServiceFactory::GetInstance();
321 }
322
ArcInputMethodManagerService(content::BrowserContext * context,ArcBridgeService * bridge_service)323 ArcInputMethodManagerService::ArcInputMethodManagerService(
324 content::BrowserContext* context,
325 ArcBridgeService* bridge_service)
326 : profile_(Profile::FromBrowserContext(context)),
327 imm_bridge_(
328 std::make_unique<ArcInputMethodManagerBridgeImpl>(this,
329 bridge_service)),
330 arc_ime_state_delegate_(
331 std::make_unique<ArcInputMethodStateDelegateImpl>(profile_)),
332 arc_ime_state_(arc_ime_state_delegate_.get()),
333 is_virtual_keyboard_shown_(false),
334 is_updating_imm_entry_(false),
335 proxy_ime_extension_id_(
336 crx_file::id_util::GenerateId(kArcIMEProxyExtensionName)),
337 proxy_ime_engine_(std::make_unique<chromeos::InputMethodEngine>()),
338 tablet_mode_observer_(std::make_unique<TabletModeObserver>(this)),
339 input_method_observer_(std::make_unique<InputMethodObserver>(this)),
340 input_method_bounds_observer_(
341 std::make_unique<ArcInputMethodBoundsObserver>(this)) {
342 auto* imm = chromeos::input_method::InputMethodManager::Get();
343 imm->AddObserver(this);
344 imm->AddImeMenuObserver(this);
345
346 proxy_ime_engine_->Initialize(
347 std::make_unique<InputMethodEngineObserver>(this),
348 proxy_ime_extension_id_.c_str(), profile_);
349
350 ash::TabletMode::Get()->AddObserver(tablet_mode_observer_.get());
351
352 chromeos::AccessibilityManager* accessibility_manager =
353 chromeos::AccessibilityManager::Get();
354 if (accessibility_manager) {
355 // accessibility_status_subscription_ ensures the callback is removed when
356 // ArcInputMethodManagerService is destroyed, so it's safe to use
357 // base::Unretained(this) here.
358 accessibility_status_subscription_ =
359 accessibility_manager->RegisterCallback(base::BindRepeating(
360 &ArcInputMethodManagerService::OnAccessibilityStatusChanged,
361 base::Unretained(this)));
362 }
363
364 DCHECK(ui::IMEBridge::Get());
365 ui::IMEBridge::Get()->AddObserver(this);
366 }
367
368 ArcInputMethodManagerService::~ArcInputMethodManagerService() = default;
369
SetInputMethodManagerBridgeForTesting(std::unique_ptr<ArcInputMethodManagerBridge> test_bridge)370 void ArcInputMethodManagerService::SetInputMethodManagerBridgeForTesting(
371 std::unique_ptr<ArcInputMethodManagerBridge> test_bridge) {
372 imm_bridge_ = std::move(test_bridge);
373 }
374
AddObserver(Observer * observer)375 void ArcInputMethodManagerService::AddObserver(Observer* observer) {
376 observers_.AddObserver(observer);
377 }
378
RemoveObserver(Observer * observer)379 void ArcInputMethodManagerService::RemoveObserver(Observer* observer) {
380 observers_.RemoveObserver(observer);
381 }
382
Shutdown()383 void ArcInputMethodManagerService::Shutdown() {
384 // Remove any Arc IME entry from preferences before shutting down.
385 // IME states (installed/enabled/disabled) are stored in Android's settings,
386 // that will be restored after Arc container starts next time.
387 RemoveArcIMEFromPrefs();
388 profile_->GetPrefs()->CommitPendingWrite();
389
390 if (input_method_) {
391 input_method_->RemoveObserver(input_method_observer_.get());
392 input_method_ = nullptr;
393 }
394
395 if (ui::IMEBridge::Get())
396 ui::IMEBridge::Get()->RemoveObserver(this);
397
398 if (ash::TabletMode::Get())
399 ash::TabletMode::Get()->RemoveObserver(tablet_mode_observer_.get());
400
401 auto* imm = chromeos::input_method::InputMethodManager::Get();
402 imm->RemoveImeMenuObserver(this);
403 imm->RemoveObserver(this);
404 }
405
OnActiveImeChanged(const std::string & ime_id)406 void ArcInputMethodManagerService::OnActiveImeChanged(
407 const std::string& ime_id) {
408 if (ime_id == kChromeOSIMEIdInArcContainer) {
409 // Chrome OS Keyboard is selected in Android side.
410 auto* imm = chromeos::input_method::InputMethodManager::Get();
411 // Create a list of active Chrome OS IMEs.
412 auto active_imes = imm->GetActiveIMEState()->GetActiveInputMethodIds();
413 base::EraseIf(active_imes, chromeos::extension_ime_util::IsArcIME);
414 DCHECK(!active_imes.empty());
415 imm->GetActiveIMEState()->ChangeInputMethod(active_imes[0],
416 false /* show_message */);
417 return;
418 }
419
420 // an ARC IME is selected.
421 auto* imm = chromeos::input_method::InputMethodManager::Get();
422 imm->GetActiveIMEState()->ChangeInputMethod(
423 chromeos::extension_ime_util::GetArcInputMethodID(proxy_ime_extension_id_,
424 ime_id),
425 false /* show_message */);
426 }
427
OnImeDisabled(const std::string & ime_id)428 void ArcInputMethodManagerService::OnImeDisabled(const std::string& ime_id) {
429 arc_ime_state_.DisableInputMethod(ime_id);
430
431 // Remove the IME from the prefs to disable it.
432 const std::string active_ime_ids =
433 profile_->GetPrefs()->GetString(prefs::kLanguageEnabledImes);
434 std::vector<std::string> active_ime_list = base::SplitString(
435 active_ime_ids, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
436
437 base::EraseIf(active_ime_list, [](const auto& id) {
438 return chromeos::extension_ime_util::IsArcIME(id);
439 });
440 for (const auto& descriptor : arc_ime_state_.GetEnabledInputMethods())
441 active_ime_list.push_back(descriptor.id());
442
443 profile_->GetPrefs()->SetString(prefs::kLanguageEnabledImes,
444 base::JoinString(active_ime_list, ","));
445
446 // Note: Since this is not about uninstalling the IME, this method does not
447 // modify InputMethodManager::State.
448 }
449
OnImeInfoChanged(std::vector<mojom::ImeInfoPtr> ime_info_array)450 void ArcInputMethodManagerService::OnImeInfoChanged(
451 std::vector<mojom::ImeInfoPtr> ime_info_array) {
452 arc_ime_state_.InitializeWithImeInfo(proxy_ime_extension_id_, ime_info_array);
453 UpdateInputMethodEntryWithImeInfo();
454 }
455
UpdateInputMethodEntryWithImeInfo()456 void ArcInputMethodManagerService::UpdateInputMethodEntryWithImeInfo() {
457 using chromeos::input_method::InputMethodDescriptor;
458 using chromeos::input_method::InputMethodDescriptors;
459 using chromeos::input_method::InputMethodManager;
460
461 InputMethodManager* imm = InputMethodManager::Get();
462 if (!imm || !imm->GetActiveIMEState()) {
463 LOG(WARNING) << "InputMethodManager is not ready yet.";
464 return;
465 }
466
467 base::AutoReset<bool> in_updating(&is_updating_imm_entry_, true);
468 scoped_refptr<InputMethodManager::State> state = imm->GetActiveIMEState();
469 const std::string active_ime_id = state->GetCurrentInputMethod().id();
470
471 // Remove the old registered entry.
472 state->RemoveInputMethodExtension(proxy_ime_extension_id_);
473
474 const InputMethodDescriptors installed_imes =
475 arc_ime_state_.GetActiveInputMethods();
476 if (installed_imes.empty()) {
477 // If no ARC IME is installed or allowed, remove ARC IME entry from
478 // preferences.
479 RemoveArcIMEFromPrefs();
480 return;
481 }
482
483 // Add the proxy IME entry to InputMethodManager if any ARC IME is installed.
484 state->AddInputMethodExtension(proxy_ime_extension_id_, installed_imes,
485 proxy_ime_engine_.get());
486
487 // Enable IMEs that are already enabled in the container.
488 // TODO(crbug.com/845079): We should keep the order of the IMEs as same as in
489 // chrome://settings
490
491 const std::string active_ime_ids =
492 profile_->GetPrefs()->GetString(prefs::kLanguageEnabledImes);
493 std::vector<std::string> active_ime_list = base::SplitString(
494 active_ime_ids, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
495
496 // Remove all ARC IMEs at first.
497 base::EraseIf(active_ime_list, [](const auto& id) {
498 return chromeos::extension_ime_util::IsArcIME(id);
499 });
500 // Re-add enabled and allowed IMEs.
501 for (const auto& descriptor : arc_ime_state_.GetEnabledInputMethods())
502 active_ime_list.push_back(descriptor.id());
503
504 // Set the pref.
505 profile_->GetPrefs()->SetString(prefs::kLanguageEnabledImes,
506 base::JoinString(active_ime_list, ","));
507
508 for (const auto& descriptor : arc_ime_state_.GetEnabledInputMethods())
509 state->EnableInputMethod(descriptor.id());
510
511 state->ChangeInputMethod(active_ime_id, false);
512 is_updating_imm_entry_ = false;
513
514 // Call ImeMenuListChanged() here to notify the latest state.
515 ImeMenuListChanged();
516 // If the active input method is changed, call InputMethodChanged() here.
517 if (active_ime_id != state->GetCurrentInputMethod().id())
518 InputMethodChanged(InputMethodManager::Get(), nullptr, false);
519
520 UMA_HISTOGRAM_COUNTS_100("Arc.ImeCount", installed_imes.size());
521 }
522
OnConnectionClosed()523 void ArcInputMethodManagerService::OnConnectionClosed() {
524 // Remove all ARC IMEs from the list and prefs.
525 const bool opted_out = !arc::IsArcPlayStoreEnabledForProfile(profile_);
526 VLOG(1) << "Lost InputMethodManagerInstance. Reason="
527 << (opted_out ? "opt-out" : "unknown");
528 // TODO(yhanada): Handle prefs better. For example, when this method is called
529 // because of the container crash (rather then opt-out), we might not want to
530 // modify the preference at all.
531 OnImeInfoChanged({});
532 }
533
ImeMenuListChanged()534 void ArcInputMethodManagerService::ImeMenuListChanged() {
535 // Ignore ime menu list change while updating the old entry in
536 // |OnImeInfoChanged| not to expose temporary state to ARC++ container.
537 if (is_updating_imm_entry_)
538 return;
539
540 auto* manager = chromeos::input_method::InputMethodManager::Get();
541 if (!manager || !manager->GetActiveIMEState()) {
542 LOG(WARNING) << "InputMethodManager is not ready yet";
543 return;
544 }
545
546 auto new_active_ime_ids =
547 manager->GetActiveIMEState()->GetActiveInputMethodIds();
548
549 // Filter out non ARC IME ids.
550 std::set<std::string> new_arc_active_ime_ids;
551 std::copy_if(
552 new_active_ime_ids.begin(), new_active_ime_ids.end(),
553 std::inserter(new_arc_active_ime_ids, new_arc_active_ime_ids.end()),
554 [](const auto& id) {
555 return chromeos::extension_ime_util::IsArcIME(id);
556 });
557
558 // TODO(yhanada|yusukes): Instead of observing ImeMenuListChanged(), it's
559 // probably better to just observe the pref (and not disabling ones still
560 // in the prefs.) See also the comment below in the second for-loop.
561 std::set<std::string> active_ime_ids_on_prefs;
562 {
563 const std::string active_ime_ids =
564 profile_->GetPrefs()->GetString(prefs::kLanguageEnabledImes);
565 std::vector<base::StringPiece> active_ime_list = base::SplitStringPiece(
566 active_ime_ids, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
567 for (const auto& id : active_ime_list)
568 active_ime_ids_on_prefs.insert(id.as_string());
569 }
570
571 for (const auto& id : new_arc_active_ime_ids) {
572 // Enable the IME which is not currently enabled.
573 if (!active_arc_ime_ids_.count(id))
574 EnableIme(id, true /* enable */);
575 }
576
577 for (const auto& id : active_arc_ime_ids_) {
578 if (!new_arc_active_ime_ids.count(id) &&
579 !active_ime_ids_on_prefs.count(id)) {
580 // This path is taken in the following two cases:
581 // 1) The device is in tablet mode, and the user disabled the IME via
582 // chrome://settings.
583 // 2) The device was just switched to laptop mode, and this service
584 // disallowed Android IMEs.
585 // In the former case, |active_ime_ids_on_prefs| doesn't have the IME,
586 // but in the latter case, the set still has it. Here, disable the IME
587 // only for the former case so that the temporary deactivation of the
588 // IME on laptop mode wouldn't be propagated to the container. Otherwise,
589 // the IME confirmation dialog will be shown again next time when you
590 // use the IME in tablet mode.
591 // TODO(yhanada|yusukes): Only observe the prefs and remove the hack.
592 EnableIme(id, false /* enable */);
593 }
594 }
595 active_arc_ime_ids_.swap(new_arc_active_ime_ids);
596 }
597
InputMethodChanged(chromeos::input_method::InputMethodManager * manager,Profile *,bool)598 void ArcInputMethodManagerService::InputMethodChanged(
599 chromeos::input_method::InputMethodManager* manager,
600 Profile* /* profile */,
601 bool /* show_message */) {
602 // Ignore input method change while updating the entry in |OnImeInfoChanged|
603 // not to expose temporary state to ARC++ container.
604 if (is_updating_imm_entry_)
605 return;
606
607 scoped_refptr<chromeos::input_method::InputMethodManager::State> state =
608 manager->GetActiveIMEState();
609 if (!state)
610 return;
611 SwitchImeTo(state->GetCurrentInputMethod().id());
612
613 if (chromeos::extension_ime_util::IsArcIME(
614 state->GetCurrentInputMethod().id())) {
615 // Disable fallback virtual keyboard while Android IME is activated.
616 SetKeyboardDisabled(true);
617 } else {
618 // Stop overriding virtual keyboard availability.
619 SetKeyboardDisabled(false);
620 }
621 }
622
OnInputContextHandlerChanged()623 void ArcInputMethodManagerService::OnInputContextHandlerChanged() {
624 if (ui::IMEBridge::Get()->GetInputContextHandler() == nullptr) {
625 if (input_method_)
626 input_method_->RemoveObserver(input_method_observer_.get());
627 input_method_ = nullptr;
628 return;
629 }
630
631 if (input_method_)
632 input_method_->RemoveObserver(input_method_observer_.get());
633 input_method_ =
634 ui::IMEBridge::Get()->GetInputContextHandler()->GetInputMethod();
635 if (input_method_)
636 input_method_->AddObserver(input_method_observer_.get());
637 }
638
OnAccessibilityStatusChanged(const chromeos::AccessibilityStatusEventDetails & event_details)639 void ArcInputMethodManagerService::OnAccessibilityStatusChanged(
640 const chromeos::AccessibilityStatusEventDetails& event_details) {
641 if (event_details.notification_type !=
642 chromeos::ACCESSIBILITY_TOGGLE_VIRTUAL_KEYBOARD) {
643 // This class is not interested in a11y events except toggling virtual
644 // keyboard event.
645 return;
646 }
647
648 UpdateInputMethodEntryWithImeInfo();
649 }
650
OnArcInputMethodBoundsChanged(const gfx::Rect & bounds)651 void ArcInputMethodManagerService::OnArcInputMethodBoundsChanged(
652 const gfx::Rect& bounds) {
653 if (is_virtual_keyboard_shown_ == !bounds.IsEmpty())
654 return;
655 is_virtual_keyboard_shown_ = !bounds.IsEmpty();
656 NotifyVirtualKeyboardVisibilityChange(is_virtual_keyboard_shown_);
657 }
658
659 InputConnectionImpl*
GetInputConnectionForTesting()660 ArcInputMethodManagerService::GetInputConnectionForTesting() {
661 return active_connection_.get();
662 }
663
EnableIme(const std::string & ime_id,bool enable)664 void ArcInputMethodManagerService::EnableIme(const std::string& ime_id,
665 bool enable) {
666 auto component_id =
667 chromeos::extension_ime_util::GetComponentIDByInputMethodID(ime_id);
668
669 // TODO(yhanada): Disable the IME in Chrome OS side if it fails.
670 imm_bridge_->SendEnableIme(
671 component_id, enable,
672 base::BindOnce(
673 [](const std::string& ime_id, bool enable, bool success) {
674 if (!success) {
675 LOG(ERROR) << (enable ? "Enabling" : "Disabling") << " \""
676 << ime_id << "\" failed";
677 }
678 },
679 ime_id, enable));
680 }
681
SwitchImeTo(const std::string & ime_id)682 void ArcInputMethodManagerService::SwitchImeTo(const std::string& ime_id) {
683 namespace ceiu = chromeos::extension_ime_util;
684
685 std::string component_id = ceiu::GetComponentIDByInputMethodID(ime_id);
686 if (!ceiu::IsArcIME(ime_id))
687 component_id = kChromeOSIMEIdInArcContainer;
688 imm_bridge_->SendSwitchImeTo(
689 component_id, base::BindOnce(&SwitchImeToCallback, ime_id, component_id));
690 }
691
Focus(int context_id)692 void ArcInputMethodManagerService::Focus(int context_id) {
693 if (!is_arc_ime_active_)
694 return;
695
696 DCHECK(!active_connection_);
697 active_connection_ = std::make_unique<InputConnectionImpl>(
698 proxy_ime_engine_.get(), imm_bridge_.get(), context_id);
699 mojo::PendingRemote<mojom::InputConnection> connection_remote;
700 active_connection_->Bind(&connection_remote);
701
702 imm_bridge_->SendFocus(std::move(connection_remote),
703 active_connection_->GetTextInputState(false));
704 }
705
Blur()706 void ArcInputMethodManagerService::Blur() {
707 active_connection_.reset();
708 is_virtual_keyboard_shown_ = false;
709 }
710
UpdateTextInputState()711 void ArcInputMethodManagerService::UpdateTextInputState() {
712 if (!is_arc_ime_active_ || !active_connection_)
713 return;
714 active_connection_->UpdateTextInputState(
715 false /* is_input_state_update_requested */);
716 }
717
RemoveArcIMEFromPrefs()718 void ArcInputMethodManagerService::RemoveArcIMEFromPrefs() {
719 RemoveArcIMEFromPref(prefs::kLanguageEnabledImes);
720 RemoveArcIMEFromPref(prefs::kLanguagePreloadEngines);
721
722 PrefService* prefs = profile_->GetPrefs();
723 if (chromeos::extension_ime_util::IsArcIME(
724 prefs->GetString(prefs::kLanguageCurrentInputMethod))) {
725 prefs->SetString(prefs::kLanguageCurrentInputMethod, std::string());
726 }
727 if (chromeos::extension_ime_util::IsArcIME(
728 prefs->GetString(prefs::kLanguagePreviousInputMethod))) {
729 prefs->SetString(prefs::kLanguagePreviousInputMethod, std::string());
730 }
731 }
732
RemoveArcIMEFromPref(const char * pref_name)733 void ArcInputMethodManagerService::RemoveArcIMEFromPref(const char* pref_name) {
734 const std::string ime_ids = profile_->GetPrefs()->GetString(pref_name);
735 std::vector<base::StringPiece> ime_id_list = base::SplitStringPiece(
736 ime_ids, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
737 base::EraseIf(ime_id_list, [](base::StringPiece id) {
738 return chromeos::extension_ime_util::IsArcIME(id.as_string());
739 });
740 profile_->GetPrefs()->SetString(pref_name,
741 base::JoinString(ime_id_list, ","));
742 }
743
OnTabletModeToggled(bool)744 void ArcInputMethodManagerService::OnTabletModeToggled(bool /* enabled */) {
745 UpdateInputMethodEntryWithImeInfo();
746 }
747
NotifyInputMethodManagerObservers(bool is_tablet_mode)748 void ArcInputMethodManagerService::NotifyInputMethodManagerObservers(
749 bool is_tablet_mode) {
750 // Togging the mode may enable or disable all the ARC IMEs. To dynamically
751 // reflect the potential state changes to chrome://settings, notify the
752 // manager's observers here.
753 // TODO(yusukes): This is a temporary workaround for supporting ARC IMEs
754 // and supports neither Chrome OS extensions nor state changes enforced by
755 // the policy. The better way to do this is to add a dedicated event to
756 // language_settings_private.idl and send the new event to the JS side
757 // instead.
758 auto* manager = chromeos::input_method::InputMethodManager::Get();
759 if (!manager)
760 return;
761 if (is_tablet_mode)
762 manager->NotifyInputMethodExtensionRemoved(proxy_ime_extension_id_);
763 else
764 manager->NotifyInputMethodExtensionAdded(proxy_ime_extension_id_);
765 }
766
IsVirtualKeyboardShown() const767 bool ArcInputMethodManagerService::IsVirtualKeyboardShown() const {
768 return is_virtual_keyboard_shown_;
769 }
770
SendShowVirtualKeyboard()771 void ArcInputMethodManagerService::SendShowVirtualKeyboard() {
772 if (!is_arc_ime_active_)
773 return;
774
775 imm_bridge_->SendShowVirtualKeyboard();
776 }
777
SendHideVirtualKeyboard()778 void ArcInputMethodManagerService::SendHideVirtualKeyboard() {
779 if (!is_arc_ime_active_)
780 return;
781
782 imm_bridge_->SendHideVirtualKeyboard();
783 }
784
NotifyVirtualKeyboardVisibilityChange(bool visible)785 void ArcInputMethodManagerService::NotifyVirtualKeyboardVisibilityChange(
786 bool visible) {
787 if (!is_arc_ime_active_)
788 return;
789 for (auto& observer : observers_)
790 observer.OnAndroidVirtualKeyboardVisibilityChanged(visible);
791 }
792
793 } // namespace arc
794