// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_ASH_GUEST_OS_GUEST_OS_REGISTRY_SERVICE_H_
#define CHROME_BROWSER_ASH_GUEST_OS_GUEST_OS_REGISTRY_SERVICE_H_
#include <map>
#include <set>
#include <string>
#include <string_view>
#include <vector>
#include "base/files/file_path.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/values.h"
#include "chrome/browser/apps/app_service/app_icon/app_icon_factory.h"
#include "chrome/browser/ash/crostini/crostini_simple_types.h"
#include "chrome/browser/ash/guest_os/guest_id.h"
#include "chrome/browser/ash/guest_os/guest_os_external_protocol_handler.h"
#include "chrome/browser/ash/guest_os/public/types.h"
#include "chromeos/ash/components/dbus/vm_applications/apps.pb.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/services/app_service/public/cpp/icon_types.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/resource/resource_scale_factor.h"
class Profile;
class PrefService;
namespace base {
class Clock;
class Time;
} // namespace base
namespace apps {
class SvgIconTranscoder;
} // namespace apps
namespace vm_tools {
namespace apps {
class ApplicationList;
} // namespace apps
} // namespace vm_tools
namespace guest_os {
using IconContentCallback = base::OnceCallback<void(std::string)>;
using CanHandleUrlCallback = base::RepeatingCallback<bool(const GURL&)>;
// The GuestOsRegistryService stores information about Desktop Entries (apps)
// in Crostini. We store this in prefs so that it is readily available even when
// the VM isn't running. The registrations here correspond to .desktop files,
// which are detailed in the spec:
// https://www.freedesktop.org/wiki/Specifications/desktop-entry-spec/
// This class deals with several types of IDs, including:
// 1) Desktop File IDs (desktop_file_id):
// - As per the desktop entry spec.
// 2) Crostini App List Ids (app_id):
// - Valid extensions ids for apps stored in the registry, derived from the
// desktop file id, vm name, and container name.
// - The Terminal is a special case, using kCrostiniTerminalId (see below).
// 3) Exo Window App Ids (window_app_id):
// - Retrieved from exo::GetShellApplicationId()
// - For Wayland apps, this is the surface class of the app
// - For X apps, this is of the form org.chromium.termina.wmclass.foo when
// WM_CLASS is set to foo, or otherwise some string prefixed by
// "org.chromium.termina." when WM_CLASS is not set.
// 4) Shelf App Ids (shelf_app_id):
// - Used in ash::ShelfID::app_id
// - Either a Window App Id prefixed by "crostini:" or a Crostini App Id.
// - For pinned apps, this is a Crostini App Id.
// The default Terminal app does not correspond to a desktop file, but users
// of the registry can treat it as a regular app that is always installed.
// Internal to the registry, the pref entry only contains the last launch time
// so some care is required.
class GuestOsRegistryService : public KeyedService {
public:
class Registration {
public:
Registration(const std::string app_id, const base::Value pref);
Registration(Registration&& registration) = default;
Registration& operator=(Registration&& registration) = default;
Registration(const Registration&) = delete;
Registration& operator=(const Registration&) = delete;
~Registration();
std::string app_id() const { return app_id_; }
std::string DesktopFileId() const;
VmType VmType() const;
std::string VmName() const;
std::string ContainerName() const;
std::string Name() const;
std::string Exec() const;
std::string ExecutableFileName() const;
std::set<std::string> Extensions() const;
std::set<std::string> MimeTypes() const;
std::set<std::string> Keywords() const;
bool NoDisplay() const;
bool Terminal() const;
std::string PackageId() const;
base::Time InstallTime() const;
base::Time LastLaunchTime() const;
// Whether this app should scale up when displayed.
bool IsScaled() const;
bool CanUninstall() const;
guest_os::GuestId ToGuestId() const;
std::string StartupWmClass() const;
bool StartupNotify() const;
private:
std::string GetString(std::string_view key) const;
bool GetBool(std::string_view key) const;
base::Time GetTime(std::string_view key) const;
std::string GetLocalizedString(std::string_view key) const;
std::set<std::string> GetLocalizedList(std::string_view key) const;
std::string app_id_;
base::Value pref_;
};
class Observer {
public:
// Called at the end of UpdateApplicationList() with lists of app_ids for
// apps which have been updated, removed, and inserted. Not called when the
// last_launch_time field is updated.
virtual void OnRegistryUpdated(
guest_os::GuestOsRegistryService* registry_service,
VmType vm_type,
const std::vector<std::string>& updated_apps,
const std::vector<std::string>& removed_apps,
const std::vector<std::string>& inserted_apps) {}
// Called at the end of AppLaunched().
virtual void OnAppLastLaunchTimeUpdated(
VmType vm_type,
const std::string& app_id,
const base::Time& last_launch_time) {}
protected:
virtual ~Observer() = default;
};
explicit GuestOsRegistryService(Profile* profile);
GuestOsRegistryService(const GuestOsRegistryService&) = delete;
GuestOsRegistryService& operator=(const GuestOsRegistryService&) = delete;
~GuestOsRegistryService() override;
base::WeakPtr<GuestOsRegistryService> GetWeakPtr();
// Return all installed apps. This always includes the Terminal app.
std::map<std::string, GuestOsRegistryService::Registration>
GetAllRegisteredApps() const;
// Return all installed apps where the VM is enabled.
std::map<std::string, GuestOsRegistryService::Registration> GetEnabledApps()
const;
// Return all installed apps for a given vm.
// If |vm_type == TERMINA_VM| then this includes the Terminal app.
std::map<std::string, GuestOsRegistryService::Registration> GetRegisteredApps(
VmType vm_type) const;
// Return null if `app_id` is not found in the registry.
std::optional<GuestOsRegistryService::Registration> GetRegistration(
const std::string& app_id) const;
// Return the preferred handler for the given URL, if any.
std::optional<GuestOsUrlHandler> GetHandler(const GURL& url) const;
// Register a non-app handler of URLs.
// Handlers registered here take priority over apps (since they come from
// the OS, rather than VMs), and are not persisted to prefs.
// `canHandleCallback` should return true when passed a URL that should be
// handled by `handler`.
void RegisterTransientUrlHandler(GuestOsUrlHandler handler,
CanHandleUrlCallback canHandleCallback);
// Constructs path to app icon for specific scale factor.
base::FilePath GetIconPath(const std::string& app_id,
ui::ResourceScaleFactor scale_factor) const;
// Attempts to load icon in the following order:
// 1/ Loads from resource if |icon_key->resource_id| is valid (non-zero).
// 2/ Looks up file cache.
// 3/ Fetches from VM.
// 4/ Uses |fallback_icon_resource_id| if it is valid (non-zero).
// 5/ Returns empty.
void LoadIcon(const std::string& app_id,
const apps::IconKey& icon_key,
apps::IconType icon_type,
int32_t size_hint_in_dip,
bool allow_placeholder_icon,
int fallback_icon_resource_id,
apps::LoadIconCallback callback);
void LoadIconFromVM(const std::string& app_id,
apps::IconType icon_type,
int32_t size_hint_in_dip,
ui::ResourceScaleFactor scale_factor,
apps::IconEffects icon_effects,
int fallback_icon_resource_id,
apps::LoadIconCallback callback);
void OnLoadIconFromVM(const std::string& app_id,
apps::IconType icon_type,
int32_t size_hint_in_dip,
apps::IconEffects icon_effects,
int fallback_icon_resource_id,
apps::LoadIconCallback callback,
std::string compressed_icon_data);
// Fetches icons from container.
void RequestIcon(const std::string& app_id,
ui::ResourceScaleFactor scale_factor,
IconContentCallback callback);
// Remove all apps from the named VM and container. If |container_name| is an
// empty string, this function removes all apps associated with the VM,
// regardless of container. Used in the uninstall process.
void ClearApplicationList(VmType vm_type,
const std::string& vm_name,
const std::string& container_name);
// Remove all apps from the named container. Used when deleting a container
// without deleting the whole VM.
void ClearApplicationListForContainer(VmType vm_type,
const std::string& vm_name,
const std::string& container_name);
// The existing list of apps is replaced by |application_list|.
void UpdateApplicationList(const vm_tools::apps::ApplicationList& app_list);
// Inform the registry that the badge color for `container_id` has changed. In
// practice, this sends an update notification for all apps associated with
// this container, which will prompt the icons to be regenerated.
void ContainerBadgeColorChanged(const guest_os::GuestId& container_id);
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
// Notify the registry to update the last_launched field.
void AppLaunched(const std::string& app_id);
// Serializes the current time and stores it in |dictionary|.
void SetCurrentTime(base::Value::Dict& dictionary, const char* key) const;
// Set the display scaled setting of the |app_id| to |scaled|.
void SetAppScaled(const std::string& app_id, bool scaled);
void SetClockForTesting(base::Clock* clock) { clock_ = clock; }
// Apply a coloured badge to the app icon if Crostini multi-container
// feature is enabled.
void ApplyContainerBadge(const std::optional<std::string>& app_id,
gfx::ImageSkia* image_skia);
// Returns the AppId that will be used to refer to the given GuestOs
// application.
static std::string GenerateAppId(const std::string& desktop_file_id,
const std::string& vm_name,
const std::string& container_name);
private:
// Construct path to app local data.
base::FilePath GetAppPath(const std::string& app_id) const;
// Called to request an icon from the container.
void RequestContainerAppIcon(const std::string& app_id,
ui::ResourceScaleFactor scale_factor);
// Callback for when we request an icon from the container.
void OnContainerAppIcon(const std::string& app_id,
ui::ResourceScaleFactor scale_factor,
bool success,
const std::vector<crostini::Icon>& icons);
// Removes all the icons installed for an application.
void RemoveAppData(const std::string& app_id);
// Apply container-specific badging to `icon_out`. This is used by
// ApplyContainerBadge.
void ApplyContainerBadgeForImageSkiaIcon(SkColor badge_color,
gfx::ImageSkia* icon_out);
// Apply container-specific badging to `icon` before running the callback.
// This is run after the generic icon loading code.
void ApplyContainerBadgeWithCallback(SkColor badge_color,
apps::LoadIconCallback callback,
apps::IconValuePtr icon);
// Call the callbacks |active_icon_requests_| for |app_id|.
void InvokeActiveIconCallbacks(std::string app_id,
ui::ResourceScaleFactor scale_factor,
std::string icon_content);
// If a valid .svg file is found at |svg_path|, transcode it to png and save
// it to |png_path| and invoke |callback|, otherwise invoke |fallback|.
void TranscodeIconFromSvg(
base::FilePath svg_path,
base::FilePath png_path,
apps::IconType icon_type,
int32_t size_hint_in_dip,
apps::IconEffects icon_effects,
base::OnceCallback<void(apps::LoadIconCallback)> fallback,
apps::LoadIconCallback callback);
// Callback for when a saved container icon is svg and was transcoded.
void OnSvgIconTranscoded(std::string app_id,
ui::ResourceScaleFactor scale_factor,
std::string svg_icon_content,
std::string png_icon_content);
// Owned by the Profile.
const raw_ptr<Profile, DanglingUntriaged> profile_;
const raw_ptr<PrefService, DanglingUntriaged> prefs_;
// Keeps root folder where Crostini app icons for different scale factors are
// stored.
base::FilePath base_icon_path_;
base::ObserverList<Observer>::Unchecked observers_;
raw_ptr<const base::Clock> clock_;
std::vector<std::pair<GuestOsUrlHandler, CanHandleUrlCallback>> url_handlers_;
// Keeps record for icon request to avoid duplication. Each app may contain
// several requests for different scale factors. Scale factor is defined by
// specific bit position. The |active_icon_requests_| holds icon request that
// are in flight. |retry_icon_requests| holds failed requests which we
// should attempt again when we get an app list refresh from the container
// which means there's a good chance the container is online and the request
// will then succeed.
std::map<std::pair<std::string, ui::ResourceScaleFactor>,
std::vector<IconContentCallback>>
active_icon_requests_;
std::map<std::string, uint32_t> retry_icon_requests_;
std::unique_ptr<apps::SvgIconTranscoder> svg_icon_transcoder_;
base::WeakPtrFactory<GuestOsRegistryService> weak_ptr_factory_{this};
};
} // namespace guest_os
#endif // CHROME_BROWSER_ASH_GUEST_OS_GUEST_OS_REGISTRY_SERVICE_H_