Commit 97173533 authored by evliu's avatar evliu Committed by Chromium LUCI CQ

Add SODA language pack components for de-DE, es-ES, fr-FR, and it-IT

This CL adds the Speech On-Device API (SODA) language pack components for de-DE, es-ES, fr-FR, and it-IT. The language pack components are implemented as separate components because users will be able to install any combination of language packs.

Bug: 1161569
Change-Id: I98f1dc2416056c10ea1d7c528ec45bca07343f48
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2602579
Commit-Queue: Evan Liu <evliu@google.com>
Reviewed-by: default avatarSorin Jianu <sorin@chromium.org>
Reviewed-by: default avatarAbigail Klein <abigailbklein@google.com>
Reviewed-by: default avatarJoshua Pawlicki <waffles@chromium.org>
Cr-Commit-Position: refs/heads/master@{#844248}
parent 75bc2309
......@@ -3495,10 +3495,8 @@ static_library("browser") {
"component_updater/intervention_policy_database_component_installer.h",
"component_updater/soda_component_installer.cc",
"component_updater/soda_component_installer.h",
"component_updater/soda_en_us_component_installer.cc",
"component_updater/soda_en_us_component_installer.h",
"component_updater/soda_ja_jp_component_installer.cc",
"component_updater/soda_ja_jp_component_installer.h",
"component_updater/soda_language_pack_component_installer.cc",
"component_updater/soda_language_pack_component_installer.h",
"content_settings/generated_cookie_prefs.cc",
"content_settings/generated_cookie_prefs.h",
"content_settings/generated_notification_pref.cc",
......
......@@ -10,13 +10,13 @@
#include "base/bind.h"
#include "base/check_op.h"
#include "base/containers/flat_set.h"
#include "base/feature_list.h"
#include "base/no_destructor.h"
#include "base/numerics/ranges.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/component_updater/soda_component_installer.h"
#include "chrome/browser/component_updater/soda_en_us_component_installer.h"
#include "chrome/browser/component_updater/soda_ja_jp_component_installer.h"
#include "chrome/browser/component_updater/soda_language_pack_component_installer.h"
#include "chrome/common/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/update_client/crx_update_item.h"
......@@ -89,30 +89,26 @@ void SodaInstallerImpl::InstallLanguage(PrefService* prefs) {
bool SodaInstallerImpl::IsSodaRegistered() {
if (!base::FeatureList::IsEnabled(media::kUseSodaForLiveCaption))
return true;
std::vector<std::string> component_ids =
g_browser_process->component_updater()->GetComponentIDs();
bool has_soda = false;
bool has_language_pack = false;
for (std::string id : component_ids) {
if (id == component_updater::SodaComponentInstallerPolicy::GetExtensionId())
has_soda = true;
if (id == component_updater::SodaEnUsComponentInstallerPolicy::
GetExtensionId() ||
id == component_updater::SodaJaJpComponentInstallerPolicy::
GetExtensionId()) {
has_language_pack = true;
}
}
base::flat_set<std::string> component_ids = component_updater::
SodaLanguagePackComponentInstallerPolicy::GetExtensionIds();
const bool has_soda = component_ids.contains(
component_updater::SodaComponentInstallerPolicy::GetExtensionId());
const bool has_language_pack =
std::any_of(component_ids.begin(), component_ids.end(),
[&component_ids](const std::string& id) {
return component_ids.contains(id);
});
return has_soda && has_language_pack;
}
void SodaInstallerImpl::OnEvent(Events event, const std::string& id) {
if (id != component_updater::SodaComponentInstallerPolicy::GetExtensionId() &&
id != component_updater::SodaEnUsComponentInstallerPolicy::
GetExtensionId() &&
id !=
component_updater::SodaJaJpComponentInstallerPolicy::GetExtensionId())
if (!component_updater::SodaLanguagePackComponentInstallerPolicy::
GetExtensionIds()
.contains(id) &&
id != component_updater::SodaComponentInstallerPolicy::GetExtensionId()) {
return;
}
switch (event) {
case Events::COMPONENT_UPDATE_FOUND:
......
......@@ -8,8 +8,7 @@
#include "base/files/file_util.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/component_updater/soda_en_us_component_installer.h"
#include "chrome/browser/component_updater/soda_ja_jp_component_installer.h"
#include "chrome/browser/component_updater/soda_language_pack_component_installer.h"
#include "chrome/common/pref_names.h"
#include "components/component_updater/component_updater_service.h"
#include "components/crx_file/id_util.h"
......@@ -178,8 +177,12 @@ void UpdateSodaInstallDirPref(PrefService* prefs,
void RegisterPrefsForSodaComponent(PrefRegistrySimple* registry) {
registry->RegisterTimePref(prefs::kSodaScheduledDeletionTime, base::Time());
registry->RegisterFilePathPref(prefs::kSodaBinaryPath, base::FilePath());
registry->RegisterFilePathPref(prefs::kSodaEnUsConfigPath, base::FilePath());
registry->RegisterFilePathPref(prefs::kSodaJaJpConfigPath, base::FilePath());
// Register language pack config path preferences.
for (const component_updater::SodaLanguagePackComponentConfig& config :
kLanguageComponentConfigs) {
registry->RegisterFilePathPref(config.config_path_pref, base::FilePath());
}
}
void RegisterSodaComponent(ComponentUpdateService* cus,
......@@ -206,7 +209,7 @@ void RegisterSodaComponent(ComponentUpdateService* cus,
installer->Register(cus, std::move(callback));
} else {
auto deletion_time =
base::Time deletion_time =
global_prefs->GetTime(prefs::kSodaScheduledDeletionTime);
if (!deletion_time.is_null() && deletion_time < base::Time::Now()) {
base::DeletePathRecursively(speech::GetSodaDirectory());
......@@ -225,24 +228,12 @@ void RegisterSodaLanguageComponent(ComponentUpdateService* cus,
if (base::FeatureList::IsEnabled(media::kUseSodaForLiveCaption) &&
base::FeatureList::IsEnabled(media::kLiveCaption)) {
if (profile_prefs->GetBoolean(prefs::kLiveCaptionEnabled)) {
speech::LanguageCode language = speech::GetLanguageCode(
profile_prefs->GetString(prefs::kLiveCaptionLanguageCode));
switch (language) {
case speech::LanguageCode::kNone:
// Do nothing.
break;
case speech::LanguageCode::kEnUs:
RegisterSodaEnUsComponent(
cus, global_prefs,
base::BindOnce(&SodaEnUsComponentInstallerPolicy::
UpdateSodaEnUsComponentOnDemand));
break;
case speech::LanguageCode::kJaJp:
RegisterSodaJaJpComponent(
cus, global_prefs,
base::BindOnce(&SodaJaJpComponentInstallerPolicy::
UpdateSodaJaJpComponentOnDemand));
break;
base::Optional<component_updater::SodaLanguagePackComponentConfig>
config = SodaLanguagePackComponentInstallerPolicy::
GetLanguageComponentConfig(
profile_prefs->GetString(prefs::kLiveCaptionLanguageCode));
if (config) {
RegisterSodaLanguagePackComponent(config.value(), cus, global_prefs);
}
}
}
......
// Copyright 2020 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/component_updater/soda_en_us_component_installer.h"
#include "base/bind.h"
#include "base/files/file_util.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/component_updater/soda_component_installer.h"
#include "chrome/common/pref_names.h"
#include "components/component_updater/component_updater_service.h"
#include "components/crx_file/id_util.h"
#include "components/soda/constants.h"
#include "components/update_client/update_client_errors.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "crypto/sha2.h"
#include "media/base/media_switches.h"
using content::BrowserThread;
namespace component_updater {
namespace {
// The SHA256 of the SubjectPublicKeyInfo used to sign the component.
// The component id is: oegebmmcimckjhkhbggblnkjloogjdfg
constexpr uint8_t kSodaEnUsPublicKeySHA256[32] = {
0xe4, 0x64, 0x1c, 0xc2, 0x8c, 0x2a, 0x97, 0xa7, 0x16, 0x61, 0xbd,
0xa9, 0xbe, 0xe6, 0x93, 0x56, 0xf5, 0x05, 0x33, 0x9b, 0x8b, 0x0b,
0x02, 0xe2, 0x6b, 0x7e, 0x6c, 0x40, 0xa1, 0xd2, 0x7e, 0x18};
static_assert(base::size(kSodaEnUsPublicKeySHA256) == crypto::kSHA256Length,
"Wrong hash length");
constexpr char kSodaEnUsManifestName[] = "SODA en-US Models";
} // namespace
SodaEnUsComponentInstallerPolicy::SodaEnUsComponentInstallerPolicy(
OnSodaEnUsComponentReadyCallback callback)
: on_component_ready_callback_(callback) {}
SodaEnUsComponentInstallerPolicy::~SodaEnUsComponentInstallerPolicy() = default;
const std::string SodaEnUsComponentInstallerPolicy::GetExtensionId() {
return crx_file::id_util::GenerateIdFromHash(
kSodaEnUsPublicKeySHA256, sizeof(kSodaEnUsPublicKeySHA256));
}
void SodaEnUsComponentInstallerPolicy::UpdateSodaEnUsComponentOnDemand() {
const std::string crx_id =
component_updater::SodaEnUsComponentInstallerPolicy::GetExtensionId();
g_browser_process->component_updater()->GetOnDemandUpdater().OnDemandUpdate(
crx_id, component_updater::OnDemandUpdater::Priority::FOREGROUND,
base::BindOnce([](update_client::Error error) {
if (error != update_client::Error::NONE &&
error != update_client::Error::UPDATE_IN_PROGRESS)
LOG(ERROR) << "On demand update of the SODA en-US component failed "
"with error: "
<< static_cast<int>(error);
}));
}
bool SodaEnUsComponentInstallerPolicy::VerifyInstallation(
const base::DictionaryValue& manifest,
const base::FilePath& install_dir) const {
return base::PathExists(
install_dir.Append(speech::kSodaLanguagePackDirectoryRelativePath));
}
bool SodaEnUsComponentInstallerPolicy::
SupportsGroupPolicyEnabledComponentUpdates() const {
return true;
}
bool SodaEnUsComponentInstallerPolicy::RequiresNetworkEncryption() const {
return true;
}
update_client::CrxInstaller::Result
SodaEnUsComponentInstallerPolicy::OnCustomInstall(
const base::DictionaryValue& manifest,
const base::FilePath& install_dir) {
return SodaComponentInstallerPolicy::SetComponentDirectoryPermission(
install_dir);
}
void SodaEnUsComponentInstallerPolicy::OnCustomUninstall() {}
void SodaEnUsComponentInstallerPolicy::ComponentReady(
const base::Version& version,
const base::FilePath& install_dir,
std::unique_ptr<base::DictionaryValue> manifest) {
VLOG(1) << "Component ready, version " << version.GetString() << " in "
<< install_dir.value();
on_component_ready_callback_.Run(install_dir);
}
base::FilePath SodaEnUsComponentInstallerPolicy::GetRelativeInstallDir() const {
return base::FilePath(speech::kSodaEnUsInstallationRelativePath);
}
void SodaEnUsComponentInstallerPolicy::GetHash(
std::vector<uint8_t>* hash) const {
hash->assign(kSodaEnUsPublicKeySHA256,
kSodaEnUsPublicKeySHA256 + base::size(kSodaEnUsPublicKeySHA256));
}
std::string SodaEnUsComponentInstallerPolicy::GetName() const {
return kSodaEnUsManifestName;
}
update_client::InstallerAttributes
SodaEnUsComponentInstallerPolicy::GetInstallerAttributes() const {
return update_client::InstallerAttributes();
}
std::vector<std::string> SodaEnUsComponentInstallerPolicy::GetMimeTypes()
const {
return std::vector<std::string>();
}
void UpdateSodaEnUsInstallDirPref(PrefService* prefs,
const base::FilePath& install_dir) {
#if !defined(OS_ANDROID)
prefs->SetFilePath(
prefs::kSodaEnUsConfigPath,
install_dir.Append(speech::kSodaLanguagePackDirectoryRelativePath));
#endif
}
void RegisterSodaEnUsComponent(ComponentUpdateService* cus,
PrefService* prefs,
base::OnceClosure callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto installer = base::MakeRefCounted<ComponentInstaller>(
std::make_unique<SodaEnUsComponentInstallerPolicy>(base::BindRepeating(
[](ComponentUpdateService* cus, PrefService* prefs,
const base::FilePath& install_dir) {
content::GetUIThreadTaskRunner({base::TaskPriority::BEST_EFFORT})
->PostTask(FROM_HERE,
base::BindOnce(&UpdateSodaEnUsInstallDirPref,
prefs, install_dir));
},
cus, prefs)));
installer->Register(cus, std::move(callback));
}
} // namespace component_updater
// Copyright 2020 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.
#ifndef CHROME_BROWSER_COMPONENT_UPDATER_SODA_EN_US_COMPONENT_INSTALLER_H_
#define CHROME_BROWSER_COMPONENT_UPDATER_SODA_EN_US_COMPONENT_INSTALLER_H_
#include <string>
#include "components/component_updater/component_installer.h"
#include "components/prefs/pref_service.h"
#include "components/update_client/update_client.h"
namespace component_updater {
// Success callback to be run after the component is downloaded.
using OnSodaEnUsComponentReadyCallback =
base::RepeatingCallback<void(const base::FilePath&)>;
class SodaEnUsComponentInstallerPolicy : public ComponentInstallerPolicy {
public:
explicit SodaEnUsComponentInstallerPolicy(
OnSodaEnUsComponentReadyCallback callback);
~SodaEnUsComponentInstallerPolicy() override;
SodaEnUsComponentInstallerPolicy(const SodaEnUsComponentInstallerPolicy&) =
delete;
SodaEnUsComponentInstallerPolicy& operator=(
const SodaEnUsComponentInstallerPolicy&) = delete;
static const std::string GetExtensionId();
static void UpdateSodaEnUsComponentOnDemand();
private:
FRIEND_TEST_ALL_PREFIXES(SodaEnUsComponentInstallerTest,
ComponentReady_CallsLambda);
// The following methods override ComponentInstallerPolicy.
bool SupportsGroupPolicyEnabledComponentUpdates() const override;
bool RequiresNetworkEncryption() const override;
update_client::CrxInstaller::Result OnCustomInstall(
const base::DictionaryValue& manifest,
const base::FilePath& install_dir) override;
void OnCustomUninstall() override;
bool VerifyInstallation(const base::DictionaryValue& manifest,
const base::FilePath& install_dir) const override;
void ComponentReady(const base::Version& version,
const base::FilePath& install_dir,
std::unique_ptr<base::DictionaryValue> manifest) override;
base::FilePath GetRelativeInstallDir() const override;
void GetHash(std::vector<uint8_t>* hash) const override;
std::string GetName() const override;
update_client::InstallerAttributes GetInstallerAttributes() const override;
std::vector<std::string> GetMimeTypes() const override;
OnSodaEnUsComponentReadyCallback on_component_ready_callback_;
};
// Call once during startup to make the component update service aware of
// the File Type Policies component.
void RegisterSodaEnUsComponent(ComponentUpdateService* cus,
PrefService* prefs,
base::OnceClosure callback);
} // namespace component_updater
#endif // CHROME_BROWSER_COMPONENT_UPDATER_SODA_EN_US_COMPONENT_INSTALLER_H_
// Copyright 2020 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/component_updater/soda_ja_jp_component_installer.h"
#include "base/bind.h"
#include "base/files/file_util.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/component_updater/soda_component_installer.h"
#include "chrome/common/pref_names.h"
#include "components/component_updater/component_updater_service.h"
#include "components/crx_file/id_util.h"
#include "components/soda/constants.h"
#include "components/update_client/update_client_errors.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "crypto/sha2.h"
#include "media/base/media_switches.h"
using content::BrowserThread;
namespace component_updater {
namespace {
// The SHA256 of the SubjectPublicKeyInfo used to sign the component.
// The component id is: onhpjgkfgajmkkeniaoflicgokpaebfa
constexpr uint8_t kSodaJaJpPublicKeySHA256[32] = {
0xed, 0x7f, 0x96, 0xa5, 0x60, 0x9c, 0xaa, 0x4d, 0x80, 0xe5, 0xb8,
0x26, 0xea, 0xf0, 0x41, 0x50, 0x09, 0x52, 0xa4, 0xb3, 0x1e, 0x6a,
0x8e, 0x24, 0x99, 0xde, 0x51, 0x14, 0xc4, 0x3c, 0xfa, 0x48};
static_assert(base::size(kSodaJaJpPublicKeySHA256) == crypto::kSHA256Length,
"Wrong hash length");
constexpr char kSodaJaJpManifestName[] = "SODA ja-JP Models";
} // namespace
SodaJaJpComponentInstallerPolicy::SodaJaJpComponentInstallerPolicy(
OnSodaJaJpComponentReadyCallback callback)
: on_component_ready_callback_(callback) {}
SodaJaJpComponentInstallerPolicy::~SodaJaJpComponentInstallerPolicy() = default;
const std::string SodaJaJpComponentInstallerPolicy::GetExtensionId() {
return crx_file::id_util::GenerateIdFromHash(
kSodaJaJpPublicKeySHA256, sizeof(kSodaJaJpPublicKeySHA256));
}
void SodaJaJpComponentInstallerPolicy::UpdateSodaJaJpComponentOnDemand() {
const std::string crx_id =
component_updater::SodaJaJpComponentInstallerPolicy::GetExtensionId();
g_browser_process->component_updater()->GetOnDemandUpdater().OnDemandUpdate(
crx_id, component_updater::OnDemandUpdater::Priority::FOREGROUND,
base::BindOnce([](update_client::Error error) {
if (error != update_client::Error::NONE &&
error != update_client::Error::UPDATE_IN_PROGRESS)
LOG(ERROR) << "On demand update of the SODA ja-JP component failed "
"with error: "
<< static_cast<int>(error);
}));
}
bool SodaJaJpComponentInstallerPolicy::VerifyInstallation(
const base::DictionaryValue& manifest,
const base::FilePath& install_dir) const {
return base::PathExists(
install_dir.Append(speech::kSodaLanguagePackDirectoryRelativePath));
}
bool SodaJaJpComponentInstallerPolicy::
SupportsGroupPolicyEnabledComponentUpdates() const {
return true;
}
bool SodaJaJpComponentInstallerPolicy::RequiresNetworkEncryption() const {
return true;
}
update_client::CrxInstaller::Result
SodaJaJpComponentInstallerPolicy::OnCustomInstall(
const base::DictionaryValue& manifest,
const base::FilePath& install_dir) {
return SodaComponentInstallerPolicy::SetComponentDirectoryPermission(
install_dir);
}
void SodaJaJpComponentInstallerPolicy::OnCustomUninstall() {}
void SodaJaJpComponentInstallerPolicy::ComponentReady(
const base::Version& version,
const base::FilePath& install_dir,
std::unique_ptr<base::DictionaryValue> manifest) {
VLOG(1) << "Component ready, version " << version.GetString() << " in "
<< install_dir.value();
on_component_ready_callback_.Run(install_dir);
}
base::FilePath SodaJaJpComponentInstallerPolicy::GetRelativeInstallDir() const {
return base::FilePath(speech::kSodaJaJpInstallationRelativePath);
}
void SodaJaJpComponentInstallerPolicy::GetHash(
std::vector<uint8_t>* hash) const {
hash->assign(kSodaJaJpPublicKeySHA256,
kSodaJaJpPublicKeySHA256 + base::size(kSodaJaJpPublicKeySHA256));
}
std::string SodaJaJpComponentInstallerPolicy::GetName() const {
return kSodaJaJpManifestName;
}
update_client::InstallerAttributes
SodaJaJpComponentInstallerPolicy::GetInstallerAttributes() const {
return update_client::InstallerAttributes();
}
std::vector<std::string> SodaJaJpComponentInstallerPolicy::GetMimeTypes()
const {
return std::vector<std::string>();
}
void UpdateSodaJaJpInstallDirPref(PrefService* prefs,
const base::FilePath& install_dir) {
#if !defined(OS_ANDROID)
prefs->SetFilePath(
prefs::kSodaJaJpConfigPath,
install_dir.Append(speech::kSodaLanguagePackDirectoryRelativePath));
#endif
}
void RegisterSodaJaJpComponent(ComponentUpdateService* cus,
PrefService* prefs,
base::OnceClosure callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto installer = base::MakeRefCounted<ComponentInstaller>(
std::make_unique<SodaJaJpComponentInstallerPolicy>(base::BindRepeating(
[](ComponentUpdateService* cus, PrefService* prefs,
const base::FilePath& install_dir) {
content::GetUIThreadTaskRunner({base::TaskPriority::BEST_EFFORT})
->PostTask(FROM_HERE,
base::BindOnce(&UpdateSodaJaJpInstallDirPref,
prefs, install_dir));
},
cus, prefs)));
installer->Register(cus, std::move(callback));
}
} // namespace component_updater
// Copyright 2020 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.
#ifndef CHROME_BROWSER_COMPONENT_UPDATER_SODA_JA_JP_COMPONENT_INSTALLER_H_
#define CHROME_BROWSER_COMPONENT_UPDATER_SODA_JA_JP_COMPONENT_INSTALLER_H_
#include <string>
#include "components/component_updater/component_installer.h"
#include "components/prefs/pref_service.h"
#include "components/update_client/update_client.h"
namespace component_updater {
// Success callback to be run after the component is downloaded.
using OnSodaJaJpComponentReadyCallback =
base::RepeatingCallback<void(const base::FilePath&)>;
class SodaJaJpComponentInstallerPolicy : public ComponentInstallerPolicy {
public:
explicit SodaJaJpComponentInstallerPolicy(
OnSodaJaJpComponentReadyCallback callback);
~SodaJaJpComponentInstallerPolicy() override;
SodaJaJpComponentInstallerPolicy(const SodaJaJpComponentInstallerPolicy&) =
delete;
SodaJaJpComponentInstallerPolicy& operator=(
const SodaJaJpComponentInstallerPolicy&) = delete;
static const std::string GetExtensionId();
static void UpdateSodaJaJpComponentOnDemand();
private:
FRIEND_TEST_ALL_PREFIXES(SodaJaJpComponentInstallerTest,
ComponentReady_CallsLambda);
// The following methods override ComponentInstallerPolicy.
bool SupportsGroupPolicyEnabledComponentUpdates() const override;
bool RequiresNetworkEncryption() const override;
update_client::CrxInstaller::Result OnCustomInstall(
const base::DictionaryValue& manifest,
const base::FilePath& install_dir) override;
void OnCustomUninstall() override;
bool VerifyInstallation(const base::DictionaryValue& manifest,
const base::FilePath& install_dir) const override;
void ComponentReady(const base::Version& version,
const base::FilePath& install_dir,
std::unique_ptr<base::DictionaryValue> manifest) override;
base::FilePath GetRelativeInstallDir() const override;
void GetHash(std::vector<uint8_t>* hash) const override;
std::string GetName() const override;
update_client::InstallerAttributes GetInstallerAttributes() const override;
std::vector<std::string> GetMimeTypes() const override;
OnSodaJaJpComponentReadyCallback on_component_ready_callback_;
};
// Call once during startup to make the component update service aware of
// the File Type Policies component.
void RegisterSodaJaJpComponent(ComponentUpdateService* cus,
PrefService* prefs,
base::OnceClosure callback);
} // namespace component_updater
#endif // CHROME_BROWSER_COMPONENT_UPDATER_SODA_JA_JP_COMPONENT_INSTALLER_H_
// Copyright 2020 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/component_updater/soda_ja_jp_component_installer.h"
#include "base/files/file_path.h"
#include "base/test/bind.h"
#include "base/version.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace component_updater {
class SodaJaJpComponentInstallerTest : public ::testing::Test {
public:
SodaJaJpComponentInstallerTest()
: fake_install_dir_(FILE_PATH_LITERAL("base/install/dir/")),
fake_version_("0.0.1") {}
protected:
base::FilePath fake_install_dir_;
base::Version fake_version_;
};
TEST_F(SodaJaJpComponentInstallerTest, ComponentReady_CallsLambda) {
base::FilePath given_path;
OnSodaJaJpComponentReadyCallback lambda = base::BindLambdaForTesting(
[&](const base::FilePath& path) { given_path = path; });
SodaJaJpComponentInstallerPolicy policy(std::move(lambda));
policy.ComponentReady(fake_version_, fake_install_dir_,
std::make_unique<base::DictionaryValue>());
ASSERT_EQ(fake_install_dir_, given_path);
}
} // namespace component_updater
// Copyright 2020 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/component_updater/soda_language_pack_component_installer.h"
#include <iterator>
#include <memory>
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/containers/flat_set.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "base/version.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/component_updater/soda_component_installer.h"
#include "components/component_updater/component_updater_service.h"
#include "components/crx_file/id_util.h"
#include "components/prefs/pref_service.h"
#include "components/soda/constants.h"
#include "components/update_client/update_client_errors.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
namespace component_updater {
namespace {
constexpr char kLanguagePackManifestName[] = "SODA %s Models";
} // namespace
SodaLanguagePackComponentInstallerPolicy::
SodaLanguagePackComponentInstallerPolicy(
SodaLanguagePackComponentConfig language_config,
OnSodaLanguagePackComponentReadyCallback callback)
: language_config_(language_config),
on_component_ready_callback_(callback) {}
SodaLanguagePackComponentInstallerPolicy::
~SodaLanguagePackComponentInstallerPolicy() = default;
std::string SodaLanguagePackComponentInstallerPolicy::GetExtensionId(
speech::LanguageCode language_code) {
base::Optional<SodaLanguagePackComponentConfig> config =
SodaLanguagePackComponentInstallerPolicy::GetLanguageComponentConfig(
language_code);
if (config) {
return crx_file::id_util::GenerateIdFromHash(
config.value().public_key_sha, sizeof(config.value().public_key_sha));
}
return std::string();
}
base::flat_set<std::string>
SodaLanguagePackComponentInstallerPolicy::GetExtensionIds() {
base::flat_set<std::string> ids;
for (const SodaLanguagePackComponentConfig& config :
kLanguageComponentConfigs) {
ids.insert(crx_file::id_util::GenerateIdFromHash(
config.public_key_sha, sizeof(config.public_key_sha)));
}
return ids;
}
void SodaLanguagePackComponentInstallerPolicy::
UpdateSodaLanguagePackComponentOnDemand(
speech::LanguageCode language_code) {
const std::string crx_id =
SodaLanguagePackComponentInstallerPolicy::GetExtensionId(language_code);
g_browser_process->component_updater()->GetOnDemandUpdater().OnDemandUpdate(
crx_id, OnDemandUpdater::Priority::FOREGROUND,
base::BindOnce([](update_client::Error error) {
if (error != update_client::Error::NONE &&
error != update_client::Error::UPDATE_IN_PROGRESS)
LOG(ERROR)
<< "On demand update of the SODA language component failed "
"with error: "
<< static_cast<int>(error);
}));
}
base::Optional<SodaLanguagePackComponentConfig>
SodaLanguagePackComponentInstallerPolicy::GetLanguageComponentConfig(
speech::LanguageCode language_code) {
for (const SodaLanguagePackComponentConfig& config :
kLanguageComponentConfigs) {
if (config.language_code == language_code) {
return config;
}
}
return base::nullopt;
}
base::Optional<SodaLanguagePackComponentConfig>
SodaLanguagePackComponentInstallerPolicy::GetLanguageComponentConfig(
const std::string& language_name) {
for (const SodaLanguagePackComponentConfig& config :
kLanguageComponentConfigs) {
if (config.language_name == language_name) {
return config;
}
}
return base::nullopt;
}
bool SodaLanguagePackComponentInstallerPolicy::VerifyInstallation(
const base::DictionaryValue& manifest,
const base::FilePath& install_dir) const {
return base::PathExists(
install_dir.Append(speech::kSodaLanguagePackDirectoryRelativePath));
}
bool SodaLanguagePackComponentInstallerPolicy::
SupportsGroupPolicyEnabledComponentUpdates() const {
return true;
}
bool SodaLanguagePackComponentInstallerPolicy::RequiresNetworkEncryption()
const {
return true;
}
update_client::CrxInstaller::Result
SodaLanguagePackComponentInstallerPolicy::OnCustomInstall(
const base::DictionaryValue& manifest,
const base::FilePath& install_dir) {
return SodaComponentInstallerPolicy::SetComponentDirectoryPermission(
install_dir);
}
void SodaLanguagePackComponentInstallerPolicy::OnCustomUninstall() {}
void SodaLanguagePackComponentInstallerPolicy::ComponentReady(
const base::Version& version,
const base::FilePath& install_dir,
std::unique_ptr<base::DictionaryValue> manifest) {
VLOG(1) << "Component ready, version " << version.GetString() << " in "
<< install_dir.value();
on_component_ready_callback_.Run(install_dir);
}
base::FilePath SodaLanguagePackComponentInstallerPolicy::GetRelativeInstallDir()
const {
return base::FilePath(speech::kSodaLanguagePacksRelativePath)
.AppendASCII(language_config_.language_name);
}
void SodaLanguagePackComponentInstallerPolicy::GetHash(
std::vector<uint8_t>* hash) const {
hash->assign(std::begin(language_config_.public_key_sha),
std::end(language_config_.public_key_sha));
}
std::string SodaLanguagePackComponentInstallerPolicy::GetName() const {
return base::StringPrintf(kLanguagePackManifestName,
language_config_.language_name);
}
update_client::InstallerAttributes
SodaLanguagePackComponentInstallerPolicy::GetInstallerAttributes() const {
return update_client::InstallerAttributes();
}
std::vector<std::string>
SodaLanguagePackComponentInstallerPolicy::GetMimeTypes() const {
return std::vector<std::string>();
}
void UpdateSodaLanguagePackInstallDirPref(speech::LanguageCode language_code,
PrefService* prefs,
const base::FilePath& install_dir) {
#if !defined(OS_ANDROID)
base::Optional<SodaLanguagePackComponentConfig> config =
SodaLanguagePackComponentInstallerPolicy::GetLanguageComponentConfig(
language_code);
if (config) {
prefs->SetFilePath(
config.value().config_path_pref,
install_dir.Append(speech::kSodaLanguagePackDirectoryRelativePath));
}
#endif
}
void RegisterSodaLanguagePackComponent(
SodaLanguagePackComponentConfig language_config,
ComponentUpdateService* cus,
PrefService* prefs) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto installer = base::MakeRefCounted<ComponentInstaller>(
std::make_unique<SodaLanguagePackComponentInstallerPolicy>(
language_config,
base::BindRepeating(
[](SodaLanguagePackComponentConfig language_config,
ComponentUpdateService* cus, PrefService* prefs,
const base::FilePath& install_dir) {
content::GetUIThreadTaskRunner(
{base::TaskPriority::BEST_EFFORT})
->PostTask(
FROM_HERE,
base::BindOnce(&UpdateSodaLanguagePackInstallDirPref,
language_config.language_code, prefs,
install_dir));
},
language_config, cus, prefs)));
installer->Register(
cus, base::BindOnce(&SodaLanguagePackComponentInstallerPolicy::
UpdateSodaLanguagePackComponentOnDemand,
language_config.language_code));
}
} // namespace component_updater
// Copyright 2020 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.
#ifndef CHROME_BROWSER_COMPONENT_UPDATER_SODA_LANGUAGE_PACK_COMPONENT_INSTALLER_H_
#define CHROME_BROWSER_COMPONENT_UPDATER_SODA_LANGUAGE_PACK_COMPONENT_INSTALLER_H_
#include <string>
#include <vector>
#include "base/callback.h"
#include "base/containers/flat_set.h"
#include "base/optional.h"
#include "chrome/common/pref_names.h"
#include "components/component_updater/component_installer.h"
#include "components/soda/constants.h"
#include "components/update_client/update_client.h"
class PrefService;
namespace base {
class FilePath;
} // namespace base
namespace component_updater {
// Success callback to be run after the component is downloaded.
using OnSodaLanguagePackComponentReadyCallback =
base::RepeatingCallback<void(const base::FilePath&)>;
// Describes all metadata needed to dynamically install ChromeOS components.
struct SodaLanguagePackComponentConfig {
// The language code of the language pack component.
speech::LanguageCode language_code = speech::LanguageCode::kNone;
// The language name for the language component (e.g. "en-US").
const char* language_name = nullptr;
// The name of the config file path pref for the language pack.
const char* config_path_pref = nullptr;
// The SHA256 of the SubjectPublicKeyInfo used to sign the language pack
// component.
const uint8_t public_key_sha[32] = {};
};
constexpr SodaLanguagePackComponentConfig kLanguageComponentConfigs[] = {
{speech::LanguageCode::kEnUs,
"en-US",
prefs::kSodaEnUsConfigPath,
{0xe4, 0x64, 0x1c, 0xc2, 0x8c, 0x2a, 0x97, 0xa7, 0x16, 0x61, 0xbd,
0xa9, 0xbe, 0xe6, 0x93, 0x56, 0xf5, 0x05, 0x33, 0x9b, 0x8b, 0x0b,
0x02, 0xe2, 0x6b, 0x7e, 0x6c, 0x40, 0xa1, 0xd2, 0x7e, 0x18}},
{speech::LanguageCode::kDeDe,
"de-DE",
prefs::kSodaDeDeConfigPath,
{0x92, 0xb6, 0xd8, 0xa3, 0x0b, 0x09, 0xce, 0x21, 0xdb, 0x68, 0x48,
0x15, 0xcb, 0x49, 0xd7, 0xc6, 0x21, 0x3f, 0xe5, 0x96, 0x10, 0x97,
0x6e, 0x0f, 0x08, 0x31, 0xec, 0xe4, 0x7f, 0xed, 0xef, 0x3d}},
{speech::LanguageCode::kEsEs,
"es-ES",
prefs::kSodaEsEsConfigPath,
{0x9a, 0x22, 0xac, 0x04, 0x97, 0xc1, 0x70, 0x61, 0x24, 0x1f, 0x49,
0x18, 0x72, 0xd8, 0x67, 0x31, 0x72, 0x7a, 0xf9, 0x77, 0x04, 0xf0,
0x17, 0xb5, 0xfe, 0x88, 0xac, 0x60, 0xdd, 0x8a, 0x67, 0xdd}},
{speech::LanguageCode::kFrFr,
"fr-FR",
prefs::kSodaFrFrConfigPath,
{0x6e, 0x0e, 0x2b, 0xd3, 0xc6, 0xe5, 0x1b, 0x5e, 0xfa, 0xef, 0x42,
0x3f, 0x57, 0xb9, 0x2b, 0x13, 0x56, 0x47, 0x58, 0xdb, 0x76, 0x89,
0x71, 0xeb, 0x1f, 0xed, 0x48, 0x6c, 0xac, 0xd5, 0x31, 0xa0}},
{speech::LanguageCode::kItIt,
"it-IT",
prefs::kSodaItItConfigPath,
{0x97, 0x45, 0xd7, 0xbc, 0xf0, 0x61, 0x24, 0xb3, 0x0e, 0x13, 0xf2,
0x97, 0xaa, 0xd5, 0x9e, 0x78, 0xa5, 0x81, 0x35, 0x75, 0xb5, 0x9d,
0x3b, 0xbb, 0xde, 0xba, 0x0e, 0xf7, 0xf0, 0x48, 0x56, 0x01}},
{speech::LanguageCode::kJaJp,
"ja-JP",
prefs::kSodaJaJpConfigPath,
{0xed, 0x7f, 0x96, 0xa5, 0x60, 0x9c, 0xaa, 0x4d, 0x80, 0xe5, 0xb8,
0x26, 0xea, 0xf0, 0x41, 0x50, 0x09, 0x52, 0xa4, 0xb3, 0x1e, 0x6a,
0x8e, 0x24, 0x99, 0xde, 0x51, 0x14, 0xc4, 0x3c, 0xfa, 0x48}},
};
class SodaLanguagePackComponentInstallerPolicy
: public ComponentInstallerPolicy {
public:
SodaLanguagePackComponentInstallerPolicy(
SodaLanguagePackComponentConfig language_config,
OnSodaLanguagePackComponentReadyCallback callback);
~SodaLanguagePackComponentInstallerPolicy() override;
SodaLanguagePackComponentInstallerPolicy(
const SodaLanguagePackComponentInstallerPolicy&) = delete;
SodaLanguagePackComponentInstallerPolicy& operator=(
const SodaLanguagePackComponentInstallerPolicy&) = delete;
static std::string GetExtensionId(speech::LanguageCode language_code);
static base::flat_set<std::string> GetExtensionIds();
static void UpdateSodaLanguagePackComponentOnDemand(
speech::LanguageCode language_code);
static base::Optional<SodaLanguagePackComponentConfig>
GetLanguageComponentConfig(speech::LanguageCode language_code);
static base::Optional<SodaLanguagePackComponentConfig>
GetLanguageComponentConfig(const std::string& language_name);
private:
FRIEND_TEST_ALL_PREFIXES(SodaLanguagePackComponentInstallerTest,
ComponentReady_CallsLambda);
// The following methods override ComponentInstallerPolicy.
bool SupportsGroupPolicyEnabledComponentUpdates() const override;
bool RequiresNetworkEncryption() const override;
update_client::CrxInstaller::Result OnCustomInstall(
const base::DictionaryValue& manifest,
const base::FilePath& install_dir) override;
void OnCustomUninstall() override;
bool VerifyInstallation(const base::DictionaryValue& manifest,
const base::FilePath& install_dir) const override;
void ComponentReady(const base::Version& version,
const base::FilePath& install_dir,
std::unique_ptr<base::DictionaryValue> manifest) override;
base::FilePath GetRelativeInstallDir() const override;
void GetHash(std::vector<uint8_t>* hash) const override;
std::string GetName() const override;
update_client::InstallerAttributes GetInstallerAttributes() const override;
std::vector<std::string> GetMimeTypes() const override;
SodaLanguagePackComponentConfig language_config_;
OnSodaLanguagePackComponentReadyCallback on_component_ready_callback_;
};
void RegisterSodaLanguagePackComponent(
SodaLanguagePackComponentConfig language_config,
ComponentUpdateService* cus,
PrefService* prefs);
} // namespace component_updater
#endif // CHROME_BROWSER_COMPONENT_UPDATER_SODA_LANGUAGE_PACK_COMPONENT_INSTALLER_H_
......@@ -2,18 +2,20 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/component_updater/soda_en_us_component_installer.h"
#include "chrome/browser/component_updater/soda_language_pack_component_installer.h"
#include "base/files/file_path.h"
#include "base/test/bind.h"
#include "base/values.h"
#include "base/version.h"
#include "components/soda/constants.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace component_updater {
class SodaEnUsComponentInstallerTest : public ::testing::Test {
class SodaLanguagePackComponentInstallerTest : public ::testing::Test {
public:
SodaEnUsComponentInstallerTest()
SodaLanguagePackComponentInstallerTest()
: fake_install_dir_(FILE_PATH_LITERAL("base/install/dir/")),
fake_version_("0.0.1") {}
......@@ -22,12 +24,13 @@ class SodaEnUsComponentInstallerTest : public ::testing::Test {
base::Version fake_version_;
};
TEST_F(SodaEnUsComponentInstallerTest, ComponentReady_CallsLambda) {
TEST_F(SodaLanguagePackComponentInstallerTest, ComponentReady_CallsLambda) {
base::FilePath given_path;
OnSodaEnUsComponentReadyCallback lambda = base::BindLambdaForTesting(
OnSodaLanguagePackComponentReadyCallback lambda = base::BindLambdaForTesting(
[&](const base::FilePath& path) { given_path = path; });
SodaEnUsComponentInstallerPolicy policy(std::move(lambda));
SodaLanguagePackComponentConfig config {speech::LanguageCode::kEnUs};
SodaLanguagePackComponentInstallerPolicy policy(config, std::move(lambda));
policy.ComponentReady(fake_version_, fake_install_dir_,
std::make_unique<base::DictionaryValue>());
......
......@@ -5,6 +5,7 @@
#include "chrome/browser/speech/speech_recognition_service.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/component_updater/soda_language_pack_component_installer.h"
#include "chrome/browser/service_sandbox_type.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
......@@ -101,17 +102,14 @@ void SpeechRecognitionService::LaunchIfNotRunning() {
}
base::FilePath SpeechRecognitionService::GetSodaConfigPath(PrefService* prefs) {
speech::LanguageCode language = speech::GetLanguageCode(
prefs->GetString(prefs::kLiveCaptionLanguageCode));
PrefService* global_prefs = g_browser_process->local_state();
switch (language) {
case speech::LanguageCode::kNone:
NOTREACHED();
return base::FilePath();
case speech::LanguageCode::kEnUs:
return global_prefs->GetFilePath(prefs::kSodaEnUsConfigPath);
case speech::LanguageCode::kJaJp:
return global_prefs->GetFilePath(prefs::kSodaJaJpConfigPath);
base::Optional<component_updater::SodaLanguagePackComponentConfig>
language_config = component_updater::
SodaLanguagePackComponentInstallerPolicy::GetLanguageComponentConfig(
prefs->GetString(prefs::kLiveCaptionLanguageCode));
if (language_config) {
return g_browser_process->local_state()->GetFilePath(
language_config.value().config_path_pref);
}
return base::FilePath();
......
......@@ -1242,6 +1242,22 @@ const char kSodaEnUsConfigPath[] =
const char kSodaJaJpConfigPath[] =
"accessibility.captions.soda_ja_jp_config_path";
// The file path of the de-DE Speech On-Device API (SODA) configuration file.
const char kSodaDeDeConfigPath[] =
"accessibility.captions.soda_de_de_config_path";
// The file path of the es-ES Speech On-Device API (SODA) configuration file.
const char kSodaEsEsConfigPath[] =
"accessibility.captions.soda_es_es_config_path";
// The file path of the fr-FR Speech On-Device API (SODA) configuration file.
const char kSodaFrFrConfigPath[] =
"accessibility.captions.soda_fr_fr_config_path";
// The file path of the it-IT Speech On-Device API (SODA) configuration file.
const char kSodaItItConfigPath[] =
"accessibility.captions.soda_it_it_config_path";
// The scheduled time to clean up the Speech On-Device API (SODA) files from the
// device.
const char kSodaScheduledDeletionTime[] =
......
......@@ -196,6 +196,10 @@ extern const char kLiveCaptionLanguageCode[];
extern const char kSodaBinaryPath[];
extern const char kSodaEnUsConfigPath[];
extern const char kSodaJaJpConfigPath[];
extern const char kSodaDeDeConfigPath[];
extern const char kSodaEsEsConfigPath[];
extern const char kSodaFrFrConfigPath[];
extern const char kSodaItItConfigPath[];
extern const char kSodaScheduledDeletionTime[];
#endif
#if defined(OS_MAC)
......
......@@ -4045,8 +4045,7 @@ test("unit_tests") {
"../browser/browsing_data/access_context_audit_service_unittest.cc",
"../browser/browsing_data/chrome_browsing_data_lifetime_manager_unittest.cc",
"../browser/component_updater/soda_component_installer_unittest.cc",
"../browser/component_updater/soda_en_us_component_installer_unittest.cc",
"../browser/component_updater/soda_ja_jp_component_installer_unittest.cc",
"../browser/component_updater/soda_language_pack_component_installer_unittest.cc",
"../browser/content_settings/generated_cookie_prefs_unittest.cc",
"../browser/content_settings/generated_notification_pref_unittest.cc",
"../browser/device_identity/device_oauth2_token_service_unittest.cc",
......
......@@ -172,8 +172,7 @@ class OnDemandUpdater {
friend class ::PluginObserver;
friend class SwReporterOnDemandFetcher;
friend class SodaComponentInstallerPolicy;
friend class SodaEnUsComponentInstallerPolicy;
friend class SodaJaJpComponentInstallerPolicy;
friend class SodaLanguagePackComponentInstallerPolicy;
#if BUILDFLAG(IS_CHROMEOS_ASH)
friend class CrOSComponentInstaller;
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
......
......@@ -26,12 +26,6 @@ constexpr base::FilePath::CharType kSodaInstallationRelativePath[] =
constexpr base::FilePath::CharType kSodaLanguagePacksRelativePath[] =
FILE_PATH_LITERAL("SODALanguagePacks");
constexpr base::FilePath::CharType kSodaEnUsInstallationRelativePath[] =
FILE_PATH_LITERAL("SODALanguagePacks/en-US");
constexpr base::FilePath::CharType kSodaJaJpInstallationRelativePath[] =
FILE_PATH_LITERAL("SODALanguagePacks/ja-JP");
constexpr base::FilePath::CharType kSodaLanguagePackDirectoryRelativePath[] =
FILE_PATH_LITERAL("SODAModels");
......@@ -74,36 +68,4 @@ const base::FilePath GetSodaBinaryPath() {
: soda_dir.Append(kSodaBinaryRelativePath);
}
LanguageCode GetLanguageCode(std::string language) {
if (language.empty()) {
return LanguageCode::kNone;
}
if (language == "en-US") {
return LanguageCode::kEnUs;
}
if (language == "ja-JP") {
return LanguageCode::kJaJp;
}
NOTREACHED();
return LanguageCode::kNone;
}
std::vector<base::FilePath> GetSodaLanguagePackDirectories() {
std::vector<base::FilePath> paths;
base::FilePath components_dir;
base::PathService::Get(component_updater::DIR_COMPONENT_USER,
&components_dir);
if (!components_dir.empty()) {
paths.push_back(components_dir.Append(kSodaEnUsInstallationRelativePath));
paths.push_back(components_dir.Append(kSodaJaJpInstallationRelativePath));
}
return paths;
}
} // namespace speech
......@@ -13,6 +13,10 @@ enum class LanguageCode {
kNone = 0,
kEnUs = 1,
kJaJp = 2,
kDeDe = 3,
kEsEs = 4,
kFrFr = 5,
kItIt = 6,
};
// Location of the libsoda binary within the SODA installation directory.
......@@ -25,14 +29,6 @@ extern const base::FilePath::CharType kSodaInstallationRelativePath[];
// directory.
extern const base::FilePath::CharType kSodaLanguagePacksRelativePath[];
// Location of the SODA en-US language pack component relative to the components
// directory.
extern const base::FilePath::CharType kSodaEnUsInstallationRelativePath[];
// Location of the SODA ja-JP language pack component relative to the components
// directory.
extern const base::FilePath::CharType kSodaJaJpInstallationRelativePath[];
// Location of the SODA models directory relative to the language pack
// installation directory.
extern const base::FilePath::CharType kSodaLanguagePackDirectoryRelativePath[];
......@@ -54,11 +50,6 @@ const base::FilePath GetLatestSodaDirectory();
// installed.
const base::FilePath GetSodaBinaryPath();
LanguageCode GetLanguageCode(std::string language);
// Gets a collection of paths to SODA language pack directories.
std::vector<base::FilePath> GetSodaLanguagePackDirectories();
} // namespace speech
#endif // COMPONENTS_SODA_CONSTANTS_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment