1// Copyright (c) 2011 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/login/base_login_display_host.h"
6
7#include "base/file_util.h"
8#include "base/logging.h"
9#include "base/threading/thread_restrictions.h"
10#include "chrome/browser/browser_process.h"
11#include "chrome/browser/chromeos/cros/cros_library.h"
12#include "chrome/browser/chromeos/cros/input_method_library.h"
13#include "chrome/browser/chromeos/cros/login_library.h"
14#include "chrome/browser/chromeos/customization_document.h"
15#include "chrome/browser/chromeos/input_method/input_method_util.h"
16#include "chrome/browser/chromeos/language_preferences.h"
17#include "chrome/browser/chromeos/login/existing_user_controller.h"
18#include "chrome/browser/chromeos/login/helper.h"
19#include "chrome/browser/chromeos/login/language_switch_menu.h"
20#include "chrome/browser/chromeos/login/login_utils.h"
21#include "chrome/browser/chromeos/login/user_manager.h"
22#include "chrome/browser/chromeos/login/views_login_display_host.h"
23#include "chrome/browser/chromeos/login/wizard_controller.h"
24#include "chrome/browser/chromeos/system_access.h"
25#include "chrome/browser/chromeos/wm_ipc.h"
26#include "chrome/browser/prefs/pref_service.h"
27#include "content/common/notification_service.h"
28#include "content/common/notification_type.h"
29#include "chrome/common/pref_names.h"
30#include "googleurl/src/gurl.h"
31#include "third_party/cros/chromeos_wm_ipc_enums.h"
32#include "ui/base/resource/resource_bundle.h"
33#include "unicode/timezone.h"
34
35#if defined(TOUCH_UI)
36#include "base/command_line.h"
37#include "chrome/browser/chromeos/login/dom_login_display_host.h"
38#endif
39
40namespace {
41
42// Determines the hardware keyboard from the given locale code
43// and the OEM layout information, and saves it to "Locale State".
44// The information will be used in input_method::GetHardwareInputMethodId().
45void DetermineAndSaveHardwareKeyboard(const std::string& locale,
46                                      const std::string& oem_layout) {
47  std::string layout;
48  if (!oem_layout.empty()) {
49    // If the OEM layout information is provided, use it.
50    layout = oem_layout;
51  } else {
52    // Otherwise, determine the hardware keyboard from the locale.
53    std::vector<std::string> input_method_ids;
54    if (chromeos::input_method::GetInputMethodIdsFromLanguageCode(
55            locale,
56            chromeos::input_method::kKeyboardLayoutsOnly,
57            &input_method_ids)) {
58      // The output list |input_method_ids| is sorted by popularity, hence
59      // input_method_ids[0] now contains the most popular keyboard layout
60      // for the given locale.
61      layout = input_method_ids[0];
62    }
63  }
64
65  if (!layout.empty()) {
66    PrefService* prefs = g_browser_process->local_state();
67    prefs->SetString(prefs::kHardwareKeyboardLayout, layout);
68    // This asks the file thread to save the prefs (i.e. doesn't block).
69    // The latest values of Local State reside in memory so we can safely
70    // get the value of kHardwareKeyboardLayout even if the data is not
71    // yet saved to disk.
72    prefs->SavePersistentPrefs();
73  }
74}
75
76}  // namespace
77
78namespace chromeos {
79
80// static
81LoginDisplayHost* BaseLoginDisplayHost::default_host_ = NULL;
82
83// BaseLoginDisplayHost --------------------------------------------------------
84
85BaseLoginDisplayHost::BaseLoginDisplayHost(const gfx::Rect& background_bounds)
86    : background_bounds_(background_bounds) {
87  registrar_.Add(
88      this,
89      NotificationType::APP_TERMINATING,
90      NotificationService::AllSources());
91  DCHECK(default_host_ == NULL);
92  default_host_ = this;
93}
94
95BaseLoginDisplayHost::~BaseLoginDisplayHost() {
96  default_host_ = NULL;
97}
98
99// LoginDisplayHost implementation ---------------------------------------------
100
101void BaseLoginDisplayHost::OnSessionStart() {
102  MessageLoop::current()->DeleteSoon(FROM_HERE, this);
103}
104
105void BaseLoginDisplayHost::StartWizard(
106    const std::string& first_screen_name,
107    const GURL& start_url) {
108  DVLOG(1) << "Starting wizard, first_screen_name: " << first_screen_name;
109  // Create and show the wizard.
110  wizard_controller_.reset();  // Only one controller in a time.
111  wizard_controller_.reset(new WizardController(this, background_bounds_));
112  wizard_controller_->set_start_url(start_url);
113  ShowBackground();
114  if (!WizardController::IsDeviceRegistered())
115    SetOobeProgressBarVisible(true);
116  wizard_controller_->Init(first_screen_name);
117}
118
119void BaseLoginDisplayHost::StartSignInScreen() {
120  DVLOG(1) << "Starting sign in screen";
121  std::vector<chromeos::UserManager::User> users =
122      chromeos::UserManager::Get()->GetUsers();
123
124  // Fix for users who updated device and thus never passed register screen.
125  // If we already have users, we assume that it is not a second part of
126  // OOBE. See http://crosbug.com/6289
127  if (!WizardController::IsDeviceRegistered() && !users.empty()) {
128    VLOG(1) << "Mark device registered because there are remembered users: "
129            << users.size();
130    WizardController::MarkDeviceRegistered();
131  }
132
133  sign_in_controller_.reset();  // Only one controller in a time.
134  sign_in_controller_.reset(new chromeos::ExistingUserController(this));
135  ShowBackground();
136  SetShutdownButtonEnabled(true);
137  sign_in_controller_->Init(users);
138
139  // Initiate services customization manifest fetching.
140  ServicesCustomizationDocument::GetInstance()->StartFetching();
141}
142
143// BaseLoginDisplayHost --------------------------------------------------------
144
145void BaseLoginDisplayHost::Observe(NotificationType type,
146                                   const NotificationSource& source,
147                                   const NotificationDetails& details) {
148  CHECK(type == NotificationType::APP_TERMINATING);
149
150  MessageLoop::current()->DeleteSoon(FROM_HERE, this);
151  MessageLoop::current()->Quit();
152  registrar_.Remove(this,
153                    NotificationType::APP_TERMINATING,
154                    NotificationService::AllSources());
155}
156
157}  // namespace chromeos
158
159// browser::ShowLoginWizard implementation -------------------------------------
160
161namespace browser {
162
163// Declared in browser_dialogs.h so that others don't need to depend on our .h.
164// TODO(nkostylev): Split this into a smaller functions.
165void ShowLoginWizard(const std::string& first_screen_name,
166                     const gfx::Size& size) {
167  VLOG(1) << "Showing login screen: " << first_screen_name;
168
169  // The login screen will enable alternate keyboard layouts, but we don't want
170  // to start the IME process unless one is selected.
171  chromeos::CrosLibrary::Get()->GetInputMethodLibrary()->
172      SetDeferImeStartup(true);
173  // Tell the window manager that the user isn't logged in.
174  chromeos::WmIpc::instance()->SetLoggedInProperty(false);
175
176  // Set up keyboards. For example, when |locale| is "en-US", enable US qwerty
177  // and US dvorak keyboard layouts.
178  if (g_browser_process && g_browser_process->local_state()) {
179    const std::string locale = g_browser_process->GetApplicationLocale();
180    // If the preferred keyboard for the login screen has been saved, use it.
181    std::string initial_input_method_id =
182        g_browser_process->local_state()->GetString(
183            chromeos::language_prefs::kPreferredKeyboardLayout);
184    if (initial_input_method_id.empty()) {
185      // If kPreferredKeyboardLayout is not specified, use the hardware layout.
186      initial_input_method_id =
187          chromeos::input_method::GetHardwareInputMethodId();
188    }
189    chromeos::input_method::EnableInputMethods(
190        locale, chromeos::input_method::kKeyboardLayoutsOnly,
191        initial_input_method_id);
192  }
193
194  gfx::Rect screen_bounds(chromeos::CalculateScreenBounds(size));
195
196  // Check whether we need to execute OOBE process.
197  bool oobe_complete = WizardController::IsOobeCompleted();
198  bool show_login_screen =
199      (first_screen_name.empty() && oobe_complete) ||
200      first_screen_name == WizardController::kLoginScreenName;
201
202  // TODO(nkostylev) Create LoginDisplayHost instance based on flag.
203#if defined(TOUCH_UI)
204  chromeos::LoginDisplayHost* display_host;
205  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kDOMLogin)) {
206    display_host = new chromeos::DOMLoginDisplayHost(screen_bounds);
207  } else {
208    display_host = new chromeos::ViewsLoginDisplayHost(screen_bounds);
209  }
210#else
211  chromeos::LoginDisplayHost* display_host =
212      new chromeos::ViewsLoginDisplayHost(screen_bounds);
213#endif
214  if (show_login_screen && chromeos::CrosLibrary::Get()->EnsureLoaded()) {
215    display_host->StartSignInScreen();
216    return;
217  }
218
219  // Load startup manifest.
220  const chromeos::StartupCustomizationDocument* startup_manifest =
221      chromeos::StartupCustomizationDocument::GetInstance();
222
223  std::string locale;
224  if (startup_manifest->IsReady()) {
225    // Switch to initial locale if specified by customization
226    // and has not been set yet. We cannot call
227    // chromeos::LanguageSwitchMenu::SwitchLanguage here before
228    // EmitLoginPromptReady.
229    PrefService* prefs = g_browser_process->local_state();
230    const std::string current_locale =
231        prefs->GetString(prefs::kApplicationLocale);
232    VLOG(1) << "Current locale: " << current_locale;
233    if (current_locale.empty()) {
234      locale = startup_manifest->initial_locale();
235      std::string layout = startup_manifest->keyboard_layout();
236      VLOG(1) << "Initial locale: " << locale
237              << "keyboard layout " << layout;
238      if (!locale.empty()) {
239        // Save initial locale from VPD/customization manifest as current
240        // Chrome locale. Otherwise it will be lost if Chrome restarts.
241        // Don't need to schedule pref save because setting initial local
242        // will enforce preference saving.
243        prefs->SetString(prefs::kApplicationLocale, locale);
244        WizardController::SetInitialLocale(locale);
245        // Determine keyboard layout from OEM customization (if provided) or
246        // initial locale and save it in preferences.
247        DetermineAndSaveHardwareKeyboard(locale, layout);
248        // Then, enable the hardware keyboard.
249        chromeos::input_method::EnableInputMethods(
250            locale,
251            chromeos::input_method::kKeyboardLayoutsOnly,
252            chromeos::input_method::GetHardwareInputMethodId());
253        // Reloading resource bundle causes us to do blocking IO on UI thread.
254        // Temporarily allow it until we fix http://crosbug.com/11102
255        base::ThreadRestrictions::ScopedAllowIO allow_io;
256        const std::string loaded_locale =
257            ResourceBundle::ReloadSharedInstance(locale);
258        CHECK(!loaded_locale.empty()) << "Locale could not be found for "
259                                      << locale;
260        // Set the application locale here so that the language switch
261        // menu works properly with the newly loaded locale.
262        g_browser_process->SetApplicationLocale(loaded_locale);
263      }
264    }
265  }
266
267  display_host->StartWizard(first_screen_name, GURL());
268
269  chromeos::LoginUtils::Get()->PrewarmAuthentication();
270  if (chromeos::CrosLibrary::Get()->EnsureLoaded())
271    chromeos::CrosLibrary::Get()->GetLoginLibrary()->EmitLoginPromptReady();
272
273  if (startup_manifest->IsReady()) {
274    // Set initial timezone if specified by customization.
275    const std::string timezone_name = startup_manifest->initial_timezone();
276    VLOG(1) << "Initial time zone: " << timezone_name;
277    // Apply locale customizations only once so preserve whatever locale
278    // user has changed to during OOBE.
279    if (!timezone_name.empty()) {
280      icu::TimeZone* timezone = icu::TimeZone::createTimeZone(
281          icu::UnicodeString::fromUTF8(timezone_name));
282      CHECK(timezone) << "Timezone could not be set for " << timezone_name;
283      chromeos::SystemAccess::GetInstance()->SetTimezone(*timezone);
284    }
285  }
286}
287
288}  // namespace browser
289