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