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.
[Module]FrameworkInitializer
patternIf 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.
[Module]ServiceManager
patternNormally, 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:
ServiceRegisterer
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.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.
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.:
<permission android:name="com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER" 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.:
<permission android:name="android.permission.health.READ_ACTIVE_CALORIES_BURNED" android:label="@string/active_calories_burned_read_content_description" android:protectionLevel="dangerous" 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
.