Mainline specific patterns [ML]

Mainline is a project to allow updating subsystems (“mainline modules”) of the Android OS individually, rather than updating the whole system image.

Mainline modules have to be “unbundled” from the core platform, which means all the interactions between each module and the rest of the world have to be done via formal (public or system) APIs.

There are certain design patterns mainline modules should follow. This section describes them.

The [Module]FrameworkInitializer pattern

If a mainline module needs to exposes @SystemService classes (e.g. JobScheduler), use the following pattern:

  • Expose a [YourModule]FrameworkInitializer class from your module. This class needs to be in $BOOTCLASSPATH. Example: StatsFrameworkInitializer

  • Mark it with @SystemApi(client = MODULE_LIBRARIES).

  • Add a public static void registerServiceWrappers() method to it.

  • Use SystemServiceRegistry.registerContextAwareService() to register a service manager class when it needs a reference to a Context.

  • Use SystemServiceRegistry.registerStaticService() to register a service manager class when it does not need a reference to a Context.

  • Call the registerServiceWrappers() method from SystemServiceRegistry's static initializer.

The [Module]ServiceManager pattern

Normally, in order to register system service binder objects and/or get references to them, one would use ServiceManager, but mainline modules can‘t use it because it’s hidden. This class is hidden because mainline modules are not supposed to register or refer to system service binder objects exposed by the non-updateable platform or by other modules.

Mainline modules can use the following pattern instead to be able to register and get references to binder services that are implemented inside the module.

  • Create a [YourModule]ServiceManager class, following the design of TelephonyServiceManager

  • Expose the class as @SystemApi. If you only need to access it from $BOOTCLASSPATH classes or system server classes, you can use @SystemApi(client = MODULE_LIBRARIES); otherwise @SystemApi(client = PRIVILEGED_APPS) would work.

  • This class would consists of:

    • A hidden constructor, so only the non-updatable platform code can instantiate it.
    • A nested class ServiceRegisterer TODO(omakoto) b/245801672: It shouldn't be an inner class. Create a sharable class for this.
    • Public getter methods that return a ServiceRegisterer instance for a specific name. If you have one binder object, then you need one getter method. If you have two, then you need two getters.
    • In ActivityThread.initializeMainlineModules(), instantiate this class, and pass it to a static method exposed by your module. Normally, you add a static @SystemApi(client = MODULE_LIBRARIES) API in your FrameworkInitializer class that takes it.

This pattern would prevent other mainline modules from accessing these APIs because there's no way for other modules to get an instance of [YourModule]ServiceManager, even though the get() and register() APIs are visible to them.

Here is how telephony [^telephonymodule] gets a reference to the telephony service: code search link.

[^telephonymodule]: Telephony is not a mainline module (yet), but this still shows a preferred pattern.

If your implements a service binder object in native code, you use the AServiceManager native APIs. These APIs correspond to the ServiceManager Java APIs but the native ones are directly exposed to mainline modules. Do not use them to register or refer to binder objects that are not owned by your module. If you expose a binder object from native, your [YourModule]ServiceManager.ServiceRegisterer does not need a register() method.

Defining permissions in Mainline module

Mainline modules containing APKs may define (custom) permissions in their APK AndroidManifest.xml in the same way as a regular APK.

NOTE: Despite that the APEX AndroidManifest.xml currently also allows <permission> tags because it's sharing the parsing code with APKs, that is unsupported and may be removed in the future.

If the defined permission is only used internally within a module, its permission name should be prefixed with the APK package name, e.g.:

    android:protectionLevel="signature" />

If the defined permission is to be provided as part of an updatable platform API to other apps, its permission name should be prefixed with “android.permission.” (like any non-updatable platform permission) plus the module package name, to signal it's a platform API from a module while avoiding any naming conflicts, e.g.:

    android:permissionGroup="android.permission-group.HEALTH" />

Then the module can expose this permission name as an API constant in its API surface, e.g. HealthPermissions.READ_ACTIVE_CALORIES_BURNED.