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 32 using base::UserMetricsAction; 33 using content::WebContents; 34 35 namespace chromeos { 36 37 namespace { 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. 44 const char* const kSkipShowNotificationLanguages[4] = {"en", "de", "fr", "it"}; 45 46 } // anonymous namespace 47 48 LocaleChangeGuard::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 58 LocaleChangeGuard::~LocaleChangeGuard() {} 59 60 void 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 67 void 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 83 void LocaleChangeGuard::RevertLocaleChangeCallback( 84 const base::ListValue* list) { 85 RevertLocaleChange(); 86 } 87 88 void 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 140 void 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 187 void 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 211 void 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 235 bool 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 255 const char* const* 256 LocaleChangeGuard::GetSkipShowNotificationLanguagesForTesting() { 257 return kSkipShowNotificationLanguages; 258 } 259 260 // static 261 size_t LocaleChangeGuard::GetSkipShowNotificationLanguagesSizeForTesting() { 262 return arraysize(kSkipShowNotificationLanguages); 263 } 264 265 } // namespace chromeos 266