browser_state_monitor.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
1// Copyright (c) 2012 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/input_method/browser_state_monitor.h"
6
7#include "chrome/browser/browser_process.h"
8#include "chrome/browser/chromeos/input_method/input_method_util.h"
9#include "chrome/browser/chromeos/language_preferences.h"
10#include "chrome/browser/prefs/pref_service.h"
11#include "chrome/browser/profiles/profile_manager.h"
12#include "chrome/common/chrome_notification_types.h"
13#include "chrome/common/pref_names.h"
14#include "content/public/browser/notification_service.h"
15
16namespace chromeos {
17namespace input_method {
18namespace {
19
20PrefService* GetPrefService() {
21  Profile* profile = ProfileManager::GetDefaultProfile();
22  if (profile)
23    return profile->GetPrefs();
24  return NULL;
25}
26
27}  // namespace
28
29BrowserStateMonitor::BrowserStateMonitor(InputMethodManager* manager)
30    : manager_(manager),
31      state_(InputMethodManager::STATE_LOGIN_SCREEN),
32      pref_service_(NULL) {
33  notification_registrar_.Add(this,
34                              chrome::NOTIFICATION_LOGIN_USER_CHANGED,
35                              content::NotificationService::AllSources());
36  notification_registrar_.Add(this,
37                              chrome::NOTIFICATION_SESSION_STARTED,
38                              content::NotificationService::AllSources());
39  notification_registrar_.Add(this,
40                              chrome::NOTIFICATION_SCREEN_LOCK_STATE_CHANGED,
41                              content::NotificationService::AllSources());
42  // We should not use ALL_BROWSERS_CLOSING here since logout might be cancelled
43  // by JavaScript after ALL_BROWSERS_CLOSING is sent (crosbug.com/11055).
44  notification_registrar_.Add(this,
45                              chrome::NOTIFICATION_APP_TERMINATING,
46                              content::NotificationService::AllSources());
47
48  manager_->SetState(state_);
49  manager_->AddObserver(this);
50}
51
52BrowserStateMonitor::~BrowserStateMonitor() {
53  manager_->RemoveObserver(this);
54}
55
56void BrowserStateMonitor::SetPrefServiceForTesting(PrefService* pref_service) {
57  pref_service_ = pref_service;
58}
59
60void BrowserStateMonitor::UpdateLocalState(
61    const std::string& current_input_method) {
62  if (!g_browser_process || !g_browser_process->local_state())
63    return;
64  g_browser_process->local_state()->SetString(
65      language_prefs::kPreferredKeyboardLayout,
66      current_input_method);
67}
68
69void BrowserStateMonitor::UpdateUserPreferences(
70    const std::string& current_input_method) {
71  PrefService* pref_service = pref_service_ ? pref_service_ : GetPrefService();
72  DCHECK(pref_service);
73
74  // Even though we're DCHECK'ing to catch this on debug builds, we don't
75  // want to crash a release build in case the pref service is no longer
76  // available.
77  if (!pref_service)
78    return;
79
80  const std::string current_input_method_on_pref =
81      pref_service->GetString(prefs::kLanguageCurrentInputMethod);
82  if (current_input_method_on_pref == current_input_method)
83    return;
84
85  pref_service->SetString(prefs::kLanguagePreviousInputMethod,
86                          current_input_method_on_pref);
87  pref_service->SetString(prefs::kLanguageCurrentInputMethod,
88                          current_input_method);
89}
90
91void BrowserStateMonitor::InputMethodChanged(InputMethodManager* manager,
92                                             bool show_message) {
93  DCHECK_EQ(manager_, manager);
94  const std::string current_input_method =
95      manager->GetCurrentInputMethod().id();
96  // Save the new input method id depending on the current browser state.
97  switch (state_) {
98    case InputMethodManager::STATE_LOGIN_SCREEN:
99      if (!InputMethodUtil::IsKeyboardLayout(current_input_method)) {
100        DVLOG(1) << "Only keyboard layouts are supported: "
101                 << current_input_method;
102        return;
103      }
104      UpdateLocalState(current_input_method);
105      return;
106    case InputMethodManager::STATE_BROWSER_SCREEN:
107      UpdateUserPreferences(current_input_method);
108      return;
109    case InputMethodManager::STATE_LOCK_SCREEN:
110      // We use a special set of input methods on the screen. Do not update.
111      return;
112    case InputMethodManager::STATE_TERMINATING:
113      return;
114  }
115  NOTREACHED();
116}
117
118void BrowserStateMonitor::InputMethodPropertyChanged(
119    InputMethodManager* manager) {}
120
121void BrowserStateMonitor::Observe(
122    int type,
123    const content::NotificationSource& source,
124    const content::NotificationDetails& details) {
125  switch (type) {
126    case chrome::NOTIFICATION_APP_TERMINATING: {
127      SetState(InputMethodManager::STATE_TERMINATING);
128      break;
129    }
130    case chrome::NOTIFICATION_LOGIN_USER_CHANGED: {
131      // The user logged in, but the browser window for user session is not yet
132      // ready. An initial input method hasn't been set to the manager.
133      // Note that the notification is also sent when Chrome crashes/restarts
134      // as of writing, but it might be changed in the future (therefore we need
135      // to listen to NOTIFICATION_SESSION_STARTED as well.)
136      DVLOG(1) << "Received chrome::NOTIFICATION_LOGIN_USER_CHANGED";
137      SetState(InputMethodManager::STATE_BROWSER_SCREEN);
138      break;
139    }
140    case chrome::NOTIFICATION_SESSION_STARTED: {
141      // The user logged in, and the browser window for user session is ready.
142      // An initial input method has already been set.
143      // We should NOT call InitializePrefMembers() here since the notification
144      // is sent in the PreProfileInit phase in case when Chrome crashes and
145      // restarts.
146      DVLOG(1) << "Received chrome::NOTIFICATION_SESSION_STARTED";
147      SetState(InputMethodManager::STATE_BROWSER_SCREEN);
148      break;
149    }
150    case chrome::NOTIFICATION_SCREEN_LOCK_STATE_CHANGED: {
151      const bool is_screen_locked = *content::Details<bool>(details).ptr();
152      SetState(is_screen_locked ? InputMethodManager::STATE_LOCK_SCREEN :
153               InputMethodManager::STATE_BROWSER_SCREEN);
154      break;
155    }
156    default: {
157      NOTREACHED();
158      break;
159    }
160  }
161  // Note: browser notifications are sent in the following order.
162  //
163  // Normal login:
164  // 1. chrome::NOTIFICATION_LOGIN_USER_CHANGED is sent.
165  // 2. Preferences::NotifyPrefChanged() is called. preload_engines (which
166  //    might change the current input method) and current/previous input method
167  //    are sent to the manager.
168  // 3. chrome::NOTIFICATION_SESSION_STARTED is sent.
169  //
170  // Chrome crash/restart (after logging in):
171  // 1. chrome::NOTIFICATION_LOGIN_USER_CHANGED might be sent.
172  // 2. chrome::NOTIFICATION_SESSION_STARTED is sent.
173  // 3. Preferences::NotifyPrefChanged() is called. The same things as above
174  //    happen.
175  //
176  // We have to be careful not to overwrite both local and user prefs when
177  // preloaded engine is set. Note that it does not work to do nothing in
178  // InputMethodChanged() between chrome::NOTIFICATION_LOGIN_USER_CHANGED and
179  // chrome::NOTIFICATION_SESSION_STARTED because SESSION_STARTED is sent very
180  // early on Chrome crash/restart.
181}
182
183void BrowserStateMonitor::SetState(InputMethodManager::State new_state) {
184  const InputMethodManager::State old_state = state_;
185  state_ = new_state;
186  if (old_state != state_)
187    manager_->SetState(state_);
188}
189
190}  // namespace input_method
191}  // namespace chromeos
192