// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMEOS_ASH_COMPONENTS_LANGUAGE_PACKS_LANGUAGE_PACK_MANAGER_H_
#define CHROMEOS_ASH_COMPONENTS_LANGUAGE_PACKS_LANGUAGE_PACK_MANAGER_H_
#include <optional>
#include <string>
#include <string_view>
#include "base/containers/flat_map.h"
#include "base/functional/callback.h"
#include "base/observer_list.h"
#include "base/scoped_observation.h"
#include "base/sequence_checker.h"
#include "base/strings/strcat.h"
#include "chromeos/ash/components/dbus/dlcservice/dlcservice_client.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_service.h"
#include "ui/base/ime/ash/input_method_util.h"
class PrefService;
namespace ash::language_packs {
// All Language Pack IDs are listed here.
inline constexpr char kHandwritingFeatureId[] = "LP_ID_HANDWRITING";
inline constexpr char kTtsFeatureId[] = "LP_ID_TTS";
inline constexpr char kFontsFeatureId[] = "LP_ID_FONT";
// Feature IDs.
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
// See enum LanguagePackFeatureIds in tools/metrics/histograms/enums.xml.
enum class FeatureIdsEnum {
kUnknown = 0,
kHandwriting = 1,
kTts = 2,
kFonts = 3,
kMaxValue = kFonts,
};
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
// See enum LanguagePackFeatureSuccess in tools/metrics/histograms/enums.xml.
enum class FeatureSuccessEnum {
kUnknownSuccess = 0,
kUnknownFailure = 1,
kHandwritingSuccess = 2,
kHandwritingFailure = 3,
kTtsSuccess = 4,
kTtsFailure = 5,
kFontsSuccess = 6,
kFontsFailure = 7,
kMaxValue = kFontsFailure,
};
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
// See enum LanguagePackDlcErrorType in tools/metrics/histograms/enums.xml.
enum class DlcErrorTypeEnum {
kErrorUnknown = 0,
kErrorNone = 1,
kErrorInternal = 2,
kErrorBusy = 3,
kErrorNeedReboot = 4,
kErrorInvalidDlc = 5,
kErrorAllocation = 6,
kErrorNoImageFound = 7,
kMaxValue = kErrorNoImageFound,
};
// Status contains information about the status of a Language Pack operation.
struct PackResult {
// Needed for Complex type checker.
PackResult();
~PackResult();
PackResult(const PackResult&);
enum class StatusCode {
kUnknown = 0,
kNotInstalled,
kInProgress,
kInstalled
};
enum class ErrorCode {
kNone = 0,
kOther,
kWrongId,
kNeedReboot,
kAllocation
};
// The code that indicates the current state of the Pack.
// kInstalled means that the Pack is ready to be used.
// If there's any error during the operation, we set status to kUnknown.
StatusCode pack_state;
// If there is any error in the operation that is requested, it is indicated
// here.
ErrorCode operation_error;
// The feature ID of the pack.
std::string feature_id;
// The resolved language code that this Pack is associated with.
// Often this field matches the locale requested by the client, but due to
// various mappings between languages, regions and variants, it might be
// different.
// This is set only if the input locale is valid; undetermined otherwise.
std::string language_code;
// The path where the Pack is available for users to use.
std::string path;
};
// We define an internal type to identify a Language Pack.
// It's a pair of featured_id and locale that is hashable.
struct PackSpecPair {
std::string feature_id;
std::string locale;
PackSpecPair(std::string feature_id, std::string locale)
: feature_id(std::move(feature_id)), locale(std::move(locale)) {}
bool operator==(const PackSpecPair& other) const {
return (feature_id == other.feature_id && locale == other.locale);
}
bool operator!=(const PackSpecPair& other) const { return !(*this == other); }
// Allows PackSpecPair to be used as a key in STL containers, like flat_map.
bool operator<(const PackSpecPair& other) const {
if (feature_id == other.feature_id) {
return locale < other.locale;
}
return feature_id < other.feature_id;
}
// Simple hash function: XOR the string hash.
struct HashFunction {
size_t operator()(const PackSpecPair& obj) const {
size_t first_hash = std::hash<std::string>()(obj.feature_id);
size_t second_hash = std::hash<std::string>()(obj.locale) << 1;
return first_hash ^ second_hash;
}
};
};
// Returns a static mapping from `PackSpecPair`s to DLC IDs.
// Internal only, do not use - this function will likely be removed in the
// future.
const base::flat_map<PackSpecPair, std::string>& GetAllLanguagePackDlcIds();
// Finds the ID of the DLC corresponding to the given spec.
// Returns the DLC ID if the DLC exists or std::nullopt otherwise.
std::optional<std::string> GetDlcIdForLanguagePack(
const std::string& feature_id,
const std::string& locale);
using OnInstallCompleteCallback =
base::OnceCallback<void(const PackResult& pack_result)>;
using GetPackStateCallback =
base::OnceCallback<void(const PackResult& pack_result)>;
using OnUninstallCompleteCallback =
base::OnceCallback<void(const PackResult& pack_result)>;
using OnInstallBasePackCompleteCallback =
base::OnceCallback<void(const PackResult& pack_result)>;
using OnUpdatePacksForOobeCallback =
base::OnceCallback<void(const PackResult& pack_result)>;
// This class manages all Language Packs and their dependencies (called Base
// Packs) on the device.
// This is a Singleton and needs to be accessed via Get().
//
// Sequencing: This class is sequence-checked so all accesses to it - non-static
// methods, `Initialise()` and `Shutdown()` - should be done on the same
// sequence. This may be overly strict, see b/319906094 for more details.
class LanguagePackManager : public DlcserviceClient::Observer {
public:
// Observer of Language Packs.
// TODO(crbug.com/1194688): Make the Observers dependent on feature and
// locale, so that clients don't get notified for things they are not
// interested in.
class Observer : public base::CheckedObserver {
public:
// Called whenever the state of a Language Pack changes, which includes
// installation, download, removal or errors.
virtual void OnPackStateChanged(const PackResult& pack_result) = 0;
};
// Do not use unless in tests.
// Only one `LanguagePackManager` can be instantiated at any time.
// Use `GetInstance()` instead to obtain the currently instantiated instance,
// likely instantiated by `Initialise()`.
LanguagePackManager();
// Disallow copy and assign.
LanguagePackManager(const LanguagePackManager&) = delete;
LanguagePackManager& operator=(const LanguagePackManager&) = delete;
~LanguagePackManager() override;
// Returns true if the given Language Pack exists and can be installed on
// this device.
// TODO(claudiomagni): Check per board.
static bool IsPackAvailable(const std::string& feature_id,
const std::string& locale);
// Installs the Language Pack.
// It takes a callback that will be triggered once the operation is done.
// A state is passed to the callback.
static void InstallPack(const std::string& feature_id,
const std::string& locale,
OnInstallCompleteCallback callback);
// Checks the state of a Language Pack.
// It takes a callback that will be triggered once the operation is done.
// A state is passed to the callback.
// If the state marks the Language Pack as ready, then there's no need to
// call Install(), otherwise the client should call Install() and not call
// this method a second time.
// This will automatically mount the DLC if it exists on disk (is_verified),
// and return a PackState of kInstalled.
static void GetPackState(const std::string& feature_id,
const std::string& locale,
GetPackStateCallback callback);
// Features should call this method to indicate that they do not intend to
// use the Pack again, until they will call |InstallPack()|.
// The Language Pack will be removed from disk, but no guarantee is given on
// when that will happen.
// TODO(claudiomagni): Allow callers to force immediate removal. Useful to
// clear space on disk for another language.
static void RemovePack(const std::string& feature_id,
const std::string& locale,
OnUninstallCompleteCallback callback);
// Explicitly installs the base pack for |feature_id|.
static void InstallBasePack(const std::string& feature_id,
OnInstallBasePackCompleteCallback callback);
// Installs relevant language packs during OOBE.
// This method should only be called during OOBE and will do nothing if called
// outside it.
static void UpdatePacksForOobe(const std::string& locale,
OnUpdatePacksForOobeCallback callback);
// Registers itself as an Observer of all the relevant languages Prefs.
void ObservePrefs(PrefService* pref_service);
// Adds an observer to the observer list.
void AddObserver(Observer* observer);
// Removes an observer from the observer list.
void RemoveObserver(Observer* observer);
// Initialises the global instance. This is typically called from
// ash_dbus_helper.h's `InitializeDBus()`, which is called from
// `ChromeMainDelegate::PostEarlyInitialization()`.
// Cannot be called multiple times - `GetInstance()` must return `nullptr`
// before this static method is called.
// Requires the global `DlcserviceClient` to be initialised.
// Do not use this in tests, instantiate a test-local `LanguagePackManager`
// instead.
static void Initialise();
// Shuts down the global instance. This is typically called from
// ash_dbus_helper.h's `ShutdownDBus()`, which is called from
// `ChromeBrowserMainPartsAsh::PostDestroyThreads()`.
// Cannot be called multiple times - `GetInstance()` must return a non-null
// pointer before this static method is called.
// The global `DlcserviceClient` at the time of initialisation must still
// exist when this is called.
// Do not use this in tests, the destructor of the test-local
// `LanguagePackManager` will correctly unset the currently instantiated
// instance.
static void Shutdown();
// Returns the currently instantiated instance. This is typically the global
// instance, but may be a test-local `LanguagePackManager` during tests.
static LanguagePackManager* GetInstance();
private:
// Retrieves the list of installed DLCs and updates Packs accordingly.
// This function should be called when LPM initializes and then each time
// Prefs change.
static void CheckAndUpdateDlcsForInputMethods(PrefService* pref_service);
// DlcserviceClient::Observer overrides.
void OnDlcStateChanged(const dlcservice::DlcState& dlc_state) override;
// Notification method called upon change of DLCs state.
void NotifyPackStateChanged(std::string_view feature_id,
std::string_view locale,
const dlcservice::DlcState& dlc_state)
VALID_CONTEXT_REQUIRED(sequence_checker_);
SEQUENCE_CHECKER(sequence_checker_);
base::ObserverList<Observer> observers_;
base::ScopedObservation<DlcserviceClient, DlcserviceClient::Observer> obs_{
this};
PrefChangeRegistrar pref_change_registrar_;
};
} // namespace ash::language_packs
#endif // CHROMEOS_ASH_COMPONENTS_LANGUAGE_PACKS_LANGUAGE_PACK_MANAGER_H_