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/locale_change_guard.h"
6
7#include <algorithm>
8
9#include "ash/shell.h"
10#include "ash/system/tray/system_tray.h"
11#include "ash/system/tray/system_tray_notifier.h"
12#include "base/bind.h"
13#include "base/prefs/pref_service.h"
14#include "base/strings/utf_string_conversions.h"
15#include "chrome/app/chrome_command_ids.h"
16#include "chrome/browser/browser_process.h"
17#include "chrome/browser/chrome_notification_types.h"
18#include "chrome/browser/chromeos/settings/device_settings_service.h"
19#include "chrome/browser/lifetime/application_lifetime.h"
20#include "chrome/browser/profiles/profile.h"
21#include "chrome/browser/ui/browser.h"
22#include "chrome/browser/ui/browser_commands.h"
23#include "chrome/browser/ui/host_desktop.h"
24#include "chrome/common/pref_names.h"
25#include "chrome/grit/generated_resources.h"
26#include "content/public/browser/notification_service.h"
27#include "content/public/browser/notification_source.h"
28#include "content/public/browser/user_metrics.h"
29#include "content/public/browser/web_contents.h"
30#include "ui/base/l10n/l10n_util.h"
31
32using base::UserMetricsAction;
33using content::WebContents;
34
35namespace chromeos {
36
37namespace {
38
39// This is the list of languages that do not require user notification when
40// locale is switched automatically between regions within the same language.
41//
42// New language in kAcceptLanguageList should be added either here or to
43// to the exception list in unit test.
44const char* const kSkipShowNotificationLanguages[4] = {"en", "de", "fr", "it"};
45
46}  // anonymous namespace
47
48LocaleChangeGuard::LocaleChangeGuard(Profile* profile)
49    : profile_(profile),
50      reverted_(false),
51      session_started_(false),
52      main_frame_loaded_(false) {
53  DCHECK(profile_);
54  registrar_.Add(this, chrome::NOTIFICATION_OWNERSHIP_STATUS_CHANGED,
55                 content::NotificationService::AllSources());
56}
57
58LocaleChangeGuard::~LocaleChangeGuard() {}
59
60void LocaleChangeGuard::OnLogin() {
61  registrar_.Add(this, chrome::NOTIFICATION_SESSION_STARTED,
62                 content::NotificationService::AllSources());
63  registrar_.Add(this, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
64                 content::NotificationService::AllBrowserContextsAndSources());
65}
66
67void LocaleChangeGuard::RevertLocaleChange() {
68  if (profile_ == NULL ||
69      from_locale_.empty() ||
70      to_locale_.empty()) {
71    NOTREACHED();
72    return;
73  }
74  if (reverted_)
75    return;
76  reverted_ = true;
77  content::RecordAction(UserMetricsAction("LanguageChange_Revert"));
78  profile_->ChangeAppLocale(
79      from_locale_, Profile::APP_LOCALE_CHANGED_VIA_REVERT);
80  chrome::AttemptUserExit();
81}
82
83void LocaleChangeGuard::RevertLocaleChangeCallback(
84    const base::ListValue* list) {
85  RevertLocaleChange();
86}
87
88void LocaleChangeGuard::Observe(int type,
89                                const content::NotificationSource& source,
90                                const content::NotificationDetails& details) {
91  if (profile_ == NULL) {
92    NOTREACHED();
93    return;
94  }
95  switch (type) {
96    case chrome::NOTIFICATION_SESSION_STARTED: {
97      session_started_ = true;
98      registrar_.Remove(this, chrome::NOTIFICATION_SESSION_STARTED,
99                        content::NotificationService::AllSources());
100      if (main_frame_loaded_)
101        Check();
102      break;
103    }
104    case content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME: {
105      if (profile_ ==
106          content::Source<WebContents>(source)->GetBrowserContext()) {
107        main_frame_loaded_ = true;
108        // We need to perform locale change check only once, so unsubscribe.
109        registrar_.Remove(this, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
110                          content::NotificationService::AllSources());
111        if (session_started_)
112          Check();
113      }
114      break;
115    }
116    case chrome::NOTIFICATION_OWNERSHIP_STATUS_CHANGED: {
117      if (DeviceSettingsService::Get()->HasPrivateOwnerKey()) {
118        PrefService* local_state = g_browser_process->local_state();
119        if (local_state) {
120          PrefService* prefs = profile_->GetPrefs();
121          if (prefs == NULL) {
122            NOTREACHED();
123            return;
124          }
125          std::string owner_locale =
126              prefs->GetString(prefs::kApplicationLocale);
127          if (!owner_locale.empty())
128            local_state->SetString(prefs::kOwnerLocale, owner_locale);
129        }
130      }
131      break;
132    }
133    default: {
134      NOTREACHED();
135      break;
136    }
137  }
138}
139
140void LocaleChangeGuard::Check() {
141  std::string cur_locale = g_browser_process->GetApplicationLocale();
142  if (cur_locale.empty()) {
143    NOTREACHED();
144    return;
145  }
146
147  PrefService* prefs = profile_->GetPrefs();
148  if (prefs == NULL) {
149    NOTREACHED();
150    return;
151  }
152
153  std::string to_locale = prefs->GetString(prefs::kApplicationLocale);
154  if (to_locale != cur_locale) {
155    // This conditional branch can occur in cases like:
156    // (1) kApplicationLocale preference was modified by synchronization;
157    // (2) kApplicationLocale is managed by policy.
158    return;
159  }
160
161  std::string from_locale = prefs->GetString(prefs::kApplicationLocaleBackup);
162  if (from_locale.empty() || from_locale == to_locale)
163    return;  // No locale change was detected, just exit.
164
165  if (prefs->GetString(prefs::kApplicationLocaleAccepted) == to_locale)
166    return;  // Already accepted.
167
168  // Locale change detected.
169  if (!ShouldShowLocaleChangeNotification(from_locale, to_locale))
170    return;
171
172  // Showing notification.
173  if (from_locale_ != from_locale || to_locale_ != to_locale) {
174    // Falling back to showing message in current locale.
175    LOG(ERROR) <<
176        "Showing locale change notification in current (not previous) language";
177    PrepareChangingLocale(from_locale, to_locale);
178  }
179
180#if !defined(USE_ATHENA)
181  // TODO(dpolukhin): Support locale change, crbug.com/411884.
182  ash::Shell::GetInstance()->system_tray_notifier()->NotifyLocaleChanged(
183      this, cur_locale, from_locale_, to_locale_);
184#endif
185}
186
187void LocaleChangeGuard::AcceptLocaleChange() {
188  if (profile_ == NULL ||
189      from_locale_.empty() ||
190      to_locale_.empty()) {
191    NOTREACHED();
192    return;
193  }
194
195  // Check whether locale has been reverted or changed.
196  // If not: mark current locale as accepted.
197  if (reverted_)
198    return;
199  PrefService* prefs = profile_->GetPrefs();
200  if (prefs == NULL) {
201    NOTREACHED();
202    return;
203  }
204  if (prefs->GetString(prefs::kApplicationLocale) != to_locale_)
205    return;
206  content::RecordAction(UserMetricsAction("LanguageChange_Accept"));
207  prefs->SetString(prefs::kApplicationLocaleBackup, to_locale_);
208  prefs->SetString(prefs::kApplicationLocaleAccepted, to_locale_);
209}
210
211void LocaleChangeGuard::PrepareChangingLocale(
212    const std::string& from_locale, const std::string& to_locale) {
213  std::string cur_locale = g_browser_process->GetApplicationLocale();
214  if (!from_locale.empty())
215    from_locale_ = from_locale;
216  if (!to_locale.empty())
217    to_locale_ = to_locale;
218
219  if (!from_locale_.empty() && !to_locale_.empty()) {
220    base::string16 from = l10n_util::GetDisplayNameForLocale(
221        from_locale_, cur_locale, true);
222    base::string16 to = l10n_util::GetDisplayNameForLocale(
223        to_locale_, cur_locale, true);
224
225    title_text_ = l10n_util::GetStringUTF16(
226        IDS_OPTIONS_SETTINGS_SECTION_TITLE_LANGUAGE);
227    message_text_ = l10n_util::GetStringFUTF16(
228        IDS_LOCALE_CHANGE_MESSAGE, from, to);
229    revert_link_text_ = l10n_util::GetStringFUTF16(
230        IDS_LOCALE_CHANGE_REVERT_MESSAGE, from);
231  }
232}
233
234// static
235bool LocaleChangeGuard::ShouldShowLocaleChangeNotification(
236    const std::string& from_locale,
237    const std::string& to_locale) {
238  const std::string from_lang = l10n_util::GetLanguage(from_locale);
239  const std::string to_lang = l10n_util::GetLanguage(to_locale);
240
241  if (from_locale == to_locale)
242    return false;
243
244  if (from_lang != to_lang)
245    return true;
246
247  const char* const* begin = kSkipShowNotificationLanguages;
248  const char* const* end = kSkipShowNotificationLanguages +
249                           arraysize(kSkipShowNotificationLanguages);
250
251  return std::find(begin, end, from_lang) == end;
252}
253
254// static
255const char* const*
256LocaleChangeGuard::GetSkipShowNotificationLanguagesForTesting() {
257  return kSkipShowNotificationLanguages;
258}
259
260// static
261size_t LocaleChangeGuard::GetSkipShowNotificationLanguagesSizeForTesting() {
262  return arraysize(kSkipShowNotificationLanguages);
263}
264
265}  // namespace chromeos
266