ProjectingObserverChromeos: Drop DBusThreadManager dependency for better testing.
[chromium-blink-merge.git] / components / translate / core / browser / translate_prefs.cc
blob4f9c2a73948a2ee4b303a40d83ef346b47f21d6a
1 // Copyright 2014 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.
5 #include "components/translate/core/browser/translate_prefs.h"
7 #include <set>
9 #include "base/prefs/pref_service.h"
10 #include "base/prefs/scoped_user_pref_update.h"
11 #include "base/strings/string_split.h"
12 #include "base/strings/string_util.h"
13 #include "components/pref_registry/pref_registry_syncable.h"
14 #include "components/translate/core/browser/translate_accept_languages.h"
15 #include "components/translate/core/browser/translate_download_manager.h"
16 #include "components/translate/core/common/translate_util.h"
18 namespace translate {
20 const char TranslatePrefs::kPrefTranslateLanguageBlacklist[] =
21 "translate_language_blacklist";
22 const char TranslatePrefs::kPrefTranslateSiteBlacklist[] =
23 "translate_site_blacklist";
24 const char TranslatePrefs::kPrefTranslateWhitelists[] =
25 "translate_whitelists";
26 const char TranslatePrefs::kPrefTranslateDeniedCount[] =
27 "translate_denied_count";
28 const char TranslatePrefs::kPrefTranslateAcceptedCount[] =
29 "translate_accepted_count";
30 const char TranslatePrefs::kPrefTranslateBlockedLanguages[] =
31 "translate_blocked_languages";
32 const char TranslatePrefs::kPrefTranslateLastDeniedTime[] =
33 "translate_last_denied_time";
34 const char TranslatePrefs::kPrefTranslateTooOftenDenied[] =
35 "translate_too_often_denied";
37 namespace {
39 void GetBlacklistedLanguages(const PrefService* prefs,
40 std::vector<std::string>* languages) {
41 DCHECK(languages);
42 DCHECK(languages->empty());
44 const char* key = TranslatePrefs::kPrefTranslateLanguageBlacklist;
45 const base::ListValue* list = prefs->GetList(key);
46 for (base::ListValue::const_iterator it = list->begin();
47 it != list->end(); ++it) {
48 std::string lang;
49 (*it)->GetAsString(&lang);
50 languages->push_back(lang);
54 // Expands language codes to make these more suitable for Accept-Language.
55 // Example: ['en-US', 'ja', 'en-CA'] => ['en-US', 'en', 'ja', 'en-CA'].
56 // 'en' won't appear twice as this function eliminates duplicates.
57 void ExpandLanguageCodes(const std::vector<std::string>& languages,
58 std::vector<std::string>* expanded_languages) {
59 DCHECK(expanded_languages);
60 DCHECK(expanded_languages->empty());
62 // used to eliminate duplicates.
63 std::set<std::string> seen;
65 for (std::vector<std::string>::const_iterator it = languages.begin();
66 it != languages.end(); ++it) {
67 const std::string& language = *it;
68 if (seen.find(language) == seen.end()) {
69 expanded_languages->push_back(language);
70 seen.insert(language);
73 std::vector<std::string> tokens;
74 base::SplitString(language, '-', &tokens);
75 if (tokens.size() == 0)
76 continue;
77 const std::string& main_part = tokens[0];
78 if (seen.find(main_part) == seen.end()) {
79 expanded_languages->push_back(main_part);
80 seen.insert(main_part);
85 } // namespace
87 TranslatePrefs::TranslatePrefs(PrefService* user_prefs,
88 const char* accept_languages_pref,
89 const char* preferred_languages_pref)
90 : accept_languages_pref_(accept_languages_pref),
91 prefs_(user_prefs) {
92 #if defined(OS_CHROMEOS)
93 preferred_languages_pref_ = preferred_languages_pref;
94 #else
95 DCHECK(!preferred_languages_pref);
96 #endif
99 void TranslatePrefs::ResetToDefaults() {
100 ClearBlockedLanguages();
101 ClearBlacklistedSites();
102 ClearWhitelistedLanguagePairs();
104 std::vector<std::string> languages;
105 GetLanguageList(&languages);
106 for (std::vector<std::string>::const_iterator it = languages.begin();
107 it != languages.end(); ++it) {
108 const std::string& language = *it;
109 ResetTranslationAcceptedCount(language);
110 ResetTranslationDeniedCount(language);
113 prefs_->ClearPref(kPrefTranslateLastDeniedTime);
114 prefs_->ClearPref(kPrefTranslateTooOftenDenied);
117 bool TranslatePrefs::IsBlockedLanguage(
118 const std::string& original_language) const {
119 return IsValueBlacklisted(kPrefTranslateBlockedLanguages,
120 original_language);
123 void TranslatePrefs::BlockLanguage(const std::string& original_language) {
124 BlacklistValue(kPrefTranslateBlockedLanguages, original_language);
126 // Add the language to the language list at chrome://settings/languages.
127 std::string language = original_language;
128 translate::ToChromeLanguageSynonym(&language);
130 std::vector<std::string> languages;
131 GetLanguageList(&languages);
133 if (std::find(languages.begin(), languages.end(), language) ==
134 languages.end()) {
135 languages.push_back(language);
136 UpdateLanguageList(languages);
140 void TranslatePrefs::UnblockLanguage(const std::string& original_language) {
141 RemoveValueFromBlacklist(kPrefTranslateBlockedLanguages, original_language);
144 void TranslatePrefs::RemoveLanguageFromLegacyBlacklist(
145 const std::string& original_language) {
146 RemoveValueFromBlacklist(kPrefTranslateLanguageBlacklist, original_language);
149 bool TranslatePrefs::IsSiteBlacklisted(const std::string& site) const {
150 return IsValueBlacklisted(kPrefTranslateSiteBlacklist, site);
153 void TranslatePrefs::BlacklistSite(const std::string& site) {
154 BlacklistValue(kPrefTranslateSiteBlacklist, site);
157 void TranslatePrefs::RemoveSiteFromBlacklist(const std::string& site) {
158 RemoveValueFromBlacklist(kPrefTranslateSiteBlacklist, site);
161 bool TranslatePrefs::IsLanguagePairWhitelisted(
162 const std::string& original_language,
163 const std::string& target_language) {
164 const base::DictionaryValue* dict =
165 prefs_->GetDictionary(kPrefTranslateWhitelists);
166 if (dict && !dict->empty()) {
167 std::string auto_target_lang;
168 if (dict->GetString(original_language, &auto_target_lang) &&
169 auto_target_lang == target_language)
170 return true;
172 return false;
175 void TranslatePrefs::WhitelistLanguagePair(const std::string& original_language,
176 const std::string& target_language) {
177 DictionaryPrefUpdate update(prefs_, kPrefTranslateWhitelists);
178 base::DictionaryValue* dict = update.Get();
179 if (!dict) {
180 NOTREACHED() << "Unregistered translate whitelist pref";
181 return;
183 dict->SetString(original_language, target_language);
186 void TranslatePrefs::RemoveLanguagePairFromWhitelist(
187 const std::string& original_language,
188 const std::string& target_language) {
189 DictionaryPrefUpdate update(prefs_, kPrefTranslateWhitelists);
190 base::DictionaryValue* dict = update.Get();
191 if (!dict) {
192 NOTREACHED() << "Unregistered translate whitelist pref";
193 return;
195 dict->Remove(original_language, NULL);
198 bool TranslatePrefs::HasBlockedLanguages() const {
199 return !IsListEmpty(kPrefTranslateBlockedLanguages);
202 void TranslatePrefs::ClearBlockedLanguages() {
203 prefs_->ClearPref(kPrefTranslateBlockedLanguages);
206 bool TranslatePrefs::HasBlacklistedSites() const {
207 return !IsListEmpty(kPrefTranslateSiteBlacklist);
210 void TranslatePrefs::ClearBlacklistedSites() {
211 prefs_->ClearPref(kPrefTranslateSiteBlacklist);
214 bool TranslatePrefs::HasWhitelistedLanguagePairs() const {
215 return !IsDictionaryEmpty(kPrefTranslateWhitelists);
218 void TranslatePrefs::ClearWhitelistedLanguagePairs() {
219 prefs_->ClearPref(kPrefTranslateWhitelists);
222 int TranslatePrefs::GetTranslationDeniedCount(
223 const std::string& language) const {
224 const base::DictionaryValue* dict =
225 prefs_->GetDictionary(kPrefTranslateDeniedCount);
226 int count = 0;
227 return dict->GetInteger(language, &count) ? count : 0;
230 void TranslatePrefs::IncrementTranslationDeniedCount(
231 const std::string& language) {
232 DictionaryPrefUpdate update(prefs_, kPrefTranslateDeniedCount);
233 base::DictionaryValue* dict = update.Get();
235 int count = 0;
236 dict->GetInteger(language, &count);
237 dict->SetInteger(language, count + 1);
240 void TranslatePrefs::ResetTranslationDeniedCount(const std::string& language) {
241 DictionaryPrefUpdate update(prefs_, kPrefTranslateDeniedCount);
242 update.Get()->SetInteger(language, 0);
245 int TranslatePrefs::GetTranslationAcceptedCount(const std::string& language) {
246 const base::DictionaryValue* dict =
247 prefs_->GetDictionary(kPrefTranslateAcceptedCount);
248 int count = 0;
249 return dict->GetInteger(language, &count) ? count : 0;
252 void TranslatePrefs::IncrementTranslationAcceptedCount(
253 const std::string& language) {
254 DictionaryPrefUpdate update(prefs_, kPrefTranslateAcceptedCount);
255 base::DictionaryValue* dict = update.Get();
256 int count = 0;
257 dict->GetInteger(language, &count);
258 dict->SetInteger(language, count + 1);
261 void TranslatePrefs::ResetTranslationAcceptedCount(
262 const std::string& language) {
263 DictionaryPrefUpdate update(prefs_, kPrefTranslateAcceptedCount);
264 update.Get()->SetInteger(language, 0);
267 void TranslatePrefs::UpdateLastDeniedTime() {
268 if (IsTooOftenDenied())
269 return;
271 double time = prefs_->GetDouble(kPrefTranslateLastDeniedTime);
272 base::Time last_closed_time = base::Time::FromJsTime(time);
273 base::Time now = base::Time::Now();
274 prefs_->SetDouble(kPrefTranslateLastDeniedTime, now.ToJsTime());
275 if (now - last_closed_time <= base::TimeDelta::FromDays(1))
276 prefs_->SetBoolean(kPrefTranslateTooOftenDenied, true);
279 bool TranslatePrefs::IsTooOftenDenied() const {
280 return prefs_->GetBoolean(kPrefTranslateTooOftenDenied);
283 void TranslatePrefs::ResetDenialState() {
284 prefs_->SetDouble(kPrefTranslateLastDeniedTime, 0);
285 prefs_->SetBoolean(kPrefTranslateTooOftenDenied, false);
288 void TranslatePrefs::GetLanguageList(std::vector<std::string>* languages) {
289 DCHECK(languages);
290 DCHECK(languages->empty());
292 #if defined(OS_CHROMEOS)
293 const char* key = preferred_languages_pref_.c_str();
294 #else
295 const char* key = accept_languages_pref_.c_str();
296 #endif
298 std::string languages_str = prefs_->GetString(key);
299 base::SplitString(languages_str, ',', languages);
302 void TranslatePrefs::UpdateLanguageList(
303 const std::vector<std::string>& languages) {
304 #if defined(OS_CHROMEOS)
305 std::string languages_str = JoinString(languages, ',');
306 prefs_->SetString(preferred_languages_pref_.c_str(), languages_str);
307 #endif
309 // Save the same language list as accept languages preference as well, but we
310 // need to expand the language list, to make it more acceptable. For instance,
311 // some web sites don't understand 'en-US' but 'en'. See crosbug.com/9884.
312 std::vector<std::string> accept_languages;
313 ExpandLanguageCodes(languages, &accept_languages);
314 std::string accept_languages_str = JoinString(accept_languages, ',');
315 prefs_->SetString(accept_languages_pref_.c_str(), accept_languages_str);
318 bool TranslatePrefs::CanTranslateLanguage(
319 TranslateAcceptLanguages* accept_languages,
320 const std::string& language) {
321 bool can_be_accept_language =
322 TranslateAcceptLanguages::CanBeAcceptLanguage(language);
323 bool is_accept_language = accept_languages->IsAcceptLanguage(language);
325 // Don't translate any user black-listed languages. Checking
326 // |is_accept_language| is necessary because if the user eliminates the
327 // language from the preference, it is natural to forget whether or not
328 // the language should be translated. Checking |cannot_be_accept_language|
329 // is also necessary because some minor languages can't be selected in the
330 // language preference even though the language is available in Translate
331 // server.
332 if (IsBlockedLanguage(language) &&
333 (is_accept_language || !can_be_accept_language))
334 return false;
336 return true;
339 bool TranslatePrefs::ShouldAutoTranslate(const std::string& original_language,
340 std::string* target_language) {
341 const base::DictionaryValue* dict =
342 prefs_->GetDictionary(kPrefTranslateWhitelists);
343 if (dict && dict->GetString(original_language, target_language)) {
344 DCHECK(!target_language->empty());
345 return !target_language->empty();
347 return false;
350 // static
351 void TranslatePrefs::RegisterProfilePrefs(
352 user_prefs::PrefRegistrySyncable* registry) {
353 registry->RegisterListPref(kPrefTranslateLanguageBlacklist,
354 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
355 registry->RegisterListPref(kPrefTranslateSiteBlacklist,
356 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
357 registry->RegisterDictionaryPref(
358 kPrefTranslateWhitelists,
359 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
360 registry->RegisterDictionaryPref(
361 kPrefTranslateDeniedCount,
362 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
363 registry->RegisterDictionaryPref(
364 kPrefTranslateAcceptedCount,
365 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
366 registry->RegisterListPref(kPrefTranslateBlockedLanguages,
367 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
368 registry->RegisterDoublePref(
369 kPrefTranslateLastDeniedTime, 0,
370 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
371 registry->RegisterBooleanPref(
372 kPrefTranslateTooOftenDenied, false,
373 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
376 // static
377 void TranslatePrefs::MigrateUserPrefs(PrefService* user_prefs,
378 const char* accept_languages_pref) {
379 // Old format of kPrefTranslateWhitelists
380 // - original language -> list of target langs to auto-translate
381 // - list of langs is in order of being enabled i.e. last in list is the
382 // most recent language that user enabled via
383 // Always translate |source_lang| to |target_lang|"
384 // - this results in a one-to-n relationship between source lang and target
385 // langs.
386 // New format:
387 // - original language -> one target language to auto-translate
388 // - each time that the user enables the "Always translate..." option, that
389 // target lang overwrites the previous one.
390 // - this results in a one-to-one relationship between source lang and target
391 // lang
392 // - we replace old list of target langs with the last target lang in list,
393 // assuming the last (i.e. most recent) target lang is what user wants to
394 // keep auto-translated.
395 DictionaryPrefUpdate update(user_prefs, kPrefTranslateWhitelists);
396 base::DictionaryValue* dict = update.Get();
397 if (dict && !dict->empty()) {
398 base::DictionaryValue::Iterator iter(*dict);
399 while (!iter.IsAtEnd()) {
400 const base::ListValue* list = NULL;
401 if (!iter.value().GetAsList(&list) || !list)
402 break; // Dictionary has either been migrated or new format.
403 std::string key = iter.key();
404 // Advance the iterator before removing the current element.
405 iter.Advance();
406 std::string target_lang;
407 if (list->empty() ||
408 !list->GetString(list->GetSize() - 1, &target_lang) ||
409 target_lang.empty()) {
410 dict->Remove(key, NULL);
411 } else {
412 dict->SetString(key, target_lang);
417 // Get the union of the blacklist and the Accept languages, and set this to
418 // the new language set 'translate_blocked_languages'. This is used for the
419 // settings UI for Translate and configration to determine which langauage
420 // should be translated instead of the blacklist. The blacklist is no longer
421 // used after launching the settings UI.
422 // After that, Set 'translate_languages_not_translate' to Accept languages to
423 // enable settings for users.
424 bool merged = user_prefs->HasPrefPath(kPrefTranslateBlockedLanguages);
426 if (!merged) {
427 std::vector<std::string> blacklisted_languages;
428 GetBlacklistedLanguages(user_prefs, &blacklisted_languages);
430 std::string accept_languages_str =
431 user_prefs->GetString(accept_languages_pref);
432 std::vector<std::string> accept_languages;
433 base::SplitString(accept_languages_str, ',', &accept_languages);
435 std::vector<std::string> blocked_languages;
436 CreateBlockedLanguages(
437 &blocked_languages, blacklisted_languages, accept_languages);
439 // Create the new preference kPrefTranslateBlockedLanguages.
441 base::ListValue blocked_languages_list;
442 for (std::vector<std::string>::const_iterator it =
443 blocked_languages.begin();
444 it != blocked_languages.end(); ++it) {
445 blocked_languages_list.Append(new base::StringValue(*it));
447 ListPrefUpdate update(user_prefs, kPrefTranslateBlockedLanguages);
448 base::ListValue* list = update.Get();
449 DCHECK(list != NULL);
450 list->Swap(&blocked_languages_list);
453 // Update kAcceptLanguages
454 for (std::vector<std::string>::const_iterator it =
455 blocked_languages.begin();
456 it != blocked_languages.end(); ++it) {
457 std::string lang = *it;
458 translate::ToChromeLanguageSynonym(&lang);
459 bool not_found =
460 std::find(accept_languages.begin(), accept_languages.end(), lang) ==
461 accept_languages.end();
462 if (not_found)
463 accept_languages.push_back(lang);
466 std::string new_accept_languages_str = JoinString(accept_languages, ",");
467 user_prefs->SetString(accept_languages_pref, new_accept_languages_str);
471 // static
472 void TranslatePrefs::CreateBlockedLanguages(
473 std::vector<std::string>* blocked_languages,
474 const std::vector<std::string>& blacklisted_languages,
475 const std::vector<std::string>& accept_languages) {
476 DCHECK(blocked_languages);
477 DCHECK(blocked_languages->empty());
479 std::set<std::string> result;
481 for (std::vector<std::string>::const_iterator it =
482 blacklisted_languages.begin();
483 it != blacklisted_languages.end(); ++it) {
484 result.insert(*it);
487 const std::string& app_locale =
488 TranslateDownloadManager::GetInstance()->application_locale();
489 std::string ui_lang = TranslateDownloadManager::GetLanguageCode(app_locale);
490 bool is_ui_english = ui_lang == "en" ||
491 StartsWithASCII(ui_lang, "en-", false);
493 for (std::vector<std::string>::const_iterator it = accept_languages.begin();
494 it != accept_languages.end(); ++it) {
495 std::string converted_lang = ConvertLangCodeForTranslation(*it);
497 // Regarding http://crbug.com/36182, even though English exists in Accept
498 // language list, English could be translated on non-English locale.
499 if (converted_lang == "en" && !is_ui_english)
500 continue;
502 result.insert(converted_lang);
505 blocked_languages->insert(
506 blocked_languages->begin(), result.begin(), result.end());
509 // static
510 std::string TranslatePrefs::ConvertLangCodeForTranslation(
511 const std::string& lang) {
512 std::vector<std::string> tokens;
513 base::SplitString(lang, '-', &tokens);
514 if (tokens.size() < 1)
515 return lang;
517 std::string main_part = tokens[0];
519 // Translate doesn't support General Chinese and the sub code is necessary.
520 if (main_part == "zh")
521 return lang;
523 translate::ToTranslateLanguageSynonym(&main_part);
524 return main_part;
527 bool TranslatePrefs::IsValueInList(const base::ListValue* list,
528 const std::string& in_value) const {
529 for (size_t i = 0; i < list->GetSize(); ++i) {
530 std::string value;
531 if (list->GetString(i, &value) && value == in_value)
532 return true;
534 return false;
537 bool TranslatePrefs::IsValueBlacklisted(const char* pref_id,
538 const std::string& value) const {
539 const base::ListValue* blacklist = prefs_->GetList(pref_id);
540 return (blacklist && !blacklist->empty() && IsValueInList(blacklist, value));
543 void TranslatePrefs::BlacklistValue(const char* pref_id,
544 const std::string& value) {
546 ListPrefUpdate update(prefs_, pref_id);
547 base::ListValue* blacklist = update.Get();
548 if (!blacklist) {
549 NOTREACHED() << "Unregistered translate blacklist pref";
550 return;
552 blacklist->Append(new base::StringValue(value));
556 void TranslatePrefs::RemoveValueFromBlacklist(const char* pref_id,
557 const std::string& value) {
558 ListPrefUpdate update(prefs_, pref_id);
559 base::ListValue* blacklist = update.Get();
560 if (!blacklist) {
561 NOTREACHED() << "Unregistered translate blacklist pref";
562 return;
564 base::StringValue string_value(value);
565 blacklist->Remove(string_value, NULL);
568 bool TranslatePrefs::IsListEmpty(const char* pref_id) const {
569 const base::ListValue* blacklist = prefs_->GetList(pref_id);
570 return (blacklist == NULL || blacklist->empty());
573 bool TranslatePrefs::IsDictionaryEmpty(const char* pref_id) const {
574 const base::DictionaryValue* dict = prefs_->GetDictionary(pref_id);
575 return (dict == NULL || dict->empty());
578 } // namespace translate