Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(69)

Side by Side Diff: chrome/browser/search/hotword_service.cc

Issue 330193005: [Hotword] Uninstall and reinstall the extension upon language change. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: responding to comments Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright 2013 The Chromium Authors. All rights reserved. 1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/search/hotword_service.h" 5 #include "chrome/browser/search/hotword_service.h"
6 6
7 #include "base/i18n/case_conversion.h" 7 #include "base/i18n/case_conversion.h"
8 #include "base/metrics/field_trial.h" 8 #include "base/metrics/field_trial.h"
9 #include "base/metrics/histogram.h" 9 #include "base/metrics/histogram.h"
10 #include "base/path_service.h" 10 #include "base/path_service.h"
11 #include "base/prefs/pref_service.h" 11 #include "base/prefs/pref_service.h"
12 #include "chrome/browser/browser_process.h" 12 #include "chrome/browser/browser_process.h"
13 #include "chrome/browser/chrome_notification_types.h" 13 #include "chrome/browser/chrome_notification_types.h"
14 #include "chrome/browser/extensions/extension_service.h" 14 #include "chrome/browser/extensions/extension_service.h"
15 #include "chrome/browser/extensions/pending_extension_manager.h"
16 #include "chrome/browser/extensions/updater/extension_updater.h"
17 #include "chrome/browser/extensions/webstore_startup_installer.h"
15 #include "chrome/browser/plugins/plugin_prefs.h" 18 #include "chrome/browser/plugins/plugin_prefs.h"
16 #include "chrome/browser/profiles/profile.h" 19 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/search/hotword_service_factory.h" 20 #include "chrome/browser/search/hotword_service_factory.h"
18 #include "chrome/common/chrome_paths.h" 21 #include "chrome/common/chrome_paths.h"
19 #include "chrome/common/extensions/extension_constants.h" 22 #include "chrome/common/extensions/extension_constants.h"
20 #include "chrome/common/pref_names.h" 23 #include "chrome/common/pref_names.h"
21 #include "content/public/browser/browser_thread.h" 24 #include "content/public/browser/browser_thread.h"
22 #include "content/public/browser/notification_service.h" 25 #include "content/public/browser/notification_service.h"
23 #include "content/public/browser/plugin_service.h" 26 #include "content/public/browser/plugin_service.h"
24 #include "content/public/common/webplugininfo.h" 27 #include "content/public/common/webplugininfo.h"
25 #include "extensions/browser/extension_system.h" 28 #include "extensions/browser/extension_system.h"
26 #include "extensions/common/extension.h" 29 #include "extensions/common/extension.h"
30 #include "extensions/common/one_shot_event.h"
27 #include "grit/generated_resources.h" 31 #include "grit/generated_resources.h"
28 #include "ui/base/l10n/l10n_util.h" 32 #include "ui/base/l10n/l10n_util.h"
29 33
30 // The whole file relies on the extension systems but this file is built on 34 // The whole file relies on the extension systems but this file is built on
31 // some non-extension supported platforms and including an API header will cause 35 // some non-extension supported platforms and including an API header will cause
32 // a compile error since it depends on header files generated by .idl. 36 // a compile error since it depends on header files generated by .idl.
33 // TODO(mukai): clean up file dependencies and remove this clause. 37 // TODO(mukai): clean up file dependencies and remove this clause.
34 #if defined(ENABLE_EXTENSIONS) 38 #if defined(ENABLE_EXTENSIONS)
35 #include "chrome/browser/extensions/api/hotword_private/hotword_private_api.h" 39 #include "chrome/browser/extensions/api/hotword_private/hotword_private_api.h"
36 #endif 40 #endif
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after
138 ExtensionService* GetExtensionService(Profile* profile) { 142 ExtensionService* GetExtensionService(Profile* profile) {
139 CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 143 CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
140 144
141 extensions::ExtensionSystem* extension_system = 145 extensions::ExtensionSystem* extension_system =
142 extensions::ExtensionSystem::Get(profile); 146 extensions::ExtensionSystem::Get(profile);
143 if (extension_system) 147 if (extension_system)
144 return extension_system->extension_service(); 148 return extension_system->extension_service();
145 return NULL; 149 return NULL;
146 } 150 }
147 151
152 std::string GetCurrentLocale(Profile* profile) {
153 std::string locale =
154 #if defined(OS_CHROMEOS)
155 // On ChromeOS locale is per-profile.
156 profile->GetPrefs()->GetString(prefs::kApplicationLocale);
157 #else
158 g_browser_process->GetApplicationLocale();
159 #endif
160 return locale;
161 }
162
148 } // namespace 163 } // namespace
149 164
150 namespace hotword_internal { 165 namespace hotword_internal {
151 // Constants for the hotword field trial. 166 // Constants for the hotword field trial.
152 const char kHotwordFieldTrialName[] = "VoiceTrigger"; 167 const char kHotwordFieldTrialName[] = "VoiceTrigger";
153 const char kHotwordFieldTrialDisabledGroupName[] = "Disabled"; 168 const char kHotwordFieldTrialDisabledGroupName[] = "Disabled";
154 // Old preference constant. 169 // Old preference constant.
155 const char kHotwordUnusablePrefName[] = "hotword.search_enabled"; 170 const char kHotwordUnusablePrefName[] = "hotword.search_enabled";
156 } // namespace hotword_internal 171 } // namespace hotword_internal
157 172
158 // static 173 // static
159 bool HotwordService::DoesHotwordSupportLanguage(Profile* profile) { 174 bool HotwordService::DoesHotwordSupportLanguage(Profile* profile) {
160 std::string locale = 175 std::string normalized_locale =
161 #if defined(OS_CHROMEOS) 176 l10n_util::NormalizeLocale(GetCurrentLocale(profile));
162 // On ChromeOS locale is per-profile.
163 profile->GetPrefs()->GetString(prefs::kApplicationLocale);
164 #else
165 g_browser_process->GetApplicationLocale();
166 #endif
167 std::string normalized_locale = l10n_util::NormalizeLocale(locale);
168 StringToLowerASCII(&normalized_locale); 177 StringToLowerASCII(&normalized_locale);
169 178
170 for (size_t i = 0; i < arraysize(kSupportedLocales); i++) { 179 for (size_t i = 0; i < arraysize(kSupportedLocales); i++) {
171 if (normalized_locale.compare(0, 2, kSupportedLocales[i]) == 0) 180 if (normalized_locale.compare(0, 2, kSupportedLocales[i]) == 0)
172 return true; 181 return true;
173 } 182 }
174 return false; 183 return false;
175 } 184 }
176 185
177 HotwordService::HotwordService(Profile* profile) 186 HotwordService::HotwordService(Profile* profile)
178 : profile_(profile), 187 : profile_(profile),
188 extension_registry_observer_(this),
179 client_(NULL), 189 client_(NULL),
180 error_message_(0) { 190 error_message_(0),
191 reinstall_pending_(false),
192 weak_factory_(this) {
193 extension_registry_observer_.Add(extensions::ExtensionRegistry::Get(profile));
181 // This will be called during profile initialization which is a good time 194 // This will be called during profile initialization which is a good time
182 // to check the user's hotword state. 195 // to check the user's hotword state.
183 HotwordEnabled enabled_state = UNSET; 196 HotwordEnabled enabled_state = UNSET;
184 if (profile_->GetPrefs()->HasPrefPath(prefs::kHotwordSearchEnabled)) { 197 if (profile_->GetPrefs()->HasPrefPath(prefs::kHotwordSearchEnabled)) {
185 if (profile_->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled)) 198 if (profile_->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled))
186 enabled_state = ENABLED; 199 enabled_state = ENABLED;
187 else 200 else
188 enabled_state = DISABLED; 201 enabled_state = DISABLED;
189 } else { 202 } else {
190 // If the preference has not been set the hotword extension should 203 // If the preference has not been set the hotword extension should
191 // not be running. However, this should only be done if auto-install 204 // not be running. However, this should only be done if auto-install
192 // is enabled which is gated through the IsHotwordAllowed check. 205 // is enabled which is gated through the IsHotwordAllowed check.
193 if (IsHotwordAllowed()) 206 if (IsHotwordAllowed())
194 DisableHotwordExtension(GetExtensionService(profile_)); 207 DisableHotwordExtension(GetExtensionService(profile_));
195 } 208 }
196 UMA_HISTOGRAM_ENUMERATION("Hotword.Enabled", enabled_state, 209 UMA_HISTOGRAM_ENUMERATION("Hotword.Enabled", enabled_state,
197 NUM_HOTWORD_ENABLED_METRICS); 210 NUM_HOTWORD_ENABLED_METRICS);
198 211
199 pref_registrar_.Init(profile_->GetPrefs()); 212 pref_registrar_.Init(profile_->GetPrefs());
200 pref_registrar_.Add( 213 pref_registrar_.Add(
201 prefs::kHotwordSearchEnabled, 214 prefs::kHotwordSearchEnabled,
202 base::Bind(&HotwordService::OnHotwordSearchEnabledChanged, 215 base::Bind(&HotwordService::OnHotwordSearchEnabledChanged,
203 base::Unretained(this))); 216 base::Unretained(this)));
204 217
205 registrar_.Add(this, 218 registrar_.Add(this,
206 chrome::NOTIFICATION_EXTENSION_INSTALLED_DEPRECATED,
207 content::Source<Profile>(profile_));
208 registrar_.Add(this,
209 chrome::NOTIFICATION_BROWSER_WINDOW_READY, 219 chrome::NOTIFICATION_BROWSER_WINDOW_READY,
210 content::NotificationService::AllSources()); 220 content::NotificationService::AllSources());
211 221
222 extensions::ExtensionSystem::Get(profile_)->ready().Post(
223 FROM_HERE,
224 base::Bind(base::IgnoreResult(
225 &HotwordService::MaybeReinstallHotwordExtension),
226 weak_factory_.GetWeakPtr()));
227
212 // Clear the old user pref because it became unusable. 228 // Clear the old user pref because it became unusable.
213 // TODO(rlp): Remove this code per crbug.com/358789. 229 // TODO(rlp): Remove this code per crbug.com/358789.
214 if (profile_->GetPrefs()->HasPrefPath( 230 if (profile_->GetPrefs()->HasPrefPath(
215 hotword_internal::kHotwordUnusablePrefName)) { 231 hotword_internal::kHotwordUnusablePrefName)) {
216 profile_->GetPrefs()->ClearPref(hotword_internal::kHotwordUnusablePrefName); 232 profile_->GetPrefs()->ClearPref(hotword_internal::kHotwordUnusablePrefName);
217 } 233 }
218 } 234 }
219 235
220 HotwordService::~HotwordService() { 236 HotwordService::~HotwordService() {
221 } 237 }
222 238
223 void HotwordService::Observe(int type, 239 void HotwordService::Observe(int type,
224 const content::NotificationSource& source, 240 const content::NotificationSource& source,
225 const content::NotificationDetails& details) { 241 const content::NotificationDetails& details) {
226 if (type == chrome::NOTIFICATION_EXTENSION_INSTALLED_DEPRECATED) { 242 if (type == chrome::NOTIFICATION_BROWSER_WINDOW_READY) {
227 const extensions::Extension* extension =
228 content::Details<const extensions::InstalledExtensionInfo>(details)
229 ->extension;
230 // Disabling the extension automatically on install should only occur
231 // if the user is in the field trial for auto-install which is gated
232 // by the IsHotwordAllowed check.
233 if (IsHotwordAllowed() &&
234 extension->id() == extension_misc::kHotwordExtensionId &&
235 !profile_->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled)) {
236 DisableHotwordExtension(GetExtensionService(profile_));
237 // Once the extension is disabled, it will not be enabled until the
238 // user opts in at which point the pref registrar will take over
239 // enabling and disabling.
240 registrar_.Remove(this,
241 chrome::NOTIFICATION_EXTENSION_INSTALLED_DEPRECATED,
242 content::Source<Profile>(profile_));
243 }
244 } else if (type == chrome::NOTIFICATION_BROWSER_WINDOW_READY) {
245 // The microphone monitor must be initialized as the page is loading 243 // The microphone monitor must be initialized as the page is loading
246 // so that the state of the microphone is available when the page 244 // so that the state of the microphone is available when the page
247 // loads. The Ok Google Hotword setting will display an error if there 245 // loads. The Ok Google Hotword setting will display an error if there
248 // is no microphone but this information will not be up-to-date unless 246 // is no microphone but this information will not be up-to-date unless
249 // the monitor had already been started. Furthermore, the pop up to 247 // the monitor had already been started. Furthermore, the pop up to
250 // opt in to hotwording won't be available if it thinks there is no 248 // opt in to hotwording won't be available if it thinks there is no
251 // microphone. There is no hard guarantee that the monitor will actually 249 // microphone. There is no hard guarantee that the monitor will actually
252 // be up by the time it's needed, but this is the best we can do without 250 // be up by the time it's needed, but this is the best we can do without
253 // starting it at start up which slows down start up too much. 251 // starting it at start up which slows down start up too much.
254 // The content/media for microphone uses the same observer design and 252 // The content/media for microphone uses the same observer design and
255 // makes use of the same audio device monitor. 253 // makes use of the same audio device monitor.
256 HotwordServiceFactory::GetInstance()->UpdateMicrophoneState(); 254 HotwordServiceFactory::GetInstance()->UpdateMicrophoneState();
257 } 255 }
258 } 256 }
259 257
258 void HotwordService::OnExtensionUninstalled(
259 content::BrowserContext* browser_context,
260 const extensions::Extension* extension) {
261 CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
262
263 if (extension->id() != extension_misc::kHotwordExtensionId ||
264 profile_ != Profile::FromBrowserContext(browser_context) ||
265 !GetExtensionService(profile_))
266 return;
267
268 // If the extension wasn't uninstalled due to language change, don't try to
269 // reinstall it.
270 if (!reinstall_pending_)
271 return;
272
273 InstallHotwordExtensionFromWebstore();
274 SetPreviousLanguagePref();
275 }
276
277 void HotwordService::InstallHotwordExtensionFromWebstore() {
278 installer_ = new extensions::WebstoreStartupInstaller(
279 extension_misc::kHotwordExtensionId,
280 profile_,
281 false,
282 extensions::WebstoreStandaloneInstaller::Callback());
283 installer_->BeginInstall();
284 }
285
286 void HotwordService::OnExtensionInstalled(
287 content::BrowserContext* browser_context,
288 const extensions::Extension* extension) {
289
290 if (extension->id() != extension_misc::kHotwordExtensionId ||
291 profile_ != Profile::FromBrowserContext(browser_context))
292 return;
293
294 // If the previous locale pref has never been set, set it now since
295 // the extension has been installed.
296 if (!profile_->GetPrefs()->HasPrefPath(prefs::kHotwordPreviousLanguage))
297 SetPreviousLanguagePref();
298
299 // If MaybeReinstallHotwordExtension already triggered an uninstall, we
300 // don't want to loop and trigger another uninstall-install cycle.
301 // However, if we arrived here via an uninstall-triggered-install (and in
302 // that case |reinstall_pending_| will be true) then we know install
303 // has completed and we can reset |reinstall_pending_|.
304 if (!reinstall_pending_)
305 MaybeReinstallHotwordExtension();
306 else
307 reinstall_pending_ = false;
308
309 // Now that the extension is installed, if the user has not selected
310 // the preference on, make sure it is turned off.
311 //
312 // Disabling the extension automatically on install should only occur
313 // if the user is in the field trial for auto-install which is gated
314 // by the IsHotwordAllowed check. The check for IsHotwordAllowed() here
315 // can be removed once it's known that few people have manually
316 // installed extension.
317 if (IsHotwordAllowed() &&
318 !profile_->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled)) {
319 DisableHotwordExtension(GetExtensionService(profile_));
320 }
321 }
322
323 bool HotwordService::MaybeReinstallHotwordExtension() {
324 CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
325
326 ExtensionService* extension_service = GetExtensionService(profile_);
327 if (!extension_service)
328 return false;
329
330 const extensions::Extension* extension = extension_service->GetExtensionById(
331 extension_misc::kHotwordExtensionId, true);
332 if (!extension)
333 return false;
334
335 // If the extension is currently pending, return and we'll check again
336 // after the install is finished.
337 extensions::PendingExtensionManager* pending_manager =
338 extension_service->pending_extension_manager();
339 if (pending_manager->IsIdPending(extension->id()))
340 return false;
341
342 // If there is already a pending request from HotwordService, don't try
343 // to uninstall either.
344 if (reinstall_pending_)
345 return false;
346
347 // Check if the current locale matches the previous. If they don't match,
348 // uninstall the extension.
349 if (!ShouldReinstallHotwordExtension())
350 return false;
351
352 // Ensure the call to OnExtensionUninstalled was triggered by a language
353 // change so it's okay to reinstall.
354 reinstall_pending_ = true;
355
356 // This works because the call path MaybeReinstallHotwordExtension->
357 // UninstallHotwordExtension->OnExtensionUninstalled is all on the UI
358 // thread.
359 return reinstall_pending_ = UninstallHotwordExtension(extension_service);
Jered 2014/06/18 21:06:04 nit: Can we write this in a less clever way? e.g.
rpetterson 2014/06/18 23:05:57 So this doesn't actually work. Which is pretty int
Jered 2014/06/18 23:12:17 I was concerned that if UninstallHotwordExtension(
rpetterson 2014/06/18 23:45:35 A valid concern. This looks like a good solution t
360 }
361
362 bool HotwordService::UninstallHotwordExtension(
363 ExtensionService* extension_service) {
364 base::string16 error;
365 if (!extension_service->UninstallExtension(
366 extension_misc::kHotwordExtensionId, true, &error)) {
367 LOG(WARNING) << "Cannot uninstall extension with id "
368 << extension_misc::kHotwordExtensionId
369 << ": " << error;
370 return false;
371 }
372 return true;
373 }
374
260 bool HotwordService::IsServiceAvailable() { 375 bool HotwordService::IsServiceAvailable() {
261 error_message_ = 0; 376 error_message_ = 0;
262 377
263 // Determine if the extension is available. 378 // Determine if the extension is available.
264 extensions::ExtensionSystem* system = 379 extensions::ExtensionSystem* system =
265 extensions::ExtensionSystem::Get(profile_); 380 extensions::ExtensionSystem::Get(profile_);
266 ExtensionService* service = system->extension_service(); 381 ExtensionService* service = system->extension_service();
267 // Include disabled extensions (true parameter) since it may not be enabled 382 // Include disabled extensions (true parameter) since it may not be enabled
268 // if the user opted out. 383 // if the user opted out.
269 const extensions::Extension* extension = 384 const extensions::Extension* extension =
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after
364 479
365 DCHECK(client_ == client); 480 DCHECK(client_ == client);
366 481
367 client_ = NULL; 482 client_ = NULL;
368 HotwordPrivateEventService* event_service = 483 HotwordPrivateEventService* event_service =
369 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_); 484 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
370 if (event_service) 485 if (event_service)
371 event_service->OnHotwordSessionStopped(); 486 event_service->OnHotwordSessionStopped();
372 #endif 487 #endif
373 } 488 }
489
490 void HotwordService::SetPreviousLanguagePref() {
491 profile_->GetPrefs()->SetString(prefs::kHotwordPreviousLanguage,
492 GetCurrentLocale(profile_));
493 }
494
495 bool HotwordService::ShouldReinstallHotwordExtension() {
496 // If there is no previous locale pref, then this is the first install
497 // so no need to uninstall first.
498 if (!profile_->GetPrefs()->HasPrefPath(prefs::kHotwordPreviousLanguage))
499 return false;
500
501 std::string previous_locale =
502 profile_->GetPrefs()->GetString(prefs::kHotwordPreviousLanguage);
503 std::string locale = GetCurrentLocale(profile_);
504
505 // If it's a new locale, then the old extension should be uninstalled.
506 return locale != previous_locale &&
507 HotwordService::DoesHotwordSupportLanguage(profile_);
508 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698