blob: 949f9e83eb54db2ae43a86edf3e9dbc8fbb69cdb [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/speech/extension_api/tts_engine_extension_observer.h"
#include "base/check.h"
#include "base/memory/singleton.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/profiles/incognito_helpers.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/speech/extension_api/tts_engine_extension_api.h"
#include "chrome/common/extensions/api/speech/tts_engine_manifest_handler.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
#include "components/keyed_service/core/keyed_service.h"
#include "content/public/browser/tts_controller.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/event_router_factory.h"
#include "extensions/common/permissions/permissions_data.h"
#if defined(OS_CHROMEOS)
namespace {
void UpdateGoogleSpeechSynthesisKeepAliveCountHelper(
content::BrowserContext* context,
bool increment) {
extensions::ProcessManager* pm = extensions::ProcessManager::Get(context);
extensions::ExtensionRegistry* registry =
extensions::ExtensionRegistry::Get(context);
const extensions::Extension* extension =
registry->enabled_extensions().GetByID(
extension_misc::kGoogleSpeechSynthesisExtensionId);
if (!extension)
return;
if (increment) {
pm->IncrementLazyKeepaliveCount(
extension, extensions::Activity::ACCESSIBILITY, std::string());
} else {
pm->DecrementLazyKeepaliveCount(
extension, extensions::Activity::ACCESSIBILITY, std::string());
}
}
void UpdateGoogleSpeechSynthesisKeepAliveCount(content::BrowserContext* context,
bool increment) {
// Deal with profiles that are non-off the record and otr. For a given
// extension load/unload, we only ever get called for one of the two potential
// profile types.
Profile* profile = Profile::FromBrowserContext(context);
if (!profile)
return;
UpdateGoogleSpeechSynthesisKeepAliveCountHelper(
profile->HasOffTheRecordProfile() ? profile->GetOffTheRecordProfile()
: profile,
increment);
}
} // namespace
#endif // defined(OS_CHROMEOS)
// Factory to load one instance of TtsExtensionLoaderChromeOs per profile.
class TtsEngineExtensionObserverFactory
: public BrowserContextKeyedServiceFactory {
public:
static TtsEngineExtensionObserver* GetForProfile(Profile* profile) {
return static_cast<TtsEngineExtensionObserver*>(
GetInstance()->GetServiceForBrowserContext(profile, true));
}
static TtsEngineExtensionObserverFactory* GetInstance() {
return base::Singleton<TtsEngineExtensionObserverFactory>::get();
}
private:
friend struct base::DefaultSingletonTraits<TtsEngineExtensionObserverFactory>;
TtsEngineExtensionObserverFactory()
: BrowserContextKeyedServiceFactory(
"TtsEngineExtensionObserver",
BrowserContextDependencyManager::GetInstance()) {
DependsOn(extensions::EventRouterFactory::GetInstance());
}
~TtsEngineExtensionObserverFactory() override {}
content::BrowserContext* GetBrowserContextToUse(
content::BrowserContext* context) const override {
// If given an incognito profile (including the Chrome OS login
// profile), share the service with the original profile.
return chrome::GetBrowserContextRedirectedInIncognito(context);
}
KeyedService* BuildServiceInstanceFor(
content::BrowserContext* profile) const override {
return new TtsEngineExtensionObserver(static_cast<Profile*>(profile));
}
};
TtsEngineExtensionObserver* TtsEngineExtensionObserver::GetInstance(
Profile* profile) {
return TtsEngineExtensionObserverFactory::GetInstance()->GetForProfile(
profile);
}
TtsEngineExtensionObserver::TtsEngineExtensionObserver(Profile* profile)
: extension_registry_observer_(this), profile_(profile) {
extension_registry_observer_.Add(
extensions::ExtensionRegistry::Get(profile_));
extensions::EventRouter* event_router =
extensions::EventRouter::Get(profile_);
DCHECK(event_router);
event_router->RegisterObserver(this, tts_engine_events::kOnSpeak);
event_router->RegisterObserver(this, tts_engine_events::kOnStop);
#if defined(OS_CHROMEOS)
accessibility_status_subscription_ =
chromeos::AccessibilityManager::Get()->RegisterCallback(
base::BindRepeating(
&TtsEngineExtensionObserver::OnAccessibilityStatusChanged,
base::Unretained(this)));
#endif
}
TtsEngineExtensionObserver::~TtsEngineExtensionObserver() = default;
const std::set<std::string> TtsEngineExtensionObserver::GetTtsExtensions() {
return engine_extension_ids_;
}
void TtsEngineExtensionObserver::Shutdown() {
extensions::EventRouter::Get(profile_)->UnregisterObserver(this);
}
bool TtsEngineExtensionObserver::IsLoadedTtsEngine(
const std::string& extension_id) {
extensions::EventRouter* event_router =
extensions::EventRouter::Get(profile_);
DCHECK(event_router);
if (event_router->ExtensionHasEventListener(extension_id,
tts_engine_events::kOnSpeak) &&
event_router->ExtensionHasEventListener(extension_id,
tts_engine_events::kOnStop)) {
return true;
}
return false;
}
void TtsEngineExtensionObserver::OnListenerAdded(
const extensions::EventListenerInfo& details) {
if (!IsLoadedTtsEngine(details.extension_id))
return;
content::TtsController::GetInstance()->VoicesChanged();
}
void TtsEngineExtensionObserver::OnExtensionLoaded(
content::BrowserContext* browser_context,
const extensions::Extension* extension) {
if (!extension->permissions_data()->HasAPIPermission(
extensions::APIPermission::kTtsEngine))
return;
engine_extension_ids_.insert(extension->id());
#if defined(OS_CHROMEOS)
if (chromeos::AccessibilityManager::Get()->IsSpokenFeedbackEnabled() &&
// This check is important because we only ever want to increment once
// when this extension loads.
extension->id() == extension_misc::kGoogleSpeechSynthesisExtensionId) {
UpdateGoogleSpeechSynthesisKeepAliveCount(browser_context,
true /* increment */);
}
#endif // defined(OS_CHROMEOS)
}
void TtsEngineExtensionObserver::OnExtensionUnloaded(
content::BrowserContext* browser_context,
const extensions::Extension* extension,
extensions::UnloadedExtensionReason reason) {
size_t erase_count = 0;
erase_count += engine_extension_ids_.erase(extension->id());
if (erase_count > 0)
content::TtsController::GetInstance()->VoicesChanged();
}
#if defined(OS_CHROMEOS)
void TtsEngineExtensionObserver::OnAccessibilityStatusChanged(
const chromeos::AccessibilityStatusEventDetails& details) {
if (details.notification_type != chromeos::AccessibilityNotificationType::
ACCESSIBILITY_TOGGLE_SPOKEN_FEEDBACK)
return;
// Google speech synthesis might not be loaded yet. If it isn't, the call in
// |OnExtensionLoaded| will do the increment. If it is, the call below will
// increment. Decrements only occur when toggling off ChromeVox here.
UpdateGoogleSpeechSynthesisKeepAliveCount(profile(), details.enabled);
}
#endif