How Project Mainline is Solving Android's Fragmentation Problem

Mishaal Rahman
|
Try Esper for Free
MDM Solutions for Android and iOS

This article sets about explaining Project Mainline, one of several Google initiatives to modularize Android to make it more updatable. The original goal behind Project Mainline was to improve the speed at which Android users receive security patches for critical system components, but it is also now used to roll out new Android features to users running different Android versions without needing them to apply a full OS update.

Although Project Mainline was introduced several years ago, it has expanded to encompass more and more parts of Android over time. With the recent release of Android 13 to AOSP, this article was written with the Android 13 changes to Project Mainline in mind. However, for a full list of Project Mainline updates specific to the Android 13 release, refer to Mishaal's coverage. And for an even deeper exploration into the internals of Android beyond Project Mainline, scroll to the end to learn more from the resources I've prepared.

The motivation for Project Mainline

In the years since its inception, Android has grown to encompass most of the mobile landscape and become the de facto operating system, the "Windows" of the mobile world. iOS maintains its considerable market share, yet it is intentionally limited to Apple's own devices. This leaves Android as the platform of (no) choice for all other vendors, thanks to Android's open source nature (AOSP) and free license, which allows a fairly large breadth of vendor customization.

The vast adoption of Android, however, is not without drawbacks. Though all vendors fork the same AOSP source tree, each is incentivized to add more and more proprietary customizations — whether in order to support specific hardware configurations or to differentiate by means of some novel UI or system application. Though discouraged from doing so, many vendors modify the contents of the system partition or change AOSP sources directly. Thus arose one of Android's most prominent problems — fragmentation.

Fragmentation occurs when AOSP customizations are significant enough to make a generic AOSP build incompatible with a device "as is". This means that it falls on the device manufacturer to keep AOSP up to date — no mere feat given Android's steady, annual progression. Users suffer from this first hand, as their devices become obsolete as early as 18 months after they were brand new. Modders experience the tribulations of creating a custom AOSP build for their device, only to realize some hardware component suddenly stops working. The ecosystem as a whole suffers as the majority of devices get their updates infrequently and often far too late (if at all), exacerbating security flaws and inhibiting the adoption of the most up-to-date Android experience.

Google first set out to address this problem in Android 8.0 Oreo with "Project Treble". This primarily involved a complete rearchitecture of Android's Hardware Abstraction Layer (the components used to interface the generic AOSP code with vendor/BSP-specific hardware aspects). A more pertinent change was in enforcing a clear-cut "division of responsibilities" through what was to become the standard partitioning layout of all devices:

  • /system — the system partition would now be exclusively comprised of AOSP code and thus become the responsibility of Google alone.
  • /vendor — would be used for the BSP (Board Support Package) binaries and files. These include the hardware-specific components needed to interface with the SoC components, ie. the baseband, sensors, camera, etc. Despite the numerous device brands, there are only a few silicon vendors, such as Qualcomm, Samsung (Exynos), MediaTek, and UNISOC (formerly Spreadtrum)).
  • /odm — would be used for files from the "Original Device Manufacturer", that is, the software vendor and brand of the device (e.g. Xiaomi, Samsung, OPPO, etc).
  • /product — would be used for device-specific idiosyncrasies like, for example, the minor differences between a Pixel 6 and 6a.

The partitions' filesystems are also similarly laid out (see the figure below), so as to effectively form a "search path" in reverse order of specificity. This way, the AOSP code first seeks out the product-specific files, if any, followed by those of the brand, then those of the SoC, and — if none are found or required — defaults to its own files, which are generic enough to serve all devices*.

Figure 1: Demonstrating the "division of responsibilities" through Android partitions (Source: "Android Internals (2nd Ed), Vol I, Chapter 3)

Thus, in a perfect world (effectively, from Android 9 and later), all Android devices — regardless of vendor — would use identical /system partitions. These could, in effect, be maintained solely by Google. Google could further push AOSP images as they see fit, relieving the vendor of the need and burden of doing so. Devices could more easily be kept up to date, and advanced use cases (notably, Generic System Images), could be considered as well.

How Project Mainline works

Android 10 brought the promise of "Project Mainline". Although it was initially conceived for out-of-band security updates, it's also enabling new feature and API backports. The big idea behind Project Mainline is to modularize system components, so that — while still part of AOSP — they can be updated in a manner not unlike a standard APK update on Google Play.

These Project Mainline modules, also called modular system components, are distributed in either the standard APK format or the Android Pony Express (APEX) format introduced in Android 10. Which format is used largely depends on how early in the boot process the component needs to be initialized, as APEX modules can be used much earlier in the boot process than APK-based modules can. The APK format is also less flexible when it comes to system modules, as it is primarily designed for Android apps. APEX files can contain native shared libraries, native executables, JAR files, data files, config files, and yes, even APKs. A more detailed explanation of the APEX format can be found in a following section.

Every Project Mainline module

Android 10 launched with thirteen Project Mainline modules. That number has more than doubled as of Android 13, or tripled if you consider undocumented modules. However, not every Mainline module will be present on any given device running a particular Android version. This is because Android Partners are only required to preload the modules listed in GMS Requirements, a document outlining the requirements that software builds must meet in order to receive Google's blessing to integrate GMS. Google requires Android Partners to preload most modules listed in table 1 below on Android 13 handheld devices, with the exception of the Bluetooth, UWB, and Wi-Fi modules.

Google provides its Android Partners a precompiled, presigned APK or APEX file for each module they require to be integrated. This is so Google can push updates to these modules through Google Play. Google-built modules typically have package names beginning with com.google instead of com.android, but the tables below will list the package names for the modules from AOSP for consistency.

Given the sheer number of Project Mainline modules, this section will not explain in detail the purpose of every module. Instead, we encourage you to visit the AOSP documentation for Project Mainline to learn about each individual module. The table below lists each module, its package name, its format (APK or APEX), when it was introduced, and what feature(s) it implements.

Table 1: Android’s Project Mainline modules (Source: Android Internals, Volume II, Chapter I)

ModulePackage nameFormatIntroduced inImplements
AdServicescom.android.adservicesAPEXAndroid 13Privacy Sandbox features
adbdcom.android.adbdAPEXAndroid 11Android Debug Bridge support
AppSearchcom.android.appsearchAPEXAndroid 13AppSearch, an on-device indexing and structured search engine
ARTcom.android.artAPEXAndroid 12Android Runtime (ART) and managed core library (libcore)
Bluetoothcom.android.bluetoothAPEXAndroid 13Bluetooth stack
Captive Portal Logincom.android.captiveportalloginAPKAndroid 10Captive Portal and Internet connectivity detection
CellBroadcastcom.android.cellbroadcastAPEXAndroid 11Cellular emergency alerts
Conscryptcom.android.conscryptAPEXAndroid 10Android TLS and cryptographic functionality
Device Schedulingcom.android.schedulingAPEXAndroid 12Determine reboot readiness
DNS Resolvercom.android.resolvAPEXAndroid 10DNS services (used by netd)
DocumentsUIcom.android.documentsuiAPKAndroid 10Documents picker for SAF
ExtServicescom.android.ext.servicesAPK (Android 10), APEX (Android 11)Android 10Miscellaneous component logic support
IPseccom.android.ipsecAPEXAndroid 11IKE[v2] support
Media Codecscom.android.media.swcodecAPEXAndroid 10Software codecs and media format support
Mediacom.android.mediaAPEXAndroid 10Media extractors, MediaSession2 API, MediaParser APIs, and media transcoding
MediaProvidercom.android.mediaproviderAPEXAndroid 11MediaStore collections
ModuleMetadatacom.android.modulemetadataAPKAndroid 10Contains metadata about the installed modules
Network Stack Permission Configurationcom.android.networkstack.permissionconfigAPKAndroid 10Empty module to define MAINLINE_NETWORK_STACK permission
Network Componentscom.android.networkstackAPKAndroid 10Network stack components
NNAPI Runtimecom.android.neuralnetworksAPKAndroid 11Updates to the Neural Network Runtime library
OnDevicePersonalizationcom.android.ondevicepersonalizationAPEXAndroid 13Simplify development of apps using user-data in a privacy-preserving way
Permissioncom.android.permissionAPK (Android 10), APEX (Android 11)Android 10Runtime permission controller and Safety Center
SDK Extensionscom.android.sdkextAPEXAndroid 11Determines SDK extension level and provides APIs to query it
Statsdcom.android.os.statsdAPEXAndroid 11statsd, a native service that collects metrics
Tetheringcom.android.tetheringAPKAndroid 11Tethering support, CrossDevice
Time Zone Datacom.android.tzdata[1-4]APEXAndroid 10Standardizes time zone data and how it’s updated
UWB Zone Datacom.android.uwbAPEXAndroid 13Ultra-Wideband stack
Wi-Ficom.android.wifiAPEXAndroid 11Wi-Fi provider

The above table only lists those modules that have been documented officially by Google. Google only documents modules as part of Project Mainline when they become updatable — certain modules like ART were originally packaged as non-updateable APEX files. Several modules are not documented on the aforementioned source.android.com page as they are not updatable and thus not considered part of "Project Mainline". However, we are listing them below for completeness:

Table 2: Android's undocumented modules (Credits: Mishaal Rahman)

ModulePackage nameFormatStatusImplements
AdServices TVPcom.android.mainline.adservicesAPKAndroid 13Privacy Sandbox
ANGLEcom.android.angleAPKIn use (since Android 10)ANGLE
Car Frameworkcom.android.car.frameworkAPEXIn use (since Android 13)Android Automotive framework stack
CompOScom.android.composAPEXIn use (since Android 13)Isolated compilation
Cronetcom.android.cronetAPEXDeferred (planned for Android 13)Chromium network stack
GeoTZcom.android.geotzAPEXReference module (for Android 12)Location-based time zone provider
GKIcom.android.gkiAPEXAbandoned (tested for Android 12)Generic Kernel Image
Nearbycom.android.nearbyAPEXIn-development (tested for Android 13)Nearby (Fast Pair) support
RuntimeI18ncom.android.i18nAPEXIn use (since Android 12)Locale Internationalization
SEPolicycom.android.sepolicyAPEXFor development only (in Android 13), mandatory (in Android 14)Modular SELinux Policy
Support componentscom.google.mainline.telemetryAPKIn use (since Android 12)Telemetry
Virtualizationcom.android.virtAPEXIn use (since Android 13)Virtual Machine support & MicroDroid
VNDKcom.android.vndk.currentAPEXIn use (since Android 13)Vendor Native Development Kit

Lastly, devices may ship vendor-specific modules. The camera HAL on Google Pixel devices, for example, is packaged in the com.google.pixel.camera.hal APEX file located in /vendor/apex. Seeing as vendor APEXes are not part of AOSP, these will not be documented in this article.

What features have Project Mainline module updates brought?

The further modularization of Android has already born fruit several times since its inception. Through Mainline updates, Google has brought (or will bring) the following features to existing devices without the need for an OS update:

  • mDNS .local resolution: The November 2021 Google Play System Update allowed the DNS Resolver module to recognize the .local domain (RFC6762) with Multicast DNS, in the implementation of getaddrinfo().
  • Photo Picker: Officially introduced as one of the new features of Android 13, the Photo Picker was backported to Android 11-12L devices with the May 2022 Google Play System Update.
  • DNS over HTTP/3 support: An update to the DNS Resolver module in the July 2022 Google Play System Update adds support for using DNS-over-HTTP/3 (DoH3) as the encrypted DNS protocol for the "private DNS" feature on all devices running Android 11-13 and some devices running Android 10.
  • New garbage collection algorithm based on userfaultfd: Through an upcoming update to the ART module, Google will introduce a new garbage collection algorithm based on Linux's userfaultfd feature that reduces memory overhead.
  • Safety Center: The new unified security & privacy settings page in Android 13 is part of the PermissionController app, which is contained within the Permission module. This, along with the new "Protected by Android" branding, will be enabled in an upcoming Google Play System Update.

These are only some of the more notable features introduced through Project Mainline module updates. Google does not provide a detailed changelog of every feature and changed introduced in monthly Google Play System Updates (GPSU), though they do document some changes in their "what's new in Google System Updates" support page. This page mainly lists changes introduced through updates to the Google Play Store and Google Play Services applications, but it also lists changes introduced through Google Play System Updates.

The structure of APEX packages

APEX packages are a special case of APKs — that is, ZIP/JAR files signed by apksigner. AOSP's /system partition now has a /system/apex directory, in which the default modules are provided. Additional modules that are not compiled by default when building AOSP but can optionally be added to builds are placed in /system_ext/apex. Vendor APEXes are installed by the build system in the /vendor/apex directory.

When the device first boots, these are mounted and used. As new APEX modules become available, they are downloaded into /data/apex. A quick version check can then be made to determine which version is the most up to date.

redfin:/system/apex $ ls -l
total 184784
-rw-r--r-- 1 root root   336254 2008-12-31 19:00 com.android.apex.cts.shim.apex
-rw-r--r-- 1 root root  7696384 2008-12-31 19:00 com.android.cronet.apex
-rw-r--r-- 1 root root 38125568 2008-12-31 19:00 com.android.i18n.apex
-rw-r--r-- 1 root root  8249344 2008-12-31 19:00 com.android.runtime.apex
-rw-r--r-- 1 root root 48242688 2008-12-31 19:00 com.android.vndk.current.apex
-rw-r--r-- 1 root root  3318377 2008-12-31 19:00 com.google.android.adbd.apex
-rw-r--r-- 1 root root   442368 2008-12-31 19:00 com.google.android.adservices.apex
-rw-r--r-- 1 root root  1286760 2008-12-31 19:00 com.google.android.appsearch.apex
-rw-r--r-- 1 root root 22458987 2008-12-31 19:00 com.google.android.art.apex
-rw-r--r-- 1 root root  9728620 2008-12-31 19:00 com.google.android.bluetooth.apex
-rw-r--r-- 1 root root  4829801 2008-12-31 19:00 com.google.android.cellbroadcast.apex
-rw-r--r-- 1 root root  2384564 2008-12-31 19:00 com.google.android.conscrypt.apex
-rw-r--r-- 1 root root  2855528 2008-12-31 19:00 com.google.android.extservices.apex
-rw-r--r-- 1 root root   356966 2008-12-31 19:00 com.google.android.ipsec.apex
-rw-r--r-- 1 root root  2634419 2008-12-31 19:00 com.google.android.media.apex
-rw-r--r-- 1 root root  8561328 2008-12-31 19:00 com.google.android.media.swcodec.apex
-rw-r--r-- 1 root root  3723880 2008-12-31 19:00 com.google.android.mediaprovider.apex
-rw-r--r-- 1 root root  3703399 2008-12-31 19:00 com.google.android.neuralnetworks.apex
-rw-r--r-- 1 root root   119401 2008-12-31 19:00 com.google.android.ondevicepersonalization.apex
-rw-r--r-- 1 root root   852586 2008-12-31 19:00 com.google.android.os.statsd.apex
-rw-r--r-- 1 root root  5710442 2008-12-31 19:00 com.google.android.permission.apex
-rw-r--r-- 1 root root  1835694 2008-12-31 19:00 com.google.android.resolv.apex
-rw-r--r-- 1 root root    98922 2008-12-31 19:00 com.google.android.scheduling.apex
-rw-r--r-- 1 root root   344680 2008-12-31 19:00 com.google.android.sdkext.apex
-rw-r--r-- 1 root root   331776 2008-12-31 19:00 com.google.android.telephony.apex
-rw-r--r-- 1 root root  4039274 2008-12-31 19:00 com.google.android.tethering.apex
-rw-r--r-- 1 root root   950272 2008-12-31 19:00 com.google.android.tzdata4.apex
-rw-r--r-- 1 root root  1176171 2008-12-31 19:00 com.google.android.uwb.apex
-rw-r--r-- 1 root root  3527272 2008-12-31 19:00 com.google.android.wifi.apex
-rw-r--r-- 1 root root  1794048 2008-12-31 19:00 com.google.mainline.primary.libs.apex

The layout of APEX files is given by the official AOSP documentation as follows:

Figure 2a: APEX file format (Source: AOSP documentation for APEX)

A more detailed view is provided in the following illustration, which focuses on the APEX Manifest — traditionally, in XML form, but recently (as of around Android 11) in Google's favorite protobuf format:

Figure 2b: APEX file format internals (Source: Android Internals, Volume I, Chapter 5)

But let's examine a real APEX file hands-on. Since there are plenty of APEXes in /system/apex, we connect to the device and adb pull one of them, Cronet, as an example:

morpheus@Forge /tmp $ adb pull /system/apex/com.android.cronet.apex
/system/apex/com.android.cronet.apex: 1 file pulled, 0 skipped. 31.0 MB/s (7696384 bytes in 0.236s)
morpheus@Forge /tmp $ file com.android.cronet.apex
com.android.cronet.apex: Java archive data (JAR)
morpheus@Forge /tmp $ unzip -l !$
unzip -l com.android.cronet.apex
Archive:  com.android.cronet.apex
 Length      Date    Time    Name
---------  ---------- -----   ----
 7573504  01-01-2009 00:00   apex_payload.img
    1032  01-01-2009 00:00   apex_pubkey
   72486  01-01-2009 00:00   assets/NOTICE.html.gz
      40  01-01-2009 00:00   resources.arsc
    1112  01-01-2009 00:00   AndroidManifest.xml
    1527  01-01-2009 00:00   apex_build_info.pb
     102  01-01-2009 00:00   apex_manifest.pb
      32  01-01-2009 00:00   stamp-cert-sha256
     868  01-01-2009 00:00   META-INF/CERT.SF
    2244  01-01-2009 00:00   META-INF/CERT.RSA
     733  01-01-2009 00:00   META-INF/MANIFEST.MF
---------                     -------
 7653680                     11 files

Every APK is a JAR, and every JAR is a ZIP. From ZIP we get the file format, archiving, and compression/storage methods. From JAR we get the META-INF/ directory, which contains the traditional signature of jarsigner (but not of apksigner, which uses special ZIP blocks). From APK we get the AndroidManifest.xml:

morpheus@Forge /tmp $ aapt d xmltree com.android.cronet.apex AndroidManifest.xml
N: android=http://schemas.android.com/apk/res/android
 E: manifest (line=17)
   A: android:versionCode(0x0101021b)=(type 0x10)0x1
   A: android:compileSdkVersion(0x01010572)=(type 0x10)0x21
   A: android:compileSdkVersionCodename(0x01010573)="Tiramisu" (Raw: "Tiramisu")
   A: package="com.google.android.cronet" (Raw: "com.google.android.cronet")
   A: platformBuildVersionCode=(type 0x10)0x21
   A: platformBuildVersionName="Tiramisu" (Raw: "Tiramisu")
   E: application (line=20)
     A: android:hasCode(0x0101000c)=(type 0x12)0x0
   E: uses-sdk (line=21)
     A: android:minSdkVersion(0x0101020c)=(type 0x10)0x1d
     A: android:targetSdkVersion(0x01010270)=(type 0x10)0x21

As the above shows, the Manifest describes an APK with a target SDK of 33 (0x21). As a codeless APK (hasCode: 0), there isn't much more to describe in the manifest — so the APEX metadata is in a separate file, apex_manifest.pb:

morpheus@Forge tmp$ unzip com.android.cronet.apex apex_manifest.pb
Archive:  com.android.cronet.apex
 inflating: apex_manifest.pb        
morpheus@Forge tmp$ hexdump -C apex_manifest.pb
00000000  0a 12 63 6f 6d 2e 61 6e  64 72 6f 69 64 2e 63 72  |..com.android.cr|
00000010  6f 6e 65 74 10 01 42 0d  6c 69 62 61 6e 64 72 6f  |onet..B.libandro|
00000020  69 64 2e 73 6f 42 07 6c  69 62 63 2e 73 6f 42 08  |id.soB.libc.soB.|
00000030  6c 69 62 64 6c 2e 73 6f  42 09 6c 69 62 6c 6f 67  |libdl.soB.liblog|
00000040  2e 73 6f 42 07 6c 69 62  6d 2e 73 6f 4a 18 6c 69  |.soB.libm.soJ.li|
00000050  62 63 72 6f 6e 65 74 2e  38 30 2e 30 2e 33 39 38  |bcronet.80.0.398|
00000060  36 2e 30 2e 73 6f                                 |6.0.so|

The .pb extension denotes the ProtoBuf format, a serialization method Google is especially fond of and has become a standard in ChromeOS and Android. With a little help from the AOSP sources, we see that element "0xa" is ;requireSharedApexLibs and that the APEX supplies libronet.80.0.3986.0.so.

The meat of the APEX is in apex_payload.img. This is a raw filesystem image signed by apex_pubkey. APEX image payloads are currently formatted in EXT4, but for Android 13 launch devices with kernel version 5.10 or higher, Google is shifting to formatting them with EROFS for additional storage savings. Knowing this, we can extract and try to mount it, in the same way Android's apexd does:

morpheus@Forge tmp$ unzip com.android.cronet.apex apex_payload.img
Archive:  com.android.cronet.apex
extracting: apex_payload.img        
[morpheus@Forge tmp]$ file apex_payload.img
apex_payload.img: Linux rev 1.0 ext2 filesystem data, UUID=7d1522e1-9dfa-5edb-a43e-98e3a4d20250 (extents) (large files) (huge files)
#
# Following requires root, since mounting with options:
#
root@Forge /tmp # mkdir /mnt1
root@Forge /tmp # mount -o loop $PWD/apex_payload.img /mnt1
mount: /mnt1: wrong fs type, bad option, bad superblock on /dev/loop0, missing codepage or helper program, or other error.
#
# Errors reported! Try dmesg:
#
root@Forge /tmp # dmesg | tail
...
[286740.820105] loop: module loaded
[286740.857796] EXT4-fs (loop0): couldn't mount RDWR because of unsupported optional features (4000)
[286740.913600] blk_update_request: I/O error, dev loop0, sector 0 op 0x1:(WRITE) flags 0x800 phys_seg 0 prio class 0
[286740.913937] blk_update_request: I/O error, dev loop0, sector 0 op 0x1:(WRITE) flags 0x800 phys_seg 0 prio class 0
#
# ext4 has the notion of "optional features", which may be ignored if mounting read-only
#
root@Forge /tmp # mount -o ro,loop $PWD/apex_payload.img /mnt1
root@Forge /tmp # cd /mnt1
root@Forge /mnt1 # ls -RF
.:
apex_manifest.pb  etc/  javalib/  lib/  lib64/  lost+found/
./etc:
permissions
./etc/permissions:
org.chromium.net.cronet.xml
./javalib:
org.chromium.net.cronet.jar
./lib:
libcronet.80.0.3986.0.so
./lib64:
libcronet.80.0.3986.0.so
./lost+found: # An artifact of mkfs, for fsck use (thus empty)

(Android 12 introduced the ability to compress pre-installed APEX files stored on read-only partitions. This will not be addressed here, but it is worth mentioning.)

The manual mounting of an APEX payload demonstrated here is performed automatically by Android's apexd. (Mounting an APEX payload requires the Linux kernel's loop driver included in Linux 4.9+.) As an automatic service, apexd starts up from its own .rc file:

service apexd /system/bin/apexd
   interface aidl apexservice
   class core
   user root
   group system
   oneshot
   disabled # does not start with the core class
   reboot_on_failure reboot,apexd-failed
service apexd-bootstrap /system/bin/apexd --bootstrap
   user root
   group system
   oneshot
   disabled
   reboot_on_failure reboot,bootloader,bootstrap-apexd-failed
service apexd-snapshotde /system/bin/apexd --snapshotde
   user root
   group system
   oneshot
   disabled

As the above shows, apexd is started under no less than three possible configurations. The first one is the apexd-bootstrap. This is a disabled service, so it is started from AOSP's init.rc explicitly:

on early-init
# Run apexd-bootstrap so that APEXes that provide critical libraries
# become available. Note that this is executed as exec_start to ensure that
# the libraries are available to the processes started after this statement.
exec_start apexd-bootstrap
# Generate linker config based on apex mounted in bootstrap namespace
update_linker_config
on property:apexd.status=ready && property:ro.product.cpu.abilist32=*
exec_start boringssl_self_test_apex32
on property:apexd.status=ready && property:ro.product.cpu.abilist64=*
exec_start boringssl_self_test_apex64
service boringssl_self_test_apex32 /apex/com.android.conscrypt/bin/boringssl_self_test32
service boringssl_self_test_apex64 /apex/com.android.conscrypt/bin/boringssl_self_test64

In the bootstrap configuration, apexd iterates over /system/apex packages and performs the mounts. After apexd-bootstrapis started, the update_linker_config directive — an /init built-in — is used to modify Bionic's linker (through /linkerconfig) so as to point to any APEX packages that were mounted as a result. Apexd communicates its status through the apexd.status system property. When it is ready, BoringSSL — a critical component of the system (as an SSL/TLS stack) is self tested from its (by now, mounted) package.

The initialization continues:

on post-fs-data
mkdir /metadata/apex 0700 root system
mkdir /metadata/apex/sessions 0700 root system
# On some devices we see a weird behaviour in which /metadata/apex doesn't
# have a correct label. To workaround this bug, explicitly call restorecon
# on /metadata/apex. For most of the boot sequences /metadata/apex will
# already have a correct selinux label, meaning that this call will be a
# no-op.
  restorecon_recursive /metadata/apex
# Make sure that apexd is started in the default namespace
# and start apexd
# before starting apexd.
# /data/apex is now available. Start apexd to scan and activate APEXes.
# that apexd is started cleanly here (set apexd.status="") and that it is
   mkdir /data/apex 0755 root system encryption=None
   mkdir /data/apex/active 0755 root system
   mkdir /data/apex/backup 0700 root system
   mkdir /data/apex/decompressed 0755 root system encryption=Require
   mkdir /data/apex/hashtree 0700 root system
   mkdir /data/apex/sessions 0700 root system
   mkdir /data/apex/ota_reserved 0700 root system encryption=Require
   setprop apexd.status ""
   restart apexd
   mkdir /data/misc/apexdata 0711 root root
   mkdir /data/misc/apexrollback 0700 root root
# Wait for apexd to finish activating APEXes before starting more processes.
   wait_for_prop apexd.status activated
   perform_apex_config
# Allow apexd to snapshot and restore device encrypted apex data in the case
# completed and apexd.status becomes "ready".
   exec_start apexd-snapshotde
   exec - system system -- /system/bin/tzdatacheck /apex/com.android.tzdata/etc/tz /data/misc/zoneinfo

In the above, we see that /data/apex directories — where downloadable APEX packages and their metadata are stored — is created (since /data may be empty after a factory reset). The apexd daemon is started again, and when it signals its status is "activated", perform_apex_config, another /init built-in (from Android 11) reconfigures the linker configuration, if necessary.

Updating Project Mainline modules

Written by Mishaal Rahman

APEX packages can be manually installed through Android's package installer like standard APKs, though this requires the use of the pm install --apex command. Doing so is difficult on GMS Android devices as Google-signed APEX packages are not readily obtainable without root access, and there is no online repository of module updates. Instead, GMS Android users should navigate to Settings > Security > Google Play system update, which launches the Mainline module updater activity in Google Play, to check for and download new Mainline updates.

Figure 3: Updating Project Mainline modules

Once a Google Play System Update has been installed, the system may reboot to install staged APEX or APK files. Devices running Android 11 and above may support soft restarts, ie. user space reboots, enabling the restarting of userspace components while keeping Credential Encrypted (CE) storage unlocked. Server-based resume-on-reboot on Android 12+ goes a step further by using reboot readiness to time this restart when the device is idle, while keeping the data used to unlock CE safe and secure. If either is implemented, this enables Mainline updates to be installed without prompting the user.

Google releases Mainline module updates as part of a "train", ie. a bundle of modules that are downloaded and installed at the same time. This is in contrast to Android app updates which are rolled out individually. When a module train rolls out, the version number of the ModuleMetadata app is incremented, which is where Settings derives the Google Play System Update version.

In case of a botched module update, Android's RollbackManager can roll the update and its data back to the previous version installed on the device. Rollbacks are automatically triggered when there are 5 ANRs or process crashes within 1 minute after an update or if any native service crashes repeatedly. If there's no network connectivity, a rollback will also be triggered for the network stack.

The command-line provides some useful tools for managing APEX module installations. The pm list packages --apex-only --show-versioncode command can be used to list all installed APEX packages and their versions. Meanwhile, the new transparency manager CLI introduced in Android 13 allows for independently comparing the cryptographic signature of each module against Google's transparency log.

Summary

This article gave a detailed account of Project Mainline modules and their behind-the-scenes operation. APEX is an important addition to Android's features (as of Android 10), since it allows a modular architecture, which provides for easy updates, bringing new features, bug fixes or security patches — all without the need for an OS update. As Android progresses, more and more components are modularized and packaged by APEX.

The article was written by Jonathan Levin with the invaluable insights provided by Mishaal Rahman. If you liked this content, consider taking a look at the books Android Internals::Power User's View and Android Internals::Developer's View. The two books provide unprecedented detail into the inner workings of Android — from the Linux system/adb shell perspective (Volume I), and from the framework implementation and infrastructure (Binder, Dalvik/ART, etc.) perspectives (Volume II). The books go above and beyond Google's documentation, elucidating Android's most undocumented domains, and detailing its operational aspects, aiming to cover every single daemon, service, and AOSP files of significance. Planned soon are Volume III (Security) and IV (Advanced topics, including hardware, graphics, media, baseband, networking, and more). For more information and free command line tools, please visit newandroidbook.com!

* It's worth mentioning that all the partitions and APEX packages are digitally signed (using DM-Verity) and backed by Android Verified Boot, so as to make them unmodifiable on non-rooted devices — much to the chagrin of modders

Esper Device Management

FAQ

No items found.
No items found.

Keep Exploring

No items found.
Mishaal Rahman
Mishaal Rahman
Mishaal Rahman is a Technical Editor at Esper. He has been an Android user for over a decade and has been at the forefront of Android news coverage for half a decade. His in-depth breakdowns of new Android versions have been referenced across the Internet.
Mishaal Rahman
Learn about Esper mobile device management software for Android and iOS
Featured resource
Read more
Featured resource
Preparing Edge Device Fleets for the Future
Understand where IoT, AI, DevOps, security, and operationalizing the edge converge in this comprehensive guide for practitioners.
Download the Guide

Esper is Modern Device Management

For tablets, smartphones, kiosks, point of sale, IoT, and other Android and iOS edge devices.
MDM Solutions