chromium/components/services/app_service/README.md

# App Service

Chrome, and especially Chrome OS, has apps, e.g. chat apps and camera apps.

There are a number (lets call it `M`) of different places or app Consumers,
usually UI (user interfaces) related, where users interact with their installed
apps: e.g. launcher, search bar, shelf, New Tab Page, the App Management
settings page, permissions or settings pages, picking and running default
handlers for URLs, MIME types or intents, etc.

There is also a different number (`N`) of app platforms or app Publishers:
built-in apps, extension-backed apps, PWAs (progressive web apps), ARC++
(Android apps), Crostini (Linux apps), etc.

Historically, each of the `M` Consumers hard-coded each of the `N` Publishers,
leading to `M×N` code paths that needed maintaining, or updating every time `M`
or `N` was incremented.

This document describes the App Service, an intermediary between app Consumers
and app Publishers. This simplifies `M×N` code paths to `M+N` code paths, each
side with a uniform API, reducing code duplication and improving behavioral
consistency.

The App Service can be decomposed into a number of aspects. In all cases, it
provides to Consumers a uniform API over the various Publisher implementations,
for these aspects:

  - App Registry: list the installed apps.
  - App Icon Factory: load an app's icon, at various resolutions.
  - App Runner: launch apps and track app instances.
  - App Installer: install, uninstall and update apps.
  - App Coordinator: keep system-wide settings, e.g. default handlers.

Some things are still the responsibility of individual Consumers or Publishers.
For example, the order in which the apps' icons are presented in the launcher
is a launcher-specific detail, not a system-wide detail, and is managed by the
launcher, not the App Service. Similarly, Android-specific VM (Virtual Machine)
configuration is Android-specific, not generalizable system-wide, and is
managed by the Android provider (ARC++).


## Profiles

Talk of *the* App Service is an over-simplification. There will be *an* App
Service instance(AppServiceProxy) per Profile, as apps can be installed for
*a* Profile.

# App Registry

The App Registry's one-liner mission is:

  - I would like to be able to for-each over all the apps in Chrome.

The App Registry design involves three actors (Consumers ⇔ AppRegistryCache
⇔ Publisher), with the AppRegistryCache providing a platform-independent
abstraction over the per-platform implementation details.

The AppRegistryCache is owned by the AppServiceProxy as an implementation
detail. Being able to for-each over all the apps is:

    proxy->AppRegistryCache().ForEachApp([..]() {
      DoSomethingWith(app);
    });

The Proxy is expected to be in the same process as its Consumers, and the Proxy
would be a singleton (per Profile) within that process: Consumers would connect
to *the* in-process Proxy. If all of the app UI code is in the browser process,
the Proxy would also be in the browser process. If app UI code migrated to e.g.
a separate Ash process, then the Proxy would move with them. There might be
multiple Proxies, one per process (per Profile).


## Code Location

Some code is tied to a particular process, some code is not. For example, the
per-Profile `AppServiceProxy` obviously contains Profile-related code (i.e. a
`KeyedService`, so that browser code can find *the* `AppServiceProxy` for a
given Profile) that is tied to being in the browser process. The
`AppServiceProxy` also contains process-agnostic code (code that could
conceivably be used by an `AppServiceProxy` living in an Ash process), such as
code to cache and update the set of known apps (as in, the `App` type).
Specifically, the `AppServiceProxy` code is split into two locations, one under
`//chrome/browser` and one not:

  - `//chrome/browser/apps/app_service`
  - `//components/services/app_service`

Code specific to publishers lives under:

  - `//chrome/browser/apps/app_service/publishers`

Code specific to web application publishers lives under:

  - `//chrome/browser/web_applications/app_service`


## The App Type

The one struct type, `App`, represents both a state, "an app that's ready
to run", and a delta or change in state, "here's what's new about an app".
Deltas include events like "an app was just installed" or "just uninstalled" or
"its icon was updated".

This is achieved by having every `App` field (other than `App.app_type` and
`App.app_id`) be optional.

An `App.readiness` field represents whether an app is installed (i.e. ready to
launch), uninstalled or otherwise disabled. "An app was just installed" is
represented by a delta whose `readiness` is `kReady` and the old state's
`readiness` being some other value. The `AppUpdate` wrapper type (see below)
can provide helper `WasJustInstalled` methods.

The `App`, `Readiness` and `OptionalBool` types are:

    struct App {
      AppType app_type;
      string app_id;

      // The fields above are mandatory. Everything else below is optional.

      Readiness readiness;
      std::optional<std::string> name;
      std::optional<IconKey> icon_key;
      std::optional<bool> show_in_launcher;
      // etc.
    };

    enum Readiness {
      kUnknown = 0,
      kReady,                // Installed and launchable.
      kDisabledByBlocklist,  // Disabled by SafeBrowsing.
      kDisabledByPolicy,     // Disabled by admin policy.
      kDisabledByUser,       // Disabled by explicit user action.
      kUninstalledByUser,
    };

    // struct IconKey is discussed in the "App Icon Factory" section.

A new state can be mechanically computed from an old state and a delta (both of
which have the same type: `App`). Specifically, last known value wins. Any
known field in the delta overwrites the corresponding field in the old state,
any unknown field in the delta is ignored. For example, if an app's name
changed but its icon didn't, the delta's `App.name` field (a
`std::optional<std::string>`) would be known (not `std::nullopt`) and copied
over but its `App.icon` field would be unknown (`std::nullopt`) and not copied
over.

The current state is thus the merger or sum of all previous deltas, including
the initial state being a delta against the ground state of "all unknown". The
`AppServiceProxy` tracks the state of its apps, and implements the
(in-process) Observer pattern so that UI surfaces can e.g. update themselves as
new apps are installed. There's only one method, `OnAppUpdate`, as opposed to
separate `OnAppInstalled`, `OnAppUninstalled`, `OnAppNameChanged`, etc.
methods. An `AppUpdate` is a pair of `App` values: old state and delta.

    class AppRegistryCache {
     public:
      class Observer : public base::CheckedObserver {
       public:
        ~Observer() override {}
        virtual void OnAppUpdate(const AppUpdate& update) = 0;
      };

      // Etc.
    };


## Registering Publisher

Once a `Publisher` registers itself to `AppServiceProxy`, the publisher sends
a stream of `App`s (calling the `OnApps` API method). On the initial
registering, the `Publisher` calls `OnApps` with "here's all the apps that I
(the `Publisher`) know about", with additional `OnApps` calls made as apps are
installed, uninstalled, updated, etc.

The key recipient of these calls is the AppRegistryCache, which coalesces the
updates into AppUpdate objects that are the core way that Consumers of the App
Service query the installed apps on the OS and interact with them.


# App Icon Factory

Icon data (even compressed as a PNG) is bulky, relative to the rest of the
`App` type. `Publisher`s will generally serve icon data lazily, on demand,
especially as the desired icon resolutions (e.g. 64dip or 256dip) aren't known
up-front. Instead of sending an icon at all possible resolutions, the
`Publisher` sends an `IconKey`: enough information to load the icon at given
resolutions.

An `IconKey` augments the `AppType app_type` and `string app_id`. For example,
some icons are statically built into the Chrome or Chrome OS binary, as
PNG-formatted resources, and can be loaded (synchronously, without sandboxing).
They can be loaded from the `IconKey.resource_id`. Other icons are dynamically
(and asynchronously) loaded from the extension database on disk. The base icon
can be loaded just from the `app_id` alone.

In either case, the `IconKey.icon_effects` bitmask holds whether to apply
further image processing effects such as desaturation to gray.

    AppServiceProxy {
      // App Icon Factory methods.
      LoadIcon(
          AppType app_type,
          string app_id,
          IconKey icon_key,
          IconType icon_type,
          int32 size_hint_in_dip,
          bool allow_placeholder_icon) => (IconValue icon_value);

      // Some additional methods; not App Icon Factory related.
    };

    Publisher {
      // App Icon Factory methods.
      LoadIcon(
          string app_id,
          IconKey icon_key,
          IconType icon_type,
          int32 size_hint_in_dip,
          bool allow_placeholder_icon) => (IconValue icon_value);

      // Some additional methods; not App Icon Factory related.
    };

    struct IconKey {
      // A monotonically increasing number so that, after an icon update, a new
      // IconKey, one that is different in terms of field-by-field equality, can be
      // broadcast by a Publisher.
      //
      // The exact value of the number isn't important, only that newer IconKey's
      // (those that were created more recently) have a larger timeline than older
      // IconKey's.
      //
      // This is, in some sense, *a* version number, but the field is not called
      // "version", to avoid any possible confusion that it encodes *the* app's
      // version number, e.g. the "2.3.5" in "FooBar version 2.3.5 is installed".
      //
      // For example, if an app is disabled for some reason (so that its icon is
      // grayed out), this would result in a different timeline even though the
      // app's version is unchanged.
      uint64 timeline;
      // If non-zero, the compressed icon is compiled into the Chromium binary
      // as a statically available, int-keyed resource.
      int32 resource_id;
      // A bitmask of icon post-processing effects, such as desaturation to
      // gray and rounding the corners.
      uint32 icon_effects;
    };

    enum IconType {
      kUnknown,
      kUncompressed,
      kCompressed,
      kStandard,
    };

    struct IconValue {
      IconType icon_type;
      gfx::ImageSkia uncompressed;
      std::vector<uint8_t> compressed;
      bool is_placeholder_icon;
    };


## Icon Changes

Apps can change their icons, e.g. after a new version is installed. From the
App Service's point of view, an icon change is like any other change: Publishers
broadcast an `App` value representing what's changed (icon or otherwise) about
an app, the Proxy's `AppRegistryCache` enriches this `App` struct to be an
`AppUpdate`, and `AppRegistryCache` observers can, if that `AppUpdate` shows
that the icon has changed, issue a new `LoadIcon` call. A new call is
necessary, because a callback is a `base::OnceCallback`, so the same
callback can't be used for both the old and the new icon.


## Caching and Other Optimizations

Grouping the `IconKey` with the other `LoadIcon` arguments, the combination
identifies a static (unchanging, but possibly obsolete) image: if a new version
of an app results in a new icon, or if a change in app state results in a
grayed out icon, this is represented by a different, larger `IconKey.timeline`.
As a consequence, the combined `LoadIcon` arguments can be used to key a cache
or map of `IconValue`s, or to recognize and coalesce multiple concurrent
requests to the same combination.

Such optimizations can be implemented as a series of "wrapper" classes (as in
the classic "decorator" or "wrapper" design pattern) that all implement the
same C++ interface (an `IconLoader` interface). They add their specific feature
(e.g. caching) by wrapping another `IconLoader`, doing feature-specific work on
every call or reply before sending the call forward or the reply backward.

There may be multiple caches, as there may be multiple cache eviction policies
(also known as garbage collection policies), spanning the trade-off from
favoring minimizing memory use to favoring maximizing cache hit rates. The
Proxy may have a single cache, with a relatively aggressive eviction policy,
which applies to all of its Consumer clients. A Consumer might have an
additional Consumer-specific cache, with a more relaxed eviction policy, if it
has additional Consumer-specific UI signals to guide when icon-loading requests
and cache hits are more or less likely.

Note that cache values (the `IconValue` struct) are, primarily, a
gfx.ImageSkia, which are cheap to share. Copying an ImageSkia value does
not duplicate any underlying pixel buffers.

As a separate optimization, if the `AppServiceProxy` knows how to load an icon
for a given `IconKey`, it can skip the round trip and bulk data IPC and load
it directly instead. For example, it may know how to load icons from a
statically built resource ID.


## Placeholder Icons

It can take some time for `Publisher`s to provide an icon. For example, loading
the canonical icon for an ARC++ or Crostini app might require waiting for a VM
to start. Such icons are often cached on the file system, but on a cache miss,
there may be a number of seconds before the system can present an icon. In this
case, we might want to present a `Publisher`-specific placeholder, typically
loaded from a resource (an asset statically compiled into the binary).

There are two boolean fields that facilitate this: `allow_placeholder_icon` is
sent from a `Subscriber` to a `Publisher` and `is_placeholder_icon` is sent in
the response.

`LoadIcon`'s `allow_placeholder_icon` states whether the caller will accept a
placeholder if the real icon can not be provided quickly. Native user
interfaces like the app launcher will probably set this to true. On the other
hand, serving Web-UI URLs such as `chrome://app-icon/app_id/icon_size` will set
this to false, as that URL should identify a particular icon, not one that
changes over time. Web-UI that wants to display placeholder icons and be
notified of when real icons are ready will require some mechanism other than a
`chrome:://app-icon/etc` URL.

`IconValue`'s `is_placeholder_icon` states whether the icon provided is a
placeholder. That field should only be true if the corresponding `LoadIcon`
call had `allow_placeholder_icon` true. When the `LoadIcon` caller receives a
placeholder icon, it is up to the caller to issue a new `LoadIcon` call, this
time with `allow_placeholder_icon` false. As before, a new call is necessary,
because a callback is a `base::OnceCallback`, so the same callback can't be
used for both the placeholder and the real icon.


## Provider-Specific Subtleties

Some concerns (like caching and coalescing multiple in-flight calls with the
same `IconKey`) are not specific to any particular Publishers like ARC++ or
Crostini, and can be solved by the Proxy.

Other concerns are Publisher-specific, and are generally solved in Publisher
implementations, albeit often with non-Publisher-specific support (such as for
placeholder icons, discussed above). Such concerns include:

  - Multiple icon sources: some icons for built-in VM-based apps (e.g. ARC++ or
    Crostini) should be served from a compiled-into-the-browser resource
    instead of from the VM.
  - Pending LoadIcon calls: some `LoadIcon` calls might need to wait on
    bringing up a VM.
  - Potential on-disk corruption: for whatever reason, an on-disk file that's
    meant to hold a cached icon may be missing or invalid. In that case, the
    Publisher should still provide a (placeholder) icon, and trigger
    Publisher-specific clean-up and re-load of the real app icon.

All of these concerns listed should be straightforward to handle, and don't
invalidate the overall App Service `Publisher.LoadIcon` design, including
its non-Publisher-specific caching and other optimization layers.

There are also yet another category of concerns that are Publisher-specific, but
also outside the purview of the App Service. For example, the file system
layout of ARC++'s on-disk icon cache is, from the App Service's point of view,
considered a private ARC++ implementation detail. As long as ARC++'s API
remains the same, and if ARC++ can notify the App Service if the App Service
needs to reload any or all icons, then any change in ARC++'s file system layout
isn't a direct concern to the App Service.


# App Runner

Each `Publisher` has (`Publisher`-specific) implementations of e.g. launching an
app and populating a context menu. The `AppService` presents a uniform API to
trigger these, forwarding each call on to the relevant `Publisher`:

    AppServiceProxy {
      // App Runner methods.
      Launch(AppType app_type, string app_id, LaunchOptions? opts);
      // etc.

      // Some additional methods; not App Runner related.
    };

    Publisher {
      // App Runner methods.
      Launch(string app_id, LaunchOptions? opts);
      // etc.

      // Some additional methods; not App Runner related.
    };

    struct LaunchOptions {
      // TBD.
    };

TBD: details for context menus.

TBD: be able to for-each over all the app *instances*, including multiple
instances (e.g. multiple windows) of the one app.


# App Installer

This includes Publisher-facing API (not Consumer-facing API like the majority of
the `AppService`) to help install and uninstall apps consistently. For example,
one part of app installation is adding an icon shortcut (e.g. on the Desktop
for Windows, on the Shelf for Chrome OS). This helper code should be written
once (in the `AppService`), not `N` times in `N` Publishers.

TBD: details.


# App Coordinator

This keeps system-wide or for-apps-as-a-whole preferences and settings, e.g.
out of all of the installed apps, which app has the user preferred for photo
editing. Consumer- or Publisher-specific settings, e.g. icon order in the Chrome
OS shelf, or Crostini VM configuration, is out of scope of the App Service.

TBD: details.


---

Updated on 2022-09-27.