Chapter 52: Mainline Modules¶
Android has historically shipped operating-system updates as monolithic OTA images. Every security patch, every bug fix, every API improvement had to flow through the device manufacturer, wait for carrier certification, and finally reach the user -- a pipeline that often took months. Project Mainline fundamentally changes this model by carving the platform into independently updatable modules that Google can push directly to devices through the Google Play infrastructure. This chapter dissects the architecture that makes that possible: the APEX container format, the apexd daemon that activates modules at boot, the catalog of 40+ Mainline modules shipped in AOSP, and the SDK Extensions mechanism that lets apps discover which module versions are present at runtime.
52.1 Project Mainline¶
52.1.1 The Problem: Fragmentation and Stale Security Fixes¶
Before Android 10 (Q), the update lifecycle for every platform component looked roughly the same:
- Google engineers commit a fix to AOSP.
- Each OEM cherry-picks the fix into their own BSP branch.
- The OEM builds a full system image.
- Carriers certify the image.
- The OTA reaches end-user devices.
For a critical CVE in, say, the DNS resolver or the media codec stack, this pipeline could take anywhere from three months to never, depending on the OEM's commitment and the device's age. The result was a fragmented ecosystem where billions of devices ran dangerously outdated platform code.
52.1.2 The Solution: Modular, Updatable Components¶
Project Mainline, introduced in Android 10 and expanded in every subsequent release, slices the platform into modules that can be updated independently of the base system image. Each module is packaged as either:
-
An APEX (Android Pony EXpress) -- a new container format for native code, Java libraries, and configuration files.
-
A standard APK -- for modules that are pure Java / Kotlin.
Google pushes module updates through the Google Play system update mechanism (branded "Google Play system updates" on devices), allowing security-critical fixes to reach all supported devices within days rather than months.
52.1.3 Design Goals¶
| Goal | Mechanism |
|---|---|
| Update native code without a full OTA | APEX file format with dm-verity payload |
| Maintain ABI stability across releases | @SystemApi, hidden-API enforcement, stable AIDL |
| Ensure rollback safety | Staged sessions, checkpoint/restore in apexd |
| Minimize OEM disruption | Modules ship pre-installed; updates are incremental |
| Support old and new devices alike | min_sdk_version per module, SDK Extensions for runtime checks |
52.1.4 Historical Timeline¶
| Release | Codename | Mainline Milestone |
|---|---|---|
| Android 10 | Q (2019) | Initial launch with ~12 APEX modules |
| Android 11 | R (2020) | Added min_sdk_version enforcement; compressed APEX (CAPEX) |
| Android 12 | S (2021) | SDK Extensions; ART module becomes updatable |
| Android 13 | T (2022) | AdServices, AppSearch, OnDevicePersonalization modules |
| Android 14 | U (2023) | ConfigInfrastructure, HealthFitness modules |
| Android 15 | V (2024) | NeuralNetworks, ThreadNetwork, Profiling modules |
| Android 16 | B / Baklava (2025) | UprobeStats; brand-new APEX support in apexd |
52.1.5 The Update Flow (High Level)¶
sequenceDiagram
participant GPlay as Google Play
participant PM as PackageManager
participant Apexd as apexd
participant Device as /apex/
GPlay->>PM: Push new APEX version
PM->>PM: Verify signature, create staged session
PM->>Apexd: submitStagedSession(session_id)
Apexd->>Apexd: VerifyPackagesStagedInstall()
Apexd->>Apexd: BackupActiveApexes()
Note over Device: Reboot required
Apexd->>Apexd: ActivatePackageImpl() for each APEX
Apexd->>Device: Mount updated APEX at /apex/<name>
Apexd->>Apexd: SetProperty("apexd.status", "ready")
The key insight is that APEX updates are staged -- they are downloaded and verified before a reboot, then atomically activated during the next boot. If activation fails, apexd rolls back to the pre-installed version.
52.2 APEX Format¶
The APEX file format is the cornerstone of Project Mainline. It solves a problem that APKs cannot: packaging and updating native shared libraries, executables, configuration files, and Java bootclasspath fragments as a single, signed, integrity-verified unit.
52.2.1 Why Not Just APK?¶
APKs are ZIP archives designed for Dalvik/ART applications. They carry DEX
bytecode, resources, and native libraries (lib/<abi>/). But they lack:
- dm-verity protection for the payload image.
-
Loop-device mounting that allows the kernel to treat the payload as a real filesystem.
-
Boot-time activation before zygote starts.
- The ability to replace platform-level native libraries like
libc++orlibcrypto.
APEX addresses all of these.
52.2.2 File Structure¶
An APEX file (.apex) is a ZIP archive containing:
my_module.apex (ZIP)
+-- AndroidManifest.xml # Standard APK manifest (for Play Store)
+-- apex_manifest.pb # Protobuf: name, version, provideNativeLibs, ...
+-- apex_payload.img # ext4/f2fs/erofs filesystem image
+-- apex_pubkey # AVB public key (embedded for verification)
+-- META-INF/ # JAR signing (v2/v3 APK signature)
The critical component is apex_payload.img. This is a real filesystem image
-- ext4, f2fs, or erofs -- containing the module's files laid out exactly as
they will appear when mounted at /apex/<module_name>/.
The apex_file.cpp implementation in system/apex/apexd/ recognizes all three
filesystem types by their magic bytes:
// Source: system/apex/apexd/apex_file.cpp
constexpr const char* kImageFilename = "apex_payload.img";
constexpr const char* kCompressedApexFilename = "original_apex";
constexpr const char* kBundledPublicKeyFilename = "apex_pubkey";
struct FsMagic {
const char* type;
int32_t offset;
int16_t len;
const char* magic;
};
constexpr const FsMagic kFsType[] = {
{"f2fs", 1024, 4, "\x10\x20\xf5\xf2"},
{"ext4", 1024 + 0x38, 2, "\123\357"},
{"erofs", 1024, 4, "\xe2\xe1\xf5\xe0"}
};
52.2.3 APEX Manifest (Protobuf)¶
Every APEX carries a protobuf manifest defined in
system/apex/proto/apex_manifest.proto:
// Source: system/apex/proto/apex_manifest.proto
message ApexManifest {
// Name used to mount under /apex/<name>
string name = 1;
// Version Number
int64 version = 2;
// Pre Install Hook
string preInstallHook = 3;
// Version Name
string versionName = 5;
// If true, apexd mounts with MS_NOEXEC
bool noCode = 6;
// Native libs provided to other APEXes or the platform
repeated string provideNativeLibs = 7;
// Native libs required from other APEXes or the platform
repeated string requireNativeLibs = 8;
// JNI libraries (used by linkerconfig/libnativeloader)
repeated string jniLibs = 9;
// Compressed APEX metadata
CompressedApexMetadata capexMetadata = 12;
// Can be updated without reboot
bool supportsRebootlessUpdate = 13;
// Whether activated in bootstrap phase
bool bootstrap = 16;
}
The provideNativeLibs and requireNativeLibs fields are particularly
important: they allow apexd and the linker configuration system to construct
correct shared-library namespaces at boot time.
52.2.4 Compressed APEX (CAPEX)¶
Starting in Android 11, APEXes can be compressed on the system partition to
save space. A compressed APEX has the suffix .capex and contains the original
APEX inside, compressed. At boot, apexd decompresses it to
/data/apex/decompressed/:
// Source: system/apex/apexd/apex_constants.h
static constexpr const char* kApexDecompressedDir = "/data/apex/decompressed";
static constexpr const char* kCompressedApexPackageSuffix = ".capex";
static constexpr const char* kDecompressedApexPackageSuffix =
".decompressed.apex";
The ApexFile::IsCompressed() method detects whether an APEX is compressed,
and ApexFile::Decompress() handles the decompression.
52.2.5 The ApexFile Class¶
The C++ ApexFile class (system/apex/apexd/apex_file.h) is the primary
abstraction for working with APEX packages at runtime:
// Source: system/apex/apexd/apex_file.h
class ApexFile {
public:
static android::base::Result<ApexFile> Open(const std::string& path);
const std::string& GetPath() const { return apex_path_; }
const std::optional<uint32_t>& GetImageOffset() const {
return image_offset_;
}
const std::optional<size_t>& GetImageSize() const { return image_size_; }
const ::apex::proto::ApexManifest& GetManifest() const {
return manifest_;
}
const std::string& GetBundledPublicKey() const { return apex_pubkey_; }
const std::optional<std::string>& GetFsType() const { return fs_type_; }
android::base::Result<ApexVerityData> VerifyApexVerity(
const std::string& public_key) const;
bool IsCompressed() const { return is_compressed_; }
android::base::Result<void> Decompress(
const std::string& output_path) const;
private:
std::string apex_path_;
std::optional<uint32_t> image_offset_;
std::optional<size_t> image_size_;
::apex::proto::ApexManifest manifest_;
std::string apex_pubkey_;
std::optional<std::string> fs_type_;
bool is_compressed_;
};
The ApexVerityData struct carries the information needed to set up dm-verity:
// Source: system/apex/apexd/apex_file.h
struct ApexVerityData {
std::unique_ptr<AvbHashtreeDescriptor> desc;
std::string hash_algorithm;
std::string salt;
std::string root_digest;
};
The Open() factory method performs the following steps:
- Opens the file as a ZIP archive using
libziparchive. - Reads the
apex_manifest.pbentry and parses the protobuf. - Reads the
apex_pubkeyentry (the AVB public key). -
Locates the
apex_payload.imgentry and records its offset and size within the ZIP. -
Detects the filesystem type by reading magic bytes from the payload image.
- Checks whether this is a compressed APEX (contains
original_apexinstead ofapex_payload.img).
// Source: system/apex/apexd/apex_file.cpp (Open method, simplified)
Result<ApexFile> ApexFile::Open(const std::string& path) {
// Open as ZIP archive
ZipArchiveHandle handle;
int ret = OpenArchiveFd(fd.get(), path.c_str(), &handle, false);
// Try to find apex_payload.img
ZipEntry entry;
ret = FindEntry(handle, kImageFilename, &entry);
if (ret == 0) {
image_offset = entry.offset;
image_size = entry.uncompressed_length;
// Detect filesystem type (ext4, f2fs, erofs)
fs_type = RetrieveFsType(fd, entry.offset);
}
// Read manifest
FindEntry(handle, kManifestFilenamePb, &entry);
// ... parse protobuf ...
// Read public key
FindEntry(handle, kBundledPublicKeyFilename, &entry);
// ... read key ...
return ApexFile(path, image_offset, image_size,
manifest, pubkey, fs_type, is_compressed);
}
52.2.6 Signing¶
APEX uses two layers of signing:
-
Payload signing (AVB): The
apex_payload.imgis signed with Android Verified Boot usingavbtool. The public key is bundled asapex_pubkeyinside the ZIP. -
Container signing (APK Signature Scheme v2/v3): The outer ZIP is signed like a regular APK, so the Google Play infrastructure can verify it.
The build system defines keys using the apex_key module type:
// Source: packages/modules/SdkExtensions/Android.bp
apex_key {
name: "com.android.sdkext.key",
public_key: "com.android.sdkext.avbpubkey",
private_key: "com.android.sdkext.pem",
}
android_app_certificate {
name: "com.android.sdkext.certificate",
certificate: "com.android.sdkext",
}
The Soong implementation in build/soong/apex/key.go resolves these keys:
// Source: build/soong/apex/key.go
type apexKeyProperties struct {
// Path or module to the public key file in avbpubkey format.
Public_key *string `android:"path"`
// Path or module to the private key file in pem format.
Private_key *string `android:"path"`
}
Key resolution follows a fallback chain: first the global apex key directory (configurable), then the local module directory.
52.2.7 Build-Time Construction: The apexer Tool¶
The apexer tool (system/apex/apexer/apexer.py) is the command-line utility
that assembles an APEX file from a directory of contents:
# Source: system/apex/apexer/apexer.py (line 16-20)
"""apexer is a command line tool for creating an APEX file, a package
format for system components.
Typical usage: apexer input_dir output.apex
"""
The Soong build rule that invokes apexer is defined in
build/soong/apex/builder.go:
// Source: build/soong/apex/builder.go
apexRule = pctx.StaticRule("apexRule", blueprint.RuleParams{
Command: `rm -rf ${image_dir} && mkdir -p ${image_dir} && ` +
`(. ${out}.copy_commands) && ` +
`APEXER_TOOL_PATH=${tool_path} ` +
`${apexer} --force --manifest ${manifest} ` +
`--file_contexts ${file_contexts} ` +
`--canned_fs_config ${canned_fs_config} ` +
`--include_build_info ` +
`--payload_type image ` +
`--key ${key} ${opt_flags} ${image_dir} ${out} `,
...
})
The process:
- Collect all native libraries, binaries, Java libraries, and config files.
- Copy them into a staging directory (
${image_dir}). -
Create a filesystem image (ext4/f2fs/erofs) using
mke2fs,make_f2fs, ormkfs.erofs. -
Sign the image with
avbtool. - Package everything into a ZIP with
soong_zip. - Sign the ZIP with APK Signature Scheme.
flowchart LR
A[Source files] --> B[Staging directory]
B --> C["mke2fs / mkfs.erofs"]
C --> D[apex_payload.img]
D --> E["avbtool (dm-verity sign)"]
E --> F["soong_zip (ZIP package)"]
F --> G["APK Signature v2/v3"]
G --> H[module.apex]
52.2.8 APEX Module Definition in Soong¶
The apex module type is registered in build/soong/apex/apex.go:
// Source: build/soong/apex/apex.go
func registerApexBuildComponents(ctx android.RegistrationContext) {
ctx.RegisterModuleType("apex", BundleFactory)
ctx.RegisterModuleType("apex_test", TestApexBundleFactory)
ctx.RegisterModuleType("apex_vndk", vndkApexBundleFactory)
ctx.RegisterModuleType("apex_defaults", DefaultsFactory)
ctx.RegisterModuleType("prebuilt_apex", PrebuiltFactory)
ctx.RegisterModuleType("override_apex", OverrideApexFactory)
ctx.RegisterModuleType("apex_set", apexSetFactory)
}
A typical APEX definition in an Android.bp file looks like:
// Source: packages/modules/SdkExtensions/Android.bp
apex {
name: "com.android.sdkext",
defaults: ["com.android.sdkext-defaults"],
bootclasspath_fragments: ["com.android.sdkext-bootclasspath-fragment"],
binaries: [
"derive_classpath",
"derive_sdk",
],
prebuilts: [
"current_sdkinfo",
"extensions_db",
],
manifest: "manifest.json",
}
The apexBundleProperties struct in apex.go exposes the full set of
configurable properties:
// Source: build/soong/apex/apex.go (selected properties)
type apexBundleProperties struct {
Manifest *string `android:"path"`
AndroidManifest proptools.Configurable[string] `android:"path,..."`
File_contexts *string `android:"path"`
// Whether this APEX is updatable (default: true)
Updatable *bool
// Filesystem type: 'ext4', 'f2fs', or 'erofs' (default: 'ext4')
Payload_fs_type *string
// Marks future updatability without enabling it yet
Future_updatable *bool
// Whether this APEX can use platform APIs (only when updatable: false)
Platform_apis *bool
// Variant version of the mainline module (0-9)
Variant_version *string
}
The dependency properties allow an APEX to include different types of content:
// Source: build/soong/apex/apex.go
type ApexNativeDependencies struct {
Native_shared_libs proptools.Configurable[[]string]
Jni_libs proptools.Configurable[[]string]
Rust_dyn_libs []string
Binaries proptools.Configurable[[]string]
Tests []string
Filesystems []string
Prebuilts proptools.Configurable[[]string]
}
52.2.9 Activation at Boot: The apexd Daemon¶
The apexd daemon (system/apex/apexd/) is the system service responsible
for activating APEX modules at boot time. It runs as three distinct service
configurations defined in apexd.rc:
# Source: system/apex/apexd/apexd.rc
service apexd /system/bin/apexd
interface aidl apexservice
class core
user root
group system
oneshot
disabled
reboot_on_failure reboot,apexd-failed
capabilities CHOWN DAC_OVERRIDE DAC_READ_SEARCH FOWNER SYS_ADMIN
service apexd-bootstrap /system/bin/apexd --bootstrap
user root
group system
oneshot
disabled
reboot_on_failure reboot,bootloader,bootstrap-apexd-failed
capabilities SYS_ADMIN
service apexd-snapshotde /system/bin/apexd --snapshotde
user root
group system
oneshot
disabled
capabilities CHOWN DAC_OVERRIDE DAC_READ_SEARCH FOWNER
Three service configurations serve distinct phases:
| Service | Phase | Purpose |
|---|---|---|
apexd-bootstrap |
Early boot | Mount critical APEXes (runtime, tzdata, i18n) before /data |
apexd |
After /data mount |
Mount all remaining APEXes, process staged sessions |
apexd-snapshotde |
After activation | Snapshot/restore DE data for rollback safety |
Boot-Time Activation Flow¶
The MountPackageImpl function in apexd.cpp implements the five-step
mounting process:
// Source: system/apex/apexd/apexd.cpp (MountPackageImpl)
// Steps to mount an APEX file:
//
// 1. create a mount point (directory)
// 2. create a block device for the payload part of the APEX
// 3. wrap it with a dm-verity device if the APEX is not on top of verity
// device
// 4. mount the payload filesystem
// 5. verify the mount
Step 2 creates a loop device from the apex_payload.img entry within the ZIP:
// Source: system/apex/apexd/apexd.cpp
if (IsMountBeforeDataEnabled() && GetImageManager()->IsPinnedApex(apex)) {
linear_dev = OR_RETURN(CreateDmLinearForPayload(apex));
block_device = linear_dev.GetDevPath();
} else {
loop = OR_RETURN(CreateLoopForApex(apex, loop_id));
block_device = loop.name;
}
Step 3 wraps the block device with dm-verity for integrity verification. Pre-
installed APEXes on dm-verity-protected partitions (like /system) can skip
this additional layer:
// Source: system/apex/apexd/apexd.cpp
const bool mount_on_verity = !instance.IsPreInstalledApex(apex) ||
instance.IsDecompressedApex(apex) ||
instance.IsBlockApex(apex);
dm-verity Table Construction¶
The CreateVerityTable function constructs the device-mapper verity table
from the AVB hashtree descriptor embedded in the APEX:
// Source: system/apex/apexd/apexd.cpp
std::unique_ptr<DmTable> CreateVerityTable(
const ApexVerityData& verity_data,
const std::string& block_device,
bool restart_on_corruption) {
AvbHashtreeDescriptor* desc = verity_data.desc.get();
auto table = std::make_unique<DmTable>();
const uint64_t start = 0;
const uint64_t length = desc->image_size / 512; // in sectors
const std::string& hash_device = block_device;
const uint32_t num_data_blocks =
desc->image_size / desc->data_block_size;
const uint32_t hash_start_block =
desc->tree_offset / desc->hash_block_size;
auto target = std::make_unique<DmTargetVerity>(
start, length, desc->dm_verity_version,
block_device, hash_device,
desc->data_block_size, desc->hash_block_size,
num_data_blocks, hash_start_block,
verity_data.hash_algorithm,
verity_data.root_digest,
verity_data.salt);
target->IgnoreZeroBlocks();
if (restart_on_corruption) {
target->SetVerityMode(kDmVerityRestartOnCorruption);
}
table->AddTarget(std::move(target));
table->set_readonly(true);
return table;
}
Key dm-verity parameters:
- Data block size and hash block size -- Typically 4096 bytes each.
- Hash algorithm -- Usually SHA-256.
- Root digest -- The top-level hash that chains to all data blocks.
- Salt -- Random data mixed into the hash computation.
- restart_on_corruption -- If set, the kernel reboots if a verity corruption is detected (as opposed to returning I/O errors).
For pre-installed APEXes on /system (which is already dm-verity protected),
the additional dm-verity layer is skipped to avoid double-verification
overhead. Updated APEXes on /data always get their own dm-verity device.
Filesystem Mounting¶
After the dm-verity device is created, the payload is mounted as a read-only filesystem:
// Source: system/apex/apexd/apexd.cpp
// Step 4. Mount the payload filesystem at the mount point
uint32_t mount_flags = MS_NOATIME | MS_NODEV | MS_DIRSYNC | MS_RDONLY;
if (apex.GetManifest().nocode()) {
mount_flags |= MS_NOEXEC;
}
mount(block_device.c_str(), mount_point.c_str(),
apex.GetFsType().value().c_str(), mount_flags, nullptr);
The mount flags enforce:
MS_RDONLY-- Read-only (APEXes are immutable once activated).MS_NOATIME-- Don't update access times (performance).MS_NODEV-- Don't allow device nodes.MS_DIRSYNC-- Synchronous directory updates.MS_NOEXEC-- Disallow execution (fornoCodeAPEXes like tzdata).
After mounting, Step 5 verifies the mounted image matches the manifest:
// Source: system/apex/apexd/apexd.cpp
// Step 5. After mounting, verify the mounted image
auto status = VerifyMountedImage(apex, mount_point);
if (!status.ok()) {
umount2(mount_point.c_str(), UMOUNT_NOFOLLOW);
return Error() << "Failed to verify " << full_path;
}
The ActivatePackageImpl function then bind-mounts the latest version to
/apex/<package_name>:
// Source: system/apex/apexd/apexd.cpp
Result<void> ActivatePackageImpl(const ApexFile& apex_file, ...) {
// ...
// Bind mount the latest version to /apex/<package_name>.
auto st = gMountedApexes.DoIfLatest(
manifest.name(), apex_file.GetPath(), [&]() -> Result<void> {
return apexd_private::BindMount(
apexd_private::GetActiveMountPoint(manifest), mount_point);
});
// ...
}
After all packages are activated, apexd sets system properties to signal readiness:
// Source: system/apex/apexd/apexd.cpp
void OnAllPackagesActivated() {
LOG(INFO) << "Marking APEXd as activated";
SetProperty(gConfig->apex_status_sysprop, kApexStatusActivated);
}
void OnAllPackagesReady() {
LOG(INFO) << "Marking APEXd as ready";
SetProperty(gConfig->apex_status_sysprop, kApexStatusReady);
SetProperty(kApexAllReadyProp, "true");
}
Other services can wait on these properties:
The following diagram shows the complete boot-time activation flow:
flowchart TD
A["init starts apexd-bootstrap"] --> B["Scan /system/apex,
/system_ext/apex,
/product/apex,
/vendor/apex,
/odm/apex"]
B --> C["Mount bootstrap APEXes
(runtime, tzdata, i18n)"]
C --> D["Set apexd.status = starting"]
D --> E["/data becomes available"]
E --> F["init starts apexd"]
F --> G["Scan /data/apex/active
for updated APEXes"]
G --> H{"Staged session
pending?"}
H -->|Yes| I["Process staged session:
verify, activate new versions"]
H -->|No| J["Activate pre-installed
or previously-updated APEXes"]
I --> J
J --> K["For each APEX:
1. Create loop device
2. Set up dm-verity
3. Mount filesystem
4. Bind-mount to /apex/name"]
K --> L["OnAllPackagesActivated()
apexd.status = activated"]
L --> M["Linker config, classpath
generation complete"]
M --> N["OnAllPackagesReady()
apexd.status = ready"]
52.2.10 The OnBootstrap and OnStart Lifecycle¶
The apexd daemon has two primary entry points corresponding to two boot
phases. Understanding this split is critical for diagnosing boot failures.
Phase 1: OnBootstrap (before /data)
// Source: system/apex/apexd/apexd.cpp
int OnBootstrap() {
ATRACE_NAME("OnBootstrap");
auto time_started = boot_clock::now();
ApexFileRepository& instance = ApexFileRepository::GetInstance();
// Scan /system/apex, /system_ext/apex, /product/apex, /vendor/apex, /odm/apex
if (auto st = AddPreinstalledData(instance); !st.ok()) {
LOG(ERROR) << st.error();
return 1;
}
ActivationContext ctx;
std::vector<ApexFileRef> activation_list;
if (IsMountBeforeDataEnabled()) {
// New flow: wait for coldboot, process sessions, scan data
base::WaitForProperty("ro.cold_boot_done", "true",
std::chrono::seconds(10));
ProcessSessions(ctx);
auto data_apexes = ScanDataApexFiles(GetImageManager());
instance.AddDataApexFiles(std::move(data_apexes));
activation_list = instance.SelectApexForActivation();
} else {
// Legacy flow: only activate bootstrap APEXes
const auto& pre_installed_apexes = instance.GetPreInstalledApexFiles();
for (const auto& apex : pre_installed_apexes) {
if (IsBootstrapApex(apex.get())) {
LOG(INFO) << "Found bootstrap APEX " << apex.get().GetPath();
activation_list.push_back(apex);
}
}
}
auto result = ActivateApexPackages(ctx, activation_list,
ActivationMode::kBootstrapMode, ...);
EmitApexInfoList(result.activated, /*is_bootstrap=*/true);
LOG(INFO) << "OnBootstrap done, duration=" << time_elapsed;
return 0;
}
Bootstrap APEXes (those with bootstrap: true in their manifest) must be
available before /data is mounted because other early services depend on
them. Examples include:
com.android.art-- The runtime itself must be active for zygote.com.android.i18n-- ICU data is needed for text processing.com.android.tzdata-- Time zone data is needed for clock display.
Phase 2: OnStart (after /data)
// Source: system/apex/apexd/apexd.cpp
void OnStart() {
ATRACE_NAME("OnStart");
LOG(INFO) << "Marking APEXd as starting";
SetProperty(gConfig->apex_status_sysprop, kApexStatusStarting);
// Check if filesystem checkpointing needs a rollback
if (gSupportsFsCheckpoints) {
Result<bool> needs_revert = gVoldService->NeedsRollback();
if (needs_revert.ok() && *needs_revert) {
LOG(INFO) << "Exceeded number of session retries. "
<< "Starting a revert";
RevertActiveSessions("", "");
}
}
// Activate remaining APEXes (the ones not in bootstrap)
if (!IsMountBeforeDataEnabled()) {
ActivateApexesOnStart();
}
// Snapshot or restore DE_sys data for rollback support
SnapshotOrRestoreDeSysData();
LOG(INFO) << "OnStart done, duration=" << time_elapsed;
}
The ActivateApexesOnStart() function first processes any pending staged
sessions, then scans /data/apex/active/ for updated APEXes, and activates
the best version of each module:
// Source: system/apex/apexd/apexd.cpp
void ActivateApexesOnStart() {
ActivationContext ctx;
// Process staged sessions first: revert or activate
ProcessSessions(ctx);
auto& instance = ApexFileRepository::GetInstance();
// Scan /data/apex/active/ for updated APEXes
instance.AddDataApex(gConfig->active_apex_data_dir);
auto activate_status = ActivateApexPackages(
ctx, instance.SelectApexForActivation(),
ActivationMode::kBootMode,
/*revert_on_error=*/true, /*fallback_on_error=*/true);
EmitApexInfoList(activate_status.activated, /*is_bootstrap=*/false);
}
The SelectApexForActivation() method compares pre-installed and data APEXes
and picks the highest version for each module name.
Session Processing
The ProcessSessions() function handles pending staged sessions:
// Source: system/apex/apexd/apexd.cpp
void ProcessSessions(ActivationContext& ctx) {
auto sessions = gSessionManager->GetSessions();
if (sessions.empty()) {
LOG(INFO) << "No sessions to revert/activate.";
return;
}
// If there's any pending revert, revert active sessions.
if (std::ranges::any_of(sessions, [](const auto& session) {
return session.GetState() == SessionState::REVERT_IN_PROGRESS;
})) {
RevertActiveSessions("", "");
} else {
// Otherwise, activate STAGED sessions.
ActivateStagedSessions(ctx, std::move(sessions));
}
}
This is the core logic that makes Mainline updates work across reboots: a
session is "staged" before reboot, and then on the next boot, ProcessSessions
activates it.
flowchart TD
PS["ProcessSessions()"] --> CHECK{"Any sessions?"}
CHECK -->|No| DONE["Return: nothing to do"]
CHECK -->|Yes| REVERT_CHECK{"Any in
REVERT_IN_PROGRESS?"}
REVERT_CHECK -->|Yes| REVERT["RevertActiveSessions()"]
REVERT_CHECK -->|No| ACTIVATE["ActivateStagedSessions()"]
ACTIVATE --> HARDLINK["Hardlink staged APEXes
to /data/apex/active/"]
HARDLINK --> REMOVE_OLD["RemovePreviouslyActiveApexFiles()"]
REMOVE_OLD --> DONE2["Sessions processed"]
REVERT --> RESTORE["Restore from
/data/apex/backup/"]
RESTORE --> DONE3["Reverted to previous version"]
52.2.11 The Rebootless Update Path¶
Starting in Android 13, APEXes that declare supportsRebootlessUpdate: true
in their manifest can be updated without a device reboot. The
installAndActivatePackage() AIDL method implements this:
- Verify the new APEX package.
- Unload the current APEX from init (stop services, unload init scripts).
- Unmount the currently active APEX.
- Hard-link the new APEX to
/data/apex/active/. - Activate (mount) the new APEX.
- Reload the APEX into init (restart services, re-read init scripts).
// Source: system/apex/apexd/apexd.cpp (rebootless update, simplified)
// 1. Unload from init
OR_RETURN(UnloadApexFromInit(module_name));
// Scope guard: reload from init whether we succeed or fail
auto reload_apex = android::base::make_scope_guard([&]() {
LoadApexFromInit(module_name);
});
// 2. Unmount currently active APEX
OR_RETURN(UnmountPackage(*cur_apex, /*deferred=*/true, ...));
// 3. Hard-link new APEX to /data/apex/active/
link(package_path.c_str(), target_file.c_str());
// 4. Activate new APEX
ActivatePackageImpl(*new_apex, loop::kFreeLoopId, new_id, false);
This is particularly useful for modules that contain only configuration data or that can gracefully restart their services.
52.2.12 Brand-New APEX Support¶
Android 16 (Baklava) introduces the concept of brand-new APEXes -- APEXes that can be installed on a device even if they were not pre-installed. This allows Google to add entirely new modules to existing devices through Play updates.
The key infrastructure files:
// Source: system/apex/apexd/apex_constants.h
static constexpr const char* kBrandNewApexPublicKeySuffix = ".avbpubkey";
static constexpr const char* kBrandNewApexBlocklistFileName = "blocklist.json";
static constexpr const char* kBrandNewApexConfigSystemDir =
"/system/etc/brand_new_apex";
Each partition can provide a configuration directory containing:
- Public keys (
.avbpubkeyfiles) -- Trusted keys for verifying new APEXes. - Blocklists (
blocklist.json) -- APEXes that should not be installed.
The ApexFileRepository handles brand-new APEX verification:
// Source: system/apex/apexd/apexd.cpp (AddPreinstalledData)
if (ApexFileRepository::IsBrandNewApexEnabled()) {
instance.AddBrandNewApexCredentialAndBlocklist(
gConfig->brand_new_apex_config_dirs);
}
52.2.13 Prebuilt APEX and the Deapexer¶
In addition to building APEXes from source, the build system supports prebuilt APEXes. This is common when a module is built in a separate build pipeline and the result is checked into the tree as a binary:
// Source: build/soong/apex/prebuilt.go
var (
extractMatchingApex = pctx.StaticRule(
"extractMatchingApex",
blueprint.RuleParams{
Command: `${extract_apks} -o "${out}" ` +
`-allow-prereleased=${allow-prereleased} ` +
`-sdk-version=${sdk-version} ` +
`-skip-sdk-check=${skip-sdk-check} ` +
`-abis=${abis} ` +
`-screen-densities=all -extract-single ` +
`${in}`,
},
"abis", "allow-prereleased", "sdk-version", "skip-sdk-check")
decompressApex = pctx.StaticRule("decompressApex",
blueprint.RuleParams{
Command: `${deapexer} decompress ` +
`--copy-if-uncompressed ` +
`--input ${in} --output ${out}`,
})
)
A prebuilt APEX is declared as:
When the build system needs to access the contents of a prebuilt APEX (e.g., to
compile against a bootclasspath fragment's JAR files), it uses the deapexer
tool to extract the contents:
# Extract all contents
$ deapexer extract com.android.art.apex output_dir/
# List contents
$ deapexer list com.android.art.apex
# Get APEX info (name, version)
$ deapexer info com.android.art.apex
The deapexer tool understands the APEX format at a low level: it opens the
ZIP, finds the payload image, mounts or reads it (using debugfs for ext4 or
fsck.erofs for erofs), and extracts the files.
This two-way flow -- building APEXes with apexer and decomposing them with
deapexer -- enables the modular development workflow where different teams
can work on different modules and integrate through prebuilt artifacts.
flowchart LR
subgraph "Module Team A"
SRC_A["Source code"] --> BUILD_A["Build APEX"]
BUILD_A --> APEX_A["com.android.art.apex"]
end
subgraph "Integration Build"
APEX_A --> DEAPEX["deapexer extract"]
DEAPEX --> JARS["bootclasspath JARs"]
DEAPEX --> LIBS["native stubs"]
JARS --> COMPILE["Compile other modules"]
LIBS --> COMPILE
end
52.2.14 APEX Mutation and Variants¶
The Soong build system creates APEX-specific variants of all modules that are
included in an APEX. This is handled by the apexTransitionMutator:
// Source: build/soong/apex/apex.go
func RegisterPostDepsMutators(ctx android.RegisterMutatorsContext) {
ctx.BottomUp("apex_unique", apexUniqueVariationsMutator)
ctx.BottomUp("mark_platform_availability",
markPlatformAvailability)
ctx.InfoBasedTransition("apex",
android.NewGenericTransitionMutatorAdapter(
&apexTransitionMutator{}))
}
This mutator creates separate build variants for each library: one for the platform and one for each APEX that includes it. The APEX variant may have different compilation flags, particularly:
- Different
min_sdk_versionrestrictions. - Different symbol visibility (for NDK compliance in updatable modules).
- Different linking behavior (static vs. dynamic for DCLA).
For example, libc++ might have:
- A platform variant linking against system
libc. - A com.android.tethering variant compiled for
min_sdk_version: 30. - A com.android.art variant compiled for
min_sdk_version: 31.
52.2.15 APEX Directory Layout on Device¶
When an APEX is activated, its contents become visible under /apex/:
/apex/
+-- apex-info-list.xml # Metadata about all activated APEXes
+-- com.android.sdkext/ # Active (latest) version
| +-- bin/
| | +-- derive_classpath
| | +-- derive_sdk
| +-- etc/
| | +-- sdkinfo.pb
| | +-- extensions_db
| +-- javalib/
| +-- framework-sdkextensions.jar
+-- com.android.sdkext@340090000/ # Versioned mount point
+-- com.android.tethering/
+-- com.android.permission/
+-- ...
The versioned mount point (@<version>) preserves the actual mounted
filesystem. The unversioned path (/apex/com.android.sdkext/) is a bind
mount pointing to the highest-version mount point.
52.2.16 APEX Partition Locations¶
APEX files can be pre-installed on multiple partitions:
// Source: system/apex/apexd/apex_constants.h
static constexpr const char* kApexPackageSystemDir = "/system/apex";
static constexpr const char* kApexPackageSystemExtDir = "/system_ext/apex";
static constexpr const char* kApexPackageProductDir = "/product/apex";
static constexpr const char* kApexPackageVendorDir = "/vendor/apex";
static constexpr const char* kApexPackageOdmDir = "/odm/apex";
Updated APEXes (from Google Play or adb install) are stored in:
// Source: system/apex/apexd/apex_constants.h
static constexpr const char* kActiveApexPackagesDataDir = "/data/apex/active";
static constexpr const char* kApexBackupDir = "/data/apex/backup";
The ApexFileRepository class (system/apex/apexd/apex_file_repository.h)
manages the mapping between pre-installed and updated APEXes:
// Source: system/apex/apexd/apex_file_repository.h
class ApexFileRepository final {
public:
static ApexFileRepository& GetInstance();
// Populate from pre-installed directories
android::base::Result<void> AddPreInstalledApex(
const std::unordered_map<ApexPartition, std::string>&
partition_to_prebuilt_dirs);
// Query methods
bool IsPreInstalledApex(const ApexFile& apex) const;
bool IsDecompressedApex(const ApexFile& apex) const;
bool IsBlockApex(const ApexFile& apex) const;
};
52.2.17 Rollback and Safety¶
If an updated APEX fails to activate, apexd supports rollback:
-
Backup: Before staging,
BackupActiveApexes()copies current active APEXes to/data/apex/backup/. -
Verification:
VerifyPackagesStagedInstall()validates signatures and compatibility before any activation. -
Checkpoint: On devices with filesystem checkpoint support (via vold), the entire
/datastate can be rolled back. -
Revert-on-failure: If
reboot_on_failuretriggers, the next boot skips the failed update and uses the pre-installed version.
// Source: system/apex/apexd/apexd.cpp (SubmitStagedSession)
OR_RETURN(BackupActiveApexes());
auto ret = OR_RETURN(
OpenApexFilesInSessionDirs(session_id, child_session_ids));
auto result = OR_RETURN(VerifyPackagesStagedInstall(ret));
52.3 Module Catalog¶
The packages/modules/ directory in AOSP contains the source code for all
Mainline modules. Each module typically produces one or more APEX packages.
52.3.1 Complete Module Inventory¶
The following table lists every module directory in packages/modules/, its
APEX package name(s), the Android release in which it became updatable, and a
summary of what it provides.
| # | Module Directory | APEX Name | Launch | Description |
|---|---|---|---|---|
| 1 | AdServices |
com.android.adservices |
T (13) | Privacy Sandbox: Topics, Attribution, FLEDGE |
| 2 | AppSearch |
com.android.appsearch |
T (13) | On-device structured search indexing engine |
| 3 | ArtPrebuilt |
com.android.art |
S (12) | Android Runtime (ART), DEX compiler, core libraries |
| 4 | Bluetooth |
com.android.bt |
B (16) | Bluetooth stack (Gabeldorsche / Fluoride) |
| 5 | CaptivePortalLogin |
(APK in Connectivity) | R (11) | Captive portal detection & sign-in UI |
| 6 | CellBroadcastService |
com.android.cellbroadcast |
R (11) | Emergency alert message handling (CMAS/ETWS) |
| 7 | ConfigInfrastructure |
com.android.configinfrastructure |
U (14) | Device configuration framework (DeviceConfig) |
| 8 | Connectivity |
com.android.tethering |
R (11) | Tethering, Connectivity, Cronet HTTP stack |
| 9 | CrashRecovery |
com.android.crashrecovery |
V (15) | System crash detection and recovery |
| 10 | DeviceLock |
com.android.devicelock |
U (14) | Device financing/locking framework |
| 11 | DnsResolver |
com.android.resolv |
Q (10) | DNS resolution (DNS-over-TLS, private DNS) |
| 12 | ExtServices |
com.android.extservices |
R (11) | Extension services (notification ranking, autofill) |
| 13 | GeoTZ |
com.android.geotz |
S (12) | Geolocation-based time zone detection |
| 14 | Gki |
com.android.gki.* |
S (12) | Generic Kernel Image support modules |
| 15 | HealthFitness |
com.android.healthfitness |
U (14) | Health Connect: health/fitness data platform |
| 16 | IPsec |
com.android.ipsec |
R (11) | IKEv2/IPsec VPN framework |
| 17 | ImsMedia |
(in Telephony) | T (13) | IMS media handling for VoLTE/VoNR |
| 18 | IntentResolver |
(APK) | T (13) | Chooser/intent resolution UI |
| 19 | Media |
com.android.media / com.android.media.swcodec |
Q (10) | Media framework, software codecs |
| 20 | ModuleMetadata |
(APK) | Q (10) | Module metadata provider |
| 21 | NetworkStack |
com.android.networkstack |
Q (10) | Network connectivity evaluation, DHCP client |
| 22 | NeuralNetworks |
com.android.neuralnetworks |
R (11) | NNAPI runtime and HAL |
| 23 | Nfc |
com.android.nfcservices |
B (16) | NFC stack and services |
| 24 | OnDevicePersonalization |
com.android.ondevicepersonalization |
T (13) | On-device ML personalization framework |
| 25 | Permission |
com.android.permission |
R (11) | Permission controller, role manager, SafetyCenter |
| 26 | Profiling |
com.android.profiling |
V (15) | System profiling infrastructure |
| 27 | RemoteKeyProvisioning |
com.android.rkpd |
U (14) | Remote key provisioning for KeyStore |
| 28 | RuntimeI18n |
com.android.i18n |
Q (10) | ICU internationalization library |
| 29 | Scheduling |
com.android.scheduling |
S (12) | Job scheduling infrastructure |
| 30 | SdkExtensions |
com.android.sdkext |
R (11) | SDK extension version management |
| 31 | StatsD |
com.android.os.statsd |
R (11) | Metrics collection daemon |
| 32 | Telecom |
com.android.telecom |
V (15) | Telecom call management framework |
| 33 | Telephony |
com.android.telephonycore / com.android.telephonymodules |
U (14) | Telephony core and modules |
| 34 | ThreadNetwork |
com.android.threadnetwork |
V (15) | Thread / Matter smart home networking |
| 35 | UprobeStats |
com.android.uprobestats |
B (16) | eBPF-based uprobe statistics collection |
| 36 | Uwb |
com.android.uwb |
T (13) | Ultra-Wideband ranging framework |
| 37 | Virtualization |
com.android.virt |
T (13) | Android Virtualization Framework (pKVM, Microdroid) |
| 38 | Wifi |
com.android.wifi |
R (11) | Wi-Fi framework and services |
| 39 | adb |
com.android.adbd |
R (11) | Android Debug Bridge daemon |
| 40 | desktop |
(multiple) | V (15) | Desktop windowing support |
In addition to packages/modules/, several APEX modules are defined elsewhere
in the tree:
| APEX Name | Source Location | Description |
|---|---|---|
com.android.media |
frameworks/av/apex/ |
Media framework (extractors, codecs) |
com.android.media.swcodec |
frameworks/av/apex/ |
Software codec process |
com.android.conscrypt |
external/conscrypt/ |
TLS/SSL provider (BoringSSL wrapper) |
com.android.mediaprovider |
packages/providers/MediaProvider/ |
MediaStore content provider |
com.android.tzdata |
system/timezone/ |
Time zone data |
52.3.2 Module Classification by Content Type¶
Mainline modules can be classified by what they primarily contain:
Native-heavy modules (primarily C/C++ shared libraries and binaries):
| Module | APEX Name | Key Native Components |
|---|---|---|
| DnsResolver | com.android.resolv |
libnetd_resolv.so |
| NeuralNetworks | com.android.neuralnetworks |
NNAPI runtime, HAL client |
| adb | com.android.adbd |
adbd binary |
| RuntimeI18n | com.android.i18n |
ICU libraries |
Java-heavy modules (primarily JAR files with bootclasspath/systemserver contributions):
| Module | APEX Name | Key Java Components |
|---|---|---|
| Permission | com.android.permission |
framework-permission.jar, PermissionController app |
| AppSearch | com.android.appsearch |
framework-appsearch.jar, service-appsearch.jar |
| SdkExtensions | com.android.sdkext |
framework-sdkextensions.jar |
| AdServices | com.android.adservices |
Privacy Sandbox Java framework |
| ConfigInfrastructure | com.android.configinfrastructure |
framework-configinfrastructure.jar |
Mixed modules (both native and Java components):
| Module | APEX Name | Key Mixed Components |
|---|---|---|
| Connectivity | com.android.tethering |
framework-connectivity.jar + libnetd_updatable.so |
| Media | com.android.media |
framework-media.jar + media extractors (native) |
| Wifi | com.android.wifi |
framework-wifi.jar + native HAL bridge |
| Bluetooth | com.android.bt |
framework-bluetooth.jar + native stack |
| Telephony | com.android.telephonycore |
framework-telephony.jar + RIL components |
| Virtualization | com.android.virt |
VirtualizationService + native hypervisor support |
Data-only modules (configuration/data with noCode: true):
| Module | APEX Name | Content |
|---|---|---|
| GeoTZ | com.android.geotz |
Geolocation time zone database |
| tzdata | com.android.tzdata |
IANA time zone data |
52.3.3 Module Architecture Diagram¶
graph TB
subgraph "Google Play System Updates"
GP[Google Play Infrastructure]
end
subgraph "Mainline Modules (APEX)"
direction LR
subgraph "Networking"
CONN["com.android.tethering<br/>Connectivity"]
DNS["com.android.resolv<br/>DNS Resolver"]
NET[NetworkStack]
WIFI["com.android.wifi<br/>Wi-Fi"]
NFC["com.android.nfcservices<br/>NFC"]
UWB["com.android.uwb<br/>UWB"]
THR["com.android.threadnetwork<br/>Thread"]
end
subgraph "Security & Privacy"
PERM["com.android.permission<br/>Permission"]
CONSCRYPT["com.android.conscrypt<br/>TLS/SSL"]
IPSEC["com.android.ipsec<br/>IPsec"]
ADS["com.android.adservices<br/>Ad Services"]
RKP["com.android.rkpd<br/>Remote Key"]
end
subgraph "Runtime & Core"
ART["com.android.art<br/>ART Runtime"]
I18N["com.android.i18n<br/>ICU"]
SDKEXT["com.android.sdkext<br/>SDK Extensions"]
STATSD["com.android.os.statsd<br/>StatsD"]
end
subgraph "Media & ML"
MEDIA["com.android.media<br/>Media"]
SWCODEC["com.android.media.swcodec<br/>SW Codecs"]
NNAPI["com.android.neuralnetworks<br/>Neural Networks"]
end
subgraph "Telephony"
TCORE["com.android.telephonycore<br/>Telephony"]
TELECOM["com.android.telecom<br/>Telecom"]
end
end
GP --> CONN
GP --> ART
GP --> PERM
GP --> MEDIA
GP --> TCORE
subgraph "apexd"
APEXD[APEX Daemon]
end
APEXD --> |"mount /apex/..."| CONN
APEXD --> |"mount /apex/..."| ART
APEXD --> |"mount /apex/..."| PERM
52.3.4 Deep Dive: Connectivity Module¶
The Connectivity module (com.android.tethering) is one of the most complex
Mainline modules. Despite its APEX name referencing "tethering," it actually
encompasses the entire connectivity stack:
// Source: packages/modules/Connectivity/Tethering/apex/Android.bp
apex {
name: "com.android.tethering",
defaults: [
"ConnectivityApexDefaults",
"CronetInTetheringApexDefaults",
"r-launched-apex-module",
],
compile_multilib: "both",
bootclasspath_fragments: [
"com.android.tethering-bootclasspath-fragment",
],
systemserverclasspath_fragments: [
"com.android.tethering-systemserverclasspath-fragment",
],
multilib: {
first: {
jni_libs: [
"libservice-connectivity",
"libservice-thread-jni",
...
],
native_shared_libs: [
"libcom.android.tethering.dns_helper",
"libcom.android.tethering.connectivity_native",
"libnetd_updatable",
],
},
},
}
Key components within this module:
- Tethering service: USB, Wi-Fi, Bluetooth, and Ethernet tethering
-
Connectivity service fragments:
framework-connectivityandservice-connectivity -
Cronet: Google's HTTP stack (optionally bundled)
- clatd: CLAT NAT64 translator (native binary)
- Thread Network: IEEE 802.15.4 Thread support (JNI library)
52.3.5 Deep Dive: Permission Module¶
The Permission module manages the runtime permission subsystem:
// Source: packages/modules/Permission/Android.bp
apex {
name: "com.android.permission",
defaults: ["com.android.permission-defaults"],
manifest: "apex_manifest.json",
}
apex_defaults {
name: "com.android.permission-defaults",
defaults: ["r-launched-apex-module"],
// Indicates that pre-installed version can be compressed.
// Actual compression is per-device.
...
}
This module contains:
- PermissionController: The system app that manages permission grants
- Role Manager: The system for declaring and assigning default apps
- Safety Center: The unified security & privacy dashboard (Android 13+)
52.3.6 Deep Dive: Virtualization Module¶
The Virtualization module (com.android.virt) is notable for its conditional
build configuration:
// Source: packages/modules/Virtualization/apex/Android.bp
virt_apex {
name: "com.android.virt",
soong_config_variables: {
avf_enabled: {
defaults: ["com.android.virt_avf_enabled"],
conditions_default: {
defaults: ["com.android.virt_avf_disabled"],
},
},
},
}
apex_defaults {
name: "com.android.virt_common",
updatable: false,
future_updatable: false,
platform_apis: true, // Can use non-public APIs
...
}
Unlike most Mainline modules, the Virtualization APEX is explicitly not
updatable today (updatable: false), and it uses platform_apis: true to
access internal platform APIs. It contains the pKVM (protected KVM)
hypervisor support, Microdroid filesystem images, and the
VirtualizationService.
52.3.7 Deep Dive: StatsD Module¶
The StatsD module (com.android.os.statsd) is the metrics collection daemon:
// Source: packages/modules/StatsD/apex/Android.bp
apex {
name: "com.android.os.statsd",
defaults: ["com.android.os.statsd-defaults"],
manifest: "apex_manifest.json",
}
apex_defaults {
name: "com.android.os.statsd-defaults",
defaults: ["r-launched-apex-module"],
...
}
StatsD is responsible for:
- Collecting system metrics (CPU, memory, battery, network statistics).
- Processing metric subscriptions from apps and system services.
- Providing the
android.app.StatsManagerAPI. - Forwarding metrics to the server-side analytics pipeline.
Its launch in R (Android 11) was significant because it moved a core platform service into a Mainline module, allowing Google to fix metrics collection bugs and add new atom definitions without a full platform OTA.
52.3.8 Deep Dive: DnsResolver Module¶
The DNS resolver was one of the original Mainline modules in Android 10:
// Source: packages/modules/DnsResolver/apex/Android.bp
apex {
name: "com.android.resolv",
manifest: "manifest.json",
multilib: { ... },
}
This module contains libnetd_resolv.so, the native library that handles all
DNS resolution on the device. Key features updatable through Mainline:
- DNS-over-TLS (DoT) support.
- DNS-over-HTTPS (DoH) support.
- Private DNS configuration.
- Bug fixes for DNS cache poisoning vulnerabilities.
Being a pure native module (no Java code), com.android.resolv is one of the
simpler APEX structures -- it contains only shared libraries and no
bootclasspath fragments.
52.3.9 Deep Dive: Profiling Module¶
The Profiling module, launched in Baklava, demonstrates a modern module definition with conditional enablement:
// Source: packages/modules/Profiling/apex/Android.bp
apex {
enabled: select(
release_flag("RELEASE_PACKAGE_PROFILING_MODULE"), {
true: true,
false: false,
}),
name: "com.android.profiling",
manifest: "manifest.json",
key: "com.android.profiling.key",
certificate: ":com.android.profiling.certificate",
defaults: ["b-launched-apex-module"],
binaries: ["trace_redactor"],
bootclasspath_fragments: [
"com.android.profiling-bootclasspath-fragment"
],
systemserverclasspath_fragments: [
"com.android.profiling-systemserverclasspath-fragment"
],
}
Notable aspects:
-
Conditional build: Uses
select(release_flag(...))to enable/disable based on a release flag, allowing the module to be excluded from certain build configurations. -
Native binary: Includes
trace_redactor, a tool for redacting PII from system traces. -
Both classpath fragments: Contributes to both the boot classpath and the system server classpath.
-
B-launched: Uses the
b-launched-apex-moduledefaults, meaningmin_sdk_version: "36".
52.3.10 Deep Dive: adb Module¶
The ADB daemon module is notable for using DCLA (Dynamic Common Lib APEX):
// Source: packages/modules/adb/apex/Android.bp
apex {
name: "com.android.adbd",
defaults: [
"com.android.adbd-defaults",
"r-launched-dcla-enabled-apex-module",
],
...
}
By using DCLA, the adb APEX can share common libraries (like libc++) with
other APEXes instead of bundling its own copy, reducing the total on-device
storage footprint.
52.3.11 Module Lifecycle Across Releases¶
The following diagram shows how the number of Mainline modules has grown across Android releases:
gantt
title Mainline Module Introduction Timeline
dateFormat YYYY
axisFormat %Y
section Q (Android 10)
DnsResolver, Media, I18n, NetworkStack : 2019, 1y
section R (Android 11)
Permission, Wifi, StatsD, IPsec, ExtServices : 2020, 1y
Connectivity, adb, NeuralNetworks, CellBroadcast : 2020, 1y
section S (Android 12)
ART Runtime, GeoTZ, Scheduling, SdkExtensions : 2021, 1y
section T (Android 13)
AdServices, AppSearch, Uwb, OnDevicePersonalization : 2022, 1y
Virtualization, IntentResolver : 2022, 1y
section U (Android 14)
ConfigInfrastructure, HealthFitness : 2023, 1y
DeviceLock, RemoteKeyProvisioning, Telephony : 2023, 1y
section V (Android 15)
Telecom, ThreadNetwork, Profiling, CrashRecovery : 2024, 1y
section B (Android 16)
Bluetooth, Nfc, UprobeStats : 2025, 1y
52.3.12 Deep Dive: Media Module¶
The Media module is split across two APEXes defined in frameworks/av/apex/:
// Source: frameworks/av/apex/Android.bp
apex {
name: "com.android.media",
manifest: "manifest.json",
defaults: ["com.android.media-defaults"],
...
}
apex {
name: "com.android.media.swcodec",
manifest: "manifest_codec.json",
defaults: ["com.android.media.swcodec-defaults"],
...
}
-
com.android.media-- The main media APEX containing extractors, the media framework service, andframework-media.jar. -
com.android.media.swcodec-- A separate process for software codecs (isolated for security viamediaswcodecservice).
This separation is a security measure: software codec bugs (often triggered by untrusted media files) are contained in a separate, sandboxed process.
52.4 SDK Extensions¶
52.4.1 The Problem: API Availability at Runtime¶
Mainline modules are updated independently of the platform. This means a device running Android 12 (S) might have an R-era module or a much newer one. How can an app know which APIs are actually available?
Traditional Build.VERSION.SDK_INT checks tell you the platform version but
say nothing about the module version. The SDK Extensions mechanism fills
this gap.
52.4.2 How It Works¶
Each Android release defines an extension version -- an integer that
advances as modules in that "train" are updated together. The version is
derived at boot time by the derive_sdk binary, which inspects the actual
versions of installed modules.
The derive_sdk process:
- Reads
sdkinfo.pbfrom each mounted APEX (at/apex/<name>/etc/sdkinfo.pb). - Reads the
extensions_db(a protobuf database of version requirements). -
For each extension (R, S, T, U, V, B, ad_services), calculates the highest version where all module requirements are met.
-
Sets system properties:
build.version.extensions.r,.s,.t, etc.
// Source: packages/modules/SdkExtensions/derive_sdk/derive_sdk.cpp
static const std::unordered_map<std::string, SdkModule> kApexNameToModule = {
{"com.android.adservices", SdkModule::AD_SERVICES},
{"com.android.appsearch", SdkModule::APPSEARCH},
{"com.android.art", SdkModule::ART},
{"com.android.configinfrastructure", SdkModule::CONFIG_INFRASTRUCTURE},
{"com.android.conscrypt", SdkModule::CONSCRYPT},
{"com.android.extservices", SdkModule::EXT_SERVICES},
{"com.android.healthfitness", SdkModule::HEALTH_FITNESS},
{"com.android.ipsec", SdkModule::IPSEC},
{"com.android.media", SdkModule::MEDIA},
{"com.android.mediaprovider", SdkModule::MEDIA_PROVIDER},
{"com.android.neuralnetworks", SdkModule::NEURAL_NETWORKS},
{"com.android.ondevicepersonalization", SdkModule::ON_DEVICE_PERSONALIZATION},
{"com.android.permission", SdkModule::PERMISSIONS},
{"com.android.scheduling", SdkModule::SCHEDULING},
{"com.android.sdkext", SdkModule::SDK_EXTENSIONS},
{"com.android.os.statsd", SdkModule::STATSD},
{"com.android.tethering", SdkModule::TETHERING},
};
Each extension train is associated with a set of modules:
// Source: packages/modules/SdkExtensions/derive_sdk/derive_sdk.cpp
static const std::unordered_set<SdkModule> kRModules = {
SdkModule::CONSCRYPT, SdkModule::EXT_SERVICES,
SdkModule::IPSEC, SdkModule::MEDIA,
SdkModule::MEDIA_PROVIDER, SdkModule::PERMISSIONS,
SdkModule::SDK_EXTENSIONS, SdkModule::STATSD,
SdkModule::TETHERING,
};
static const std::unordered_set<SdkModule> kSModules = {
SdkModule::ART, SdkModule::SCHEDULING
};
static const std::unordered_set<SdkModule> kTModules = {
SdkModule::AD_SERVICES, SdkModule::APPSEARCH,
SdkModule::ON_DEVICE_PERSONALIZATION
};
static const std::unordered_set<SdkModule> kUModules = {
SdkModule::CONFIG_INFRASTRUCTURE, SdkModule::HEALTH_FITNESS
};
static const std::unordered_set<SdkModule> kVModules = {};
static const std::unordered_set<SdkModule> kBModules = {
SdkModule::NEURAL_NETWORKS
};
52.4.3 Version Derivation Algorithm¶
The GetSdkLevel function determines the highest extension version whose
requirements are all met:
// Source: packages/modules/SdkExtensions/derive_sdk/derive_sdk.cpp
int GetSdkLevel(const ExtensionDatabase& db,
const std::unordered_set<SdkModule>& relevant_modules,
const std::unordered_map<SdkModule, int>& module_versions) {
int max = 0;
for (const auto& ext_version : db.versions()) {
if (ext_version.version() > max &&
VersionRequirementsMet(ext_version, relevant_modules,
module_versions)) {
max = ext_version.version();
}
}
return max;
}
And VersionRequirementsMet checks each module individually:
// Source: packages/modules/SdkExtensions/derive_sdk/derive_sdk.cpp
bool VersionRequirementsMet(
const ExtensionVersion& ext_version,
const std::unordered_set<SdkModule>& relevant_modules,
const std::unordered_map<SdkModule, int>& module_versions) {
for (const auto& requirement : ext_version.requirements()) {
if (relevant_modules.find(requirement.module()) ==
relevant_modules.end())
continue;
auto version = module_versions.find(requirement.module());
if (version == module_versions.end()) return false;
if (version->second < requirement.version().version())
return false;
}
return true;
}
The algorithm: for each extension level (R, S, T, ...), iterate over all defined versions in the database. For each version number, check if every required module meets the minimum version threshold. The highest passing version number becomes the extension level.
flowchart TD
A["derive_sdk starts at boot"] --> B["Read sdkinfo.pb from
each /apex/*/etc/sdkinfo.pb"]
B --> C["Build module_versions map:
{CONSCRYPT: 12, MEDIA: 8, ...}"]
C --> D["Read extensions_db
(version requirements database)"]
D --> E["For each extension (R, S, T, ...)"]
E --> F["For each version in db.versions()"]
F --> G{"All required modules
meet minimum version?"}
G -->|Yes| H["Track as candidate max"]
G -->|No| I["Skip this version"]
H --> F
I --> F
F --> J["Set property:
build.version.extensions.r = max"]
J --> E
52.4.4 The Java API: SdkExtensions Class¶
Apps query extension versions through android.os.ext.SdkExtensions:
// Source: packages/modules/SdkExtensions/java/android/os/ext/SdkExtensions.java
public class SdkExtensions {
public static final int AD_SERVICES = 1_000_000;
private static final int R_EXTENSION_INT;
private static final int S_EXTENSION_INT;
private static final int T_EXTENSION_INT;
private static final int U_EXTENSION_INT;
private static final int V_EXTENSION_INT;
private static final int B_EXTENSION_INT;
private static final int AD_SERVICES_EXTENSION_INT;
static {
R_EXTENSION_INT = SystemProperties.getInt(
"build.version.extensions.r", 0);
S_EXTENSION_INT = SystemProperties.getInt(
"build.version.extensions.s", 0);
T_EXTENSION_INT = SystemProperties.getInt(
"build.version.extensions.t", 0);
U_EXTENSION_INT = SystemProperties.getInt(
"build.version.extensions.u", 0);
V_EXTENSION_INT = SystemProperties.getInt(
"build.version.extensions.v", 0);
B_EXTENSION_INT = SystemProperties.getInt(
"build.version.extensions.b", 0);
AD_SERVICES_EXTENSION_INT = SystemProperties.getInt(
"build.version.extensions.ad_services", 0);
}
/**
* Return the version of the specified extensions.
*
* Example:
* if (getExtensionVersion(VERSION_CODES.R) >= 3) {
* // Safely use API available since R extensions version 3
* }
*/
public static int getExtensionVersion(@Extension int extension) {
if (extension < VERSION_CODES.R) {
throw new IllegalArgumentException(
"not a valid extension: " + extension);
}
if (extension == VERSION_CODES.R) return R_EXTENSION_INT;
if (extension == VERSION_CODES.S) return S_EXTENSION_INT;
if (extension == VERSION_CODES.TIRAMISU) return T_EXTENSION_INT;
if (extension == VERSION_CODES.UPSIDE_DOWN_CAKE) return U_EXTENSION_INT;
if (extension == VERSION_CODES.VANILLA_ICE_CREAM) return V_EXTENSION_INT;
if (extension == VERSION_CODES.BAKLAVA) return B_EXTENSION_INT;
if (extension == AD_SERVICES) return AD_SERVICES_EXTENSION_INT;
return 0;
}
public static Map<Integer, Integer> getAllExtensionVersions() {
return ALL_EXTENSION_INTS;
}
}
52.4.5 Using Extension Versions in App Code¶
The typical pattern for an app developer:
import android.os.Build;
import android.os.ext.SdkExtensions;
// Check if a T-extensions API (added in extension version 5) is available
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
SdkExtensions.getExtensionVersion(Build.VERSION_CODES.TIRAMISU) >= 5) {
// Safe to use the API
useNewTiramisuApi();
}
The @RequiresExtension annotation (from AndroidX) makes this more ergonomic:
@RequiresExtension(extension = Build.VERSION_CODES.TIRAMISU, version = 5)
public void useNewTiramisuApi() {
// ...
}
52.4.6 Worked Example: How Extension Version is Computed¶
Let us walk through a concrete example of how derive_sdk computes the R
extension version.
Input: The extensions_db contains the following (simplified) entries:
version: 5
requirements:
- module: CONSCRYPT, min_version: 10
- module: MEDIA, min_version: 8
- module: PERMISSIONS, min_version: 12
- module: TETHERING, min_version: 6
version: 6
requirements:
- module: CONSCRYPT, min_version: 11
- module: MEDIA, min_version: 9
- module: PERMISSIONS, min_version: 14
- module: TETHERING, min_version: 7
Installed module versions (from sdkinfo.pb in each APEX):
Evaluation:
For version 5:
- CONSCRYPT 12 >= 10 -- pass
- MEDIA 9 >= 8 -- pass
- PERMISSIONS 14 >= 12 -- pass
- TETHERING 6 >= 6 -- pass
- All met: version 5 is a candidate.
For version 6:
- CONSCRYPT 12 >= 11 -- pass
- MEDIA 9 >= 9 -- pass
- PERMISSIONS 14 >= 14 -- pass
- TETHERING 6 >= 7 -- FAIL (6 < 7)
- Not all met: version 6 is rejected.
Result: R extension version = 5.
The TETHERING module at version 6 does not meet the minimum requirement of 7 for extension version 6. If Google pushes a Connectivity module update that brings TETHERING to version 7 or higher, the R extension version would automatically advance to 6 on the next reboot.
This mechanism ensures that apps can trust extension version checks: if
SdkExtensions.getExtensionVersion(R) >= 6, then all modules in the R
train are at or above the required versions, and all APIs introduced in R
extension 6 are available.
52.4.7 Ad Services Extension¶
The Ad Services extension is a special case: it has its own independent
extension track (AD_SERVICES = 1_000_000) because the Privacy Sandbox
APIs evolve on a different cadence from the platform extensions:
// Source: packages/modules/SdkExtensions/java/android/os/ext/SdkExtensions.java
public static final int AD_SERVICES = 1_000_000;
// In the static initializer:
AD_SERVICES_EXTENSION_INT = SystemProperties.getInt(
"build.version.extensions.ad_services", 0);
if (SdkLevel.isAtLeastT()) {
extensions.put(AD_SERVICES, AD_SERVICES_EXTENSION_INT);
}
Apps that use Privacy Sandbox APIs check:
if (SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES) >= 4) {
// Safe to use Ad Services API from extension version 4
}
52.4.8 The SdkExtensions APEX¶
The SdkExtensions module itself is packaged as an APEX:
// Source: packages/modules/SdkExtensions/Android.bp
apex {
name: "com.android.sdkext",
defaults: ["com.android.sdkext-defaults"],
bootclasspath_fragments: [
"com.android.sdkext-bootclasspath-fragment"
],
binaries: [
"derive_classpath",
"derive_sdk",
],
prebuilts: [
"current_sdkinfo",
"extensions_db",
],
manifest: "manifest.json",
}
It bundles:
derive_sdk-- The binary that computes extension versions at boot.-
derive_classpath-- A binary that generates the DEX2OAT boot classpath configuration based on installed modules. -
extensions_db-- The protobuf database of version requirements. framework-sdkextensions.jar-- The Java API (SdkExtensionsclass).
52.4.9 Extension Version Lifecycle¶
sequenceDiagram
participant Boot as Boot Sequence
participant DS as derive_sdk
participant Props as System Properties
participant App as Application
Boot->>DS: Execute at boot (via .rc service)
DS->>DS: Read /apex/*/etc/sdkinfo.pb
DS->>DS: Read extensions_db
DS->>DS: Compute version for each extension
DS->>Props: Set build.version.extensions.r = 14
DS->>Props: Set build.version.extensions.s = 12
DS->>Props: Set build.version.extensions.t = 8
DS->>Props: Set build.version.extensions.u = 3
DS->>Props: Set build.version.extensions.v = 1
DS->>Props: Set build.version.extensions.b = 0
Note over Props: Properties available system-wide
App->>Props: SdkExtensions.getExtensionVersion(R)
Props->>App: 14
52.5 Module Boundaries¶
Mainline modules must coexist with both the platform and other modules while maintaining strict interface boundaries. This section explains the rules that govern what can and cannot be inside a module.
52.5.1 The apex_available Property¶
Every library, binary, or app that wants to be included in an APEX must declare which APEXes it is available for:
// Source: build/soong/android/apex.go
type ApexProperties struct {
// Availability of this module in APEXes. Only the listed APEXes
// can contain this module.
//
// "//apex_available:anyapex" matches any APEX.
// "//apex_available:platform" refers to non-APEX partitions.
// Prefix pattern (com.foo.*) can match any APEX with that prefix.
// Default is ["//apex_available:platform"].
Apex_available []string
}
For example, a library that should be available to both the platform and the Connectivity APEX would declare:
cc_library {
name: "libnetutils",
apex_available: [
"//apex_available:platform",
"com.android.tethering",
],
}
The build system enforces that a module cannot be included in an APEX unless
its apex_available list permits it. This prevents accidental dependency
bloat and ensures clear ownership.
52.5.2 API Surface Levels¶
Mainline modules interact with the platform and with each other through
carefully defined API surfaces. The java_sdk_library module type in Soong
generates multiple API scopes:
// Source: build/soong/java/sdk_library.go
apiScopePublic = initApiScope(&apiScope{
name: "public",
sdkVersion: "current",
kind: android.SdkPublic,
})
apiScopeSystem = initApiScope(&apiScope{
name: "system",
annotation: "android.annotation.SystemApi(
client=android.annotation.SystemApi.Client.PRIVILEGED_APPS)",
kind: android.SdkSystem,
})
apiScopeModuleLib = initApiScope(&apiScope{
name: "module-lib",
annotation: "android.annotation.SystemApi(
client=android.annotation.SystemApi.Client.MODULE_LIBRARIES)",
kind: android.SdkModule,
})
apiScopeSystemServer = initApiScope(&apiScope{
name: "system-server",
annotation: "android.annotation.SystemApi(
client=android.annotation.SystemApi.Client.SYSTEM_SERVER)",
kind: android.SdkSystemServer,
})
These map to five distinct API surfaces:
| Surface | Annotation | Who Can Use It |
|---|---|---|
| Public | (none) | Any app (stable, CTS-tested) |
| System | @SystemApi(PRIVILEGED_APPS) |
Privileged apps and system services |
| Module-lib | @SystemApi(MODULE_LIBRARIES) |
Other Mainline modules only |
| System-server | @SystemApi(SYSTEM_SERVER) |
System server process only |
| Test | @TestApi |
CTS and other test suites |
graph TB
subgraph "API Surface Hierarchy"
PUB["Public API<br/>(any app)"]
SYS["System API<br/>(privileged apps)"]
MOD["Module-lib API<br/>(other modules)"]
SS["System-server API<br/>(system_server)"]
TEST["Test API<br/>(CTS tests)"]
PUB --> SYS
SYS --> MOD
SYS --> TEST
SYS --> SS
SS -.->|"can access"| MOD
end
52.5.3 Hidden API Enforcement¶
APIs not annotated with any of the above markers are considered hidden APIs. Mainline modules are subject to hidden-API enforcement just like regular apps:
-
The build system tracks hidden APIs through
hidden_apistanza in bootclasspath fragment definitions. -
At runtime, the
libnativeloaderand ART enforce access restrictions based on the calling module's API level.
A bootclasspath fragment declares which packages it owns and their hidden API flags:
bootclasspath_fragment {
name: "com.android.permission-bootclasspath-fragment",
contents: ["framework-permission"],
apex_available: ["com.android.permission"],
hidden_api: {
split_packages: ["*"],
// annotation flags, metadata, index files
},
}
52.5.4 What Can Be in a Module¶
A Mainline module (APEX) can contain:
| Content Type | Build Module Type | Property |
|---|---|---|
| Native shared libraries | cc_library |
native_shared_libs |
| Native executables | cc_binary |
binaries |
| JNI libraries | cc_library (with is_jni: true) |
jni_libs |
| Java libraries | java_library |
java_libs |
| Bootclasspath fragments | bootclasspath_fragment |
bootclasspath_fragments |
| System server fragments | systemserverclasspath_fragment |
systemserverclasspath_fragments |
| Android apps (APKs) | android_app |
apps |
| Shell scripts | sh_binary |
sh_binaries |
| Configuration files | prebuilt_etc |
prebuilts |
| Rust dynamic libraries | rust_library |
rust_dyn_libs |
| Filesystem images | android_filesystem |
filesystems |
| Runtime resource overlays | runtime_resource_overlay |
rros |
| Compat config files | platform_compat_config |
compat_configs |
52.5.5 What Cannot Be in a Module¶
Not everything can be modularized. The following constraints apply:
- Kernel modules -- Kernel code is updated through GKI, not APEX.
-
HAL implementations -- HALs are vendor-specific; they use VINTF manifests and are not part of Mainline (though vendor APEXes exist).
-
Boot-critical init scripts -- The very earliest init stages must work without any APEX mounted.
-
SELinux policy -- Base policy ships with the platform; modules only contribute
file_contextsfor their own mount points. -
Resources from framework-res -- Core framework resources are not in an APEX (though overlays are possible).
52.5.6 Module Dependencies and min_sdk_version¶
Each APEX declares a min_sdk_version that determines:
- The minimum platform version on which the APEX can be installed.
- Which NDK/SDK APIs the module's native code can use.
The packages/modules/common/sdk/Android.bp file defines standard defaults
for each launch window:
// Source: packages/modules/common/sdk/Android.bp
apex_defaults {
name: "r-launched-apex-module",
defaults: ["any-launched-apex-modules"],
min_sdk_version: "30", // APEX_LOWEST_MIN_SDK_VERSION
}
apex_defaults {
name: "s-launched-apex-module",
defaults: ["any-launched-apex-modules"],
min_sdk_version: "31",
compressible: true,
}
apex_defaults {
name: "t-launched-apex-module",
defaults: ["any-launched-apex-modules"],
min_sdk_version: "Tiramisu",
compressible: true,
}
apex_defaults {
name: "u-launched-apex-module",
defaults: ["any-launched-apex-modules"],
min_sdk_version: "UpsideDownCake",
compressible: true,
}
apex_defaults {
name: "v-launched-apex-module",
defaults: ["any-launched-apex-modules"],
min_sdk_version: "VanillaIceCream",
compressible: true,
}
apex_defaults {
name: "b-launched-apex-module",
defaults: ["any-launched-apex-modules"],
min_sdk_version: "36",
compressible: true,
}
All updatable APEXes inherit from any-launched-apex-modules:
// Source: packages/modules/common/sdk/Android.bp
apex_defaults {
name: "any-launched-apex-modules",
updatable: true,
}
This sets updatable: true, which triggers additional build-time checks:
- All native dependencies must use stable NDK APIs.
- All Java dependencies must use
sdk_version: "module_current"or lower. - Symbol versioning is enforced for shared libraries.
52.5.7 Dynamic Common Lib APEXes (DCLA)¶
Some modules use the DCLA (Dynamic Common Lib APEX) strategy to share native libraries across multiple APEXes without duplicating them:
// Source: packages/modules/common/sdk/Android.bp
DCLA_MIN_SDK_VERSION = "31"
library_linking_strategy_apex_defaults {
name: "r-launched-dcla-enabled-apex-module",
defaults: ["r-launched-apex-module"],
soong_config_variables: {
library_linking_strategy: {
prefer_static: {},
conditions_default: {
min_sdk_version: DCLA_MIN_SDK_VERSION,
},
},
},
}
When DCLA is enabled, shared libraries like libc++ and libcrypto are
loaded from a shared location rather than being duplicated in each APEX.
This reduces on-device storage but requires API level 31+ for the shared
library loading infrastructure.
52.5.8 Cross-Module Dependencies¶
Modules can depend on each other through:
-
provideNativeLibs / requireNativeLibs -- Declared in the APEX manifest, these let one APEX export a native library that another APEX imports.
-
java_sdk_library -- Provides stable Java API stubs that other modules compile against.
-
Stable AIDL -- For IPC between services in different modules.
graph LR
subgraph "com.android.tethering"
FW_CONN["framework-connectivity.jar"]
SVC_CONN["service-connectivity.jar"]
end
subgraph "com.android.permission"
FW_PERM["framework-permission.jar"]
SVC_PERM["service-permission.jar"]
end
subgraph "com.android.sdkext"
FW_SDK["framework-sdkextensions.jar"]
DS["derive_sdk"]
end
FW_CONN -->|"@SystemApi(MODULE_LIBRARIES)"| FW_PERM
SVC_CONN -->|"stable AIDL"| SVC_PERM
DS -->|"reads sdkinfo.pb from"| FW_CONN
52.5.9 The apex_available Enforcement Mechanism¶
The build system enforces apex_available at the Soong module-graph level.
The ApexModule interface in build/soong/android/apex.go defines the
contract:
// Source: build/soong/android/apex.go
type ApexModule interface {
// Returns true if this module is available in any APEX
InAnyApex() bool
// Returns true if the module is NOT available to the platform
NotInPlatform() bool
// Tests if this module can have APEX variants
CanHaveApexVariants() bool
// Tests if installable as a file in an APEX
IsInstallableToApex() bool
// Tests availability for a specific APEX or ":platform"
AvailableFor(what string) bool
// Returns the list of APEXes this module is available for
ApexAvailableFor() []string
// Returns the min SDK version the module supports
MinSdkVersionSupported(ctx BaseModuleContext) ApiLevel
}
When an APEX definition references a library (e.g., native_shared_libs:
["libfoo"]), the build system:
- Creates an APEX variant of
libfoothrough theapexTransitionMutator. - Checks that
libfoo'sapex_availablelist includes the APEX name. - Validates that
libfoo'smin_sdk_versionis compatible with the APEX. - Transitively checks all dependencies of
libfoo.
If any check fails, the build aborts with a clear error message indicating which module violates the boundary.
52.5.10 Linker Namespace Configuration¶
When an APEX is activated, its native libraries need their own linker
namespace to avoid conflicts with platform libraries and other APEXes. The
linkerconfig tool generates namespace configuration dynamically based on the
activated APEXes.
Each APEX's apex_manifest.pb declares:
provideNativeLibs-- Libraries this APEX exports to others.requireNativeLibs-- Libraries this APEX needs from others.
The linker configuration ensures:
- Libraries inside an APEX can see each other.
-
Libraries from the platform are accessible only through the platform namespace.
-
Cross-APEX library sharing follows explicit
provideNativeLibs/requireNativeLibsdeclarations.
graph TB
subgraph "Linker Namespaces"
PLAT["platform namespace<br/>/system/lib64/"]
APEX_A["com.android.tethering<br/>/apex/com.android.tethering/lib64/"]
APEX_B["com.android.permission<br/>/apex/com.android.permission/lib64/"]
APEX_C["com.android.art<br/>/apex/com.android.art/lib64/"]
end
APEX_A -->|"requireNativeLibs"| PLAT
APEX_B -->|"requireNativeLibs"| PLAT
APEX_A -->|"requireNativeLibs"| APEX_C
APEX_B -->|"requireNativeLibs"| APEX_C
subgraph "Not Allowed"
style X fill:#f99,stroke:#f00
X["APEX_A directly accessing<br/>APEX_B internal libs"]
end
52.5.11 Bootclasspath and System Server Classpath¶
Java-containing Mainline modules participate in the boot classpath through bootclasspath fragments and systemserverclasspath fragments. These fragments define which JAR files from the module should be added to the classpath.
A bootclasspath fragment declaration:
bootclasspath_fragment {
name: "com.android.tethering-bootclasspath-fragment",
contents: [
"framework-connectivity",
"framework-connectivity-t",
],
apex_available: ["com.android.tethering"],
hidden_api: {
split_packages: ["*"],
// ...
},
}
At boot time, the derive_classpath binary (from the SdkExtensions module)
reads the active APEXes and generates:
BOOTCLASSPATH-- The list of JARs for the boot class loader.DEX2OATBOOTCLASSPATH-- The subset compiled ahead-of-time by dex2oat.SYSTEMSERVERCLASSPATH-- JARs loaded into the system server process.
This dynamic generation is essential because the set of active modules can change with updates, and the classpath must always reflect what is actually installed.
52.6 Module Development¶
52.6.1 Building a Mainline Module¶
Building a specific module:
# Build a single APEX
$ m com.android.tethering
# Build all Mainline modules
$ m mainline_modules
# Build a module and install it on a connected device
$ m com.android.sdkext && adb install out/target/product/generic_arm64/system/apex/com.android.sdkext.apex
52.6.2 Anatomy of a Module Build¶
A complete module definition typically involves:
- APEX definition (
Android.bpin module'sapex/directory) - APEX key (
apex_keymodule) - Certificate (
android_app_certificatemodule) - Bootclasspath fragment (for Java-containing modules)
- System server classpath fragment (for modules with system server code)
- Framework library (usually
java_sdk_library) - Service implementation (usually
java_library)
Here is the minimal structure, using the SdkExtensions module as an example:
packages/modules/SdkExtensions/
+-- Android.bp # APEX definition + key + certificate
+-- manifest.json # APEX manifest (JSON, converted to protobuf)
+-- com.android.sdkext.avbpubkey # AVB public key
+-- com.android.sdkext.pem # AVB private key
+-- com.android.sdkext.pk8 # Container signing private key
+-- com.android.sdkext.x509.pem # Container signing certificate
+-- derive_sdk/
| +-- derive_sdk.cpp # Boot-time binary
| +-- derive_sdk.rc # init service definition
+-- java/
| +-- android/os/ext/
| +-- SdkExtensions.java # Public API
+-- gen_sdk/ # SDK version database generation
+-- javatests/ # Tests
52.6.3 Creating a New Module from Scratch¶
If you were to create a new Mainline module, the steps would be:
Step 1: Generate signing keys.
# Generate AVB key pair
$ openssl genrsa -out com.android.mymodule.pem 4096
$ avbtool extract_public_key --key com.android.mymodule.pem \
--output com.android.mymodule.avbpubkey
# Generate container signing key
$ development/tools/make_key com.android.mymodule \
'/CN=com.android.mymodule'
Step 2: Write the Android.bp.
apex {
name: "com.android.mymodule",
defaults: ["v-launched-apex-module"], // launched in V
manifest: "apex_manifest.json",
key: "com.android.mymodule.key",
certificate: ":com.android.mymodule.certificate",
bootclasspath_fragments: [
"com.android.mymodule-bootclasspath-fragment",
],
native_shared_libs: ["libmymodule"],
java_libs: ["mymodule-java-lib"],
}
apex_key {
name: "com.android.mymodule.key",
public_key: "com.android.mymodule.avbpubkey",
private_key: "com.android.mymodule.pem",
}
android_app_certificate {
name: "com.android.mymodule.certificate",
certificate: "com.android.mymodule",
}
Step 3: Write the APEX manifest.
Step 4: Create SELinux file contexts.
# system/sepolicy/apex/com.android.mymodule-file_contexts
(/.*)? u:object_r:system_file:s0
/bin(/.*)? u:object_r:mymodule_exec:s0
/lib(64)?(/.*)? u:object_r:system_lib_file:s0
Step 5: Declare apex_available in all dependencies.
cc_library {
name: "libmymodule",
srcs: ["mymodule.cpp"],
apex_available: [
"com.android.mymodule",
],
min_sdk_version: "VanillaIceCream",
}
52.6.4 Testing Mainline Modules¶
CTS (Compatibility Test Suite)¶
CTS tests verify that the device behaves correctly with the installed module versions. Many module-specific CTS tests live alongside the module source:
packages/modules/SdkExtensions/javatests/
+-- com/android/os/ext/SdkExtensionsTest.java
+-- com/android/sdkext/extensions/SdkExtensionsHostTest.java
MTS (Mainline Test Suite)¶
MTS is a subset of CTS designed specifically for testing Mainline module updates. It can be run independently:
# Run MTS for a specific module
$ atest --mts com.android.sdkext.tests
# Or use the MTS test plan
$ cts-tradefed run mts --module SdkExtensionsTests
Unit Tests¶
Individual module components have their own unit tests:
# Run apexd unit tests
$ atest apex_file_test
$ atest apex_manifest_test
$ atest apex_database_test
$ atest apex_file_repository_test
# Run derive_sdk tests
$ atest derive_sdk_test
TEST_MAPPING¶
Modules use TEST_MAPPING files to declare which tests should run during
pre-submit and post-submit:
{
"presubmit": [
{"name": "SdkExtensionsTests"},
{"name": "derive_sdk_test"}
],
"postsubmit": [
{"name": "SdkExtensionsHostTest"}
]
}
52.6.5 Installing and Updating on Device¶
Install a locally-built APEX:
$ adb install --staged out/target/product/generic_arm64/system/apex/com.android.sdkext.apex
$ adb reboot
The --staged flag is required for APEXes because they need a reboot to
activate (unless the APEX supports rebootless update via
supportsRebootlessUpdate in the manifest).
Revert to the pre-installed version:
Check installed APEX versions:
$ adb shell pm list packages --apex-only --show-versioncode
package:com.android.adbd versionCode:340090000
package:com.android.art versionCode:340090000
package:com.android.conscrypt versionCode:340090000
package:com.android.i18n versionCode:340090000
package:com.android.media versionCode:340090000
package:com.android.media.swcodec versionCode:340090000
package:com.android.os.statsd versionCode:340090000
package:com.android.permission versionCode:340090000
package:com.android.resolv versionCode:340090000
package:com.android.sdkext versionCode:340090000
package:com.android.tethering versionCode:340090000
package:com.android.wifi versionCode:340090000
...
Inspect a mounted APEX:
$ adb shell ls /apex/com.android.sdkext/
bin/
etc/
javalib/
$ adb shell cat /apex/apex-info-list.xml
Query extension versions:
$ adb shell getprop build.version.extensions.r
14
$ adb shell getprop build.version.extensions.s
12
$ adb shell getprop build.version.extensions.t
8
52.6.6 Debugging APEX Issues¶
Check apexd logs:
Examine the APEX database:
# List all activated APEXes
$ adb shell cmd -w apexservice getActivePackages
# Get info about a specific APEX
$ adb shell cmd -w apexservice getApexInfo com.android.sdkext
Inspect APEX file contents from host:
Check SELinux contexts:
52.6.7 Staged Sessions and Update Workflow¶
The complete lifecycle of a Mainline module update:
stateDiagram-v2
[*] --> Downloaded: Play downloads new APEX
Downloaded --> Verified: PackageManager verifies APK signature
Verified --> Staged: apexd submitStagedSession
Staged --> ReadyForReboot: apexd markStagedSessionReady
ReadyForReboot --> Rebooting: Device reboots
Rebooting --> Activating: apexd processes staged session
Activating --> Active: ActivatePackageImpl succeeds
Activating --> RollbackPending: Activation fails
RollbackPending --> RolledBack: Restore from /data/apex/backup/
RolledBack --> Active: Pre-installed version activated
Active --> [*]
52.6.8 The APEX Build Pipeline (Full)¶
flowchart TB
subgraph "Soong Build System"
direction TB
BP["Android.bp<br/>(apex module definition)"]
BP --> DEPS["Resolve dependencies:<br/>native_shared_libs, java_libs,<br/>bootclasspath_fragments, ..."]
DEPS --> STAGE["Stage files into<br/>\$OUT/image_dir/"]
STAGE --> MANIFEST["Generate apex_manifest.pb<br/>(from .json source)"]
MANIFEST --> IMG["Create filesystem image<br/>(mke2fs / mkfs.erofs / make_f2fs)"]
IMG --> SIGN_AVB["Sign with avbtool<br/>(dm-verity hash tree)"]
SIGN_AVB --> ZIP["Package into ZIP<br/>(soong_zip)"]
ZIP --> SIGN_APK["Sign with APK Signature v2/v3"]
SIGN_APK --> OUTPUT["com.android.module.apex"]
end
subgraph "Optional: Compression"
OUTPUT --> COMPRESS["apex_compression_tool"]
COMPRESS --> CAPEX["com.android.module.capex"]
end
subgraph "Prebuilt Flow"
PREBUILT["prebuilt_apex module"]
PREBUILT --> DEAPEX["deapexer: extract for build deps"]
end
52.6.9 The APEX Service Interface (AIDL)¶
The apexd daemon exposes an AIDL service interface that PackageManager and
other system components use to manage APEX packages. Key methods include:
// Source: system/apex/apexd/aidl (simplified interface)
interface IApexService {
ApexSessionInfo[] getStagedSessionInfo();
void submitStagedSession(in ApexSessionParams params,
out ApexInfoList packages);
void markStagedSessionReady(int session_id);
void markStagedSessionSuccessful();
void markBootCompleted();
ApexInfo[] getActivePackages();
ApexInfo[] getAllPackages();
void installAndActivatePackage(String packagePath,
out ApexInfo info);
void revertActiveSession();
}
The installAndActivatePackage method is the entry point for rebootless
updates. It validates the caller (must be system or root), verifies the
package, and performs the activation steps described in Section 32.2.10.
52.6.10 Continuous Integration and Module Testing¶
Mainline modules follow a rigorous testing pipeline:
Pre-submit (before code lands):
- Unit tests (specified in
TEST_MAPPING). - Build verification (module must build cleanly).
- API compatibility checks (no breaking changes to stable APIs).
Post-submit (after code lands):
- MTS (Mainline Test Suite) runs on multiple device types.
- CTS (Compatibility Test Suite) for behavior verification.
- Performance benchmarks to catch regressions.
Before module release:
- Full MTS pass on target devices.
- Dogfood deployment to internal devices.
- Staged rollout (1%, 5%, 25%, 50%, 100%).
- Monitoring for crash rates, ANR rates, and metric anomalies.
52.6.11 Handling API Evolution in Modules¶
When a Mainline module adds a new API, the process is:
- Add the API with appropriate annotations (
@SystemApi,@TestApi, etc.). - Update the API surface files (
current.txt/system-current.txt). - Run
m update-apito regenerate API tracking files. - Add CTS tests for the new API.
- If the API should be gated on extension version, add the module to the
appropriate extension train in
derive_sdk.cpp.
For example, if a new API was added to the Permission module for the T
extension train at version 5, the extensions_db would be updated to require
a minimum Permission module version that includes the new API.
52.6.12 Debugging Build Failures¶
Common build issues with Mainline modules:
apex_available violations:
error: "libfoo" is not available for "com.android.mymodule"
Add "com.android.mymodule" to apex_available of "libfoo"
Fix: Add the APEX name to the library's apex_available property.
min_sdk_version violations:
error: "libfoo" with min_sdk_version "current" cannot be used
in "com.android.mymodule" with min_sdk_version "30"
Fix: Set a concrete min_sdk_version on the dependency library.
Updatable module restrictions:
error: "com.android.mymodule" is updatable but references "libbar"
which uses unstable platform APIs
Fix: Ensure all dependencies use stable API levels (NDK, SDK stubs).
52.6.13 Module Versioning Strategy¶
APEX version codes follow a specific format: XYYZZZ000 where:
X-- Reserved (usually 3 for trunk).YY-- Platform SDK version (e.g., 40 for B/Baklava SDK 36 encoded).ZZZ-- Build number within the release.000-- Variant (000 for release, 090 for development).
For example, 340090000 means:
3-- Trunk40-- SDK version encoding090-- Development build000-- Default variant
This scheme ensures that newer module versions always have higher version codes, which is critical for the Play Store update mechanism.
52.7 Try It¶
The following exercises walk through inspecting Mainline modules on a running device and understanding their structure from source.
Exercise 52.1: Inspect Activated APEXes¶
List all activated APEXes on a device or emulator:
For each APEX, inspect its contents:
$ adb shell ls -la /apex/com.android.sdkext/
$ adb shell ls -la /apex/com.android.tethering/
$ adb shell ls -la /apex/com.android.permission/
Question: Which modules contain native binaries (bin/ directory)? Which
contain only Java libraries (javalib/)?
Exercise 52.2: Read the APEX Info List¶
The file /apex/apex-info-list.xml contains metadata about every activated
APEX:
Parse the XML to answer:
- Which APEXes are on the
/systempartition vs./system_ext? - Which APEXes have been updated from their pre-installed version?
- What is the version code of each APEX?
Exercise 52.3: Query Extension Versions¶
Check all SDK extension versions:
Compare the R, S, T, U, V, and B extension versions. Using the
kRModules, kSModules, kTModules sets from derive_sdk.cpp, identify
which modules contribute to each extension level.
Exercise 52.4: Examine APEX Build Rules¶
Read the APEX module definition for the Permission module:
Trace the dependency chain:
- What defaults does it inherit?
- What is its
min_sdk_version? - What bootclasspath fragments does it include?
- What key and certificate does it use?
Exercise 52.5: Build and Install an APEX¶
Build the SdkExtensions module and install it:
$ source build/envsetup.sh
$ lunch aosp_cf_x86_64_phone-trunk_staging-userdebug
$ m com.android.sdkext
# Install on a running emulator
$ adb install --staged out/target/product/vsoc_x86_64/system/apex/com.android.sdkext.apex
$ adb reboot
After reboot, verify the version changed:
Exercise 52.6: Trace the APEX Activation in Logcat¶
Capture boot logs to see apexd in action:
Look for:
"Scanning <path>"messages showing where APEXes are found."Successfully activated"messages for each APEX.- The
"Marking APEXd as activated"and"Marking APEXd as ready"milestones. - Timing information (
"OnStart done, duration=...") for boot performance.
Exercise 52.7: Examine the dm-verity Setup¶
For an updated APEX (one in /data/apex/active/), the dm-verity layer adds
integrity protection. Use dmsetup to inspect:
This shows the dm-verity parameters: hash algorithm, data block size, hash block size, number of blocks, and root hash.
Exercise 52.8: Compare Pre-installed vs. Updated APEX¶
Find a module that has both a pre-installed version and an updated version:
# Pre-installed (factory) version
$ adb shell ls /system/apex/ | grep sdkext
# Updated (if any) version
$ adb shell ls /data/apex/active/ | grep sdkext
Pull both and compare their manifests:
# On host (requires deapexer)
$ deapexer info /path/to/system_version.apex
$ deapexer info /path/to/data_version.apex
Exercise 52.9: Examine Module Boundaries¶
Pick two modules that have a dependency relationship (e.g., com.android.tethering
depends on APIs from com.android.permission).
In the source:
- Find the
java_sdk_librarydefinitions in both modules. -
Identify which API scope (
public,system,module-lib) is used for the cross-module dependency. -
Check the
apex_availabledeclarations on shared dependencies.
Exercise 52.10: Write an Extension Version Check¶
Write a small Android app (or use adb shell am with a test APK) that:
- Calls
SdkExtensions.getExtensionVersion(Build.VERSION_CODES.R). - Calls
SdkExtensions.getAllExtensionVersions(). - Prints all extension versions to logcat.
- Conditionally uses an API based on the extension version.
This demonstrates the runtime API-availability checking pattern that all apps should use when targeting APIs introduced through Mainline modules.
Exercise 52.11: Explore the APEX Build System¶
Examine the Soong build rules for APEX construction:
# Look at the APEX module type registration
$ cat build/soong/apex/apex.go | grep -A5 "RegisterModuleType"
# Look at the builder rules
$ cat build/soong/apex/builder.go | grep -A10 "apexRule ="
# Examine the key management
$ cat build/soong/apex/key.go | grep -A10 "apexKeyProperties"
Question: What host tools does the APEX builder depend on? List at least ten tools that participate in building an APEX file.
Exercise 52.12: Map Module Dependencies¶
For the Connectivity module (com.android.tethering), trace the dependency
chain:
# Find all libraries declared in the APEX
$ grep -A30 "multilib" \
packages/modules/Connectivity/Tethering/apex/Android.bp
# For each JNI library, find its apex_available declaration
$ grep -r "apex_available" \
packages/modules/Connectivity/service/ \
--include="*.bp" | grep tethering
Draw a dependency graph showing:
- The APEX package.
- Its bootclasspath fragments.
- Its system server classpath fragments.
- The native shared libraries.
- The JNI libraries.
- The apps (APKs) inside the APEX.
Exercise 52.13: Simulate an APEX Update Rollback¶
On a userdebug device:
# 1. Check current APEX versions
$ adb shell pm list packages --apex-only --show-versioncode | grep sdkext
# 2. Build a modified APEX with a higher version
# (edit the manifest.json to bump version)
# 3. Install it as a staged session
$ adb install --staged com.android.sdkext.apex
# 4. Reboot
$ adb reboot
$ adb wait-for-device
# 5. Verify the new version is active
$ adb shell pm list packages --apex-only --show-versioncode | grep sdkext
# 6. Trigger a rollback
$ adb shell cmd -w apexservice revertActiveSession
$ adb reboot
$ adb wait-for-device
# 7. Verify we're back to the original version
$ adb shell pm list packages --apex-only --show-versioncode | grep sdkext
Exercise 52.14: Analyze dm-verity Protection¶
The dm-verity layer is what makes APEX tamper-proof. Examine how it works:
# 1. List all device-mapper devices
$ adb root
$ adb shell dmsetup ls
# 2. Show the dm-verity table for an APEX
$ adb shell dmsetup table | grep com.android
# 3. The table format is:
# <start_sector> <num_sectors> verity <version> <data_dev> <hash_dev>
# <data_block_size> <hash_block_size> <num_data_blocks> <hash_start_block>
# <algorithm> <digest> <salt> [optional_params]
Question: What hash algorithm is used? What happens if a block is
corrupted (look for restart_on_corruption in the table)?
Exercise 52.15: Build the Full Module Test Suite¶
Run the test suite for a specific Mainline module:
# Run SdkExtensions tests
$ atest SdkExtensionsTests
# Run apexd unit tests
$ atest apex_file_test apex_manifest_test apex_database_test
# Run derive_sdk tests
$ atest derive_sdk_test
# Run CTS tests for a module
$ atest CtsApexTestCases
Exercise 52.16: Examine the apex-info-list.xml Schema¶
The apex-info-list.xml file is the authoritative record of all APEXes on the
device. Examine its schema:
The XML contains entries like:
<apex-info
moduleName="com.android.sdkext"
modulePath="/system/apex/com.android.sdkext.apex"
preinstalledModulePath="/system/apex/com.android.sdkext.apex"
versionCode="340090000"
versionName=""
isFactory="true"
isActive="true"
lastUpdateMillis="0" />
For each entry, identify:
isFactory-- Was this the pre-installed version?isActive-- Is this the currently active version?modulePathvs.preinstalledModulePath-- Has it been updated?
Exercise 52.17: Create a Minimal Test APEX¶
Create a minimal APEX that contains a single shell script:
- Create a directory
packages/modules/MyTest/:
- Write the
Android.bp:
apex {
name: "com.android.mytest",
updatable: false,
platform_apis: true,
manifest: "apex_manifest.json",
sh_binaries: ["my_test_script"],
key: "com.android.mytest.key",
certificate: ":com.android.mytest.certificate",
file_contexts: ":apex.test-file_contexts",
}
sh_binary {
name: "my_test_script",
src: "my_script.sh",
apex_available: ["com.android.mytest"],
}
- Build and inspect:
Question: What files are inside the APEX besides your script? What generates them?
Exercise 52.18: Trace derive_sdk Boot Behavior¶
Monitor what derive_sdk does at boot time:
# Enable verbose logging
$ adb shell setprop log.tag.derive_sdk VERBOSE
$ adb reboot
$ adb wait-for-device
$ adb logcat -d -s derive_sdk | head -50
Match the log output against the code in
packages/modules/SdkExtensions/derive_sdk/derive_sdk.cpp:
- Which APEXes were found?
- What
sdkinfo.pbversions were read? - What extension version was computed for each train (R, S, T, U, V, B)?
Summary¶
Architecture Recap¶
The following diagram captures the complete Mainline architecture from build time to runtime:
graph TB
subgraph "Build Time"
SRC["Module Source Code<br/>(packages/modules/*)"]
SOONG["Soong Build System<br/>(build/soong/apex/)"]
APEXER["apexer tool<br/>(system/apex/apexer/)"]
KEYS["Signing Keys<br/>(AVB + APK Signature)"]
SRC --> SOONG
SOONG --> APEXER
KEYS --> APEXER
APEXER --> APEX_FILE[".apex file"]
end
subgraph "Distribution"
APEX_FILE --> PREINSTALL["Pre-installed<br/>(/system/apex/)"]
APEX_FILE --> GPLAY["Google Play<br/>System Updates"]
end
subgraph "Boot Time"
PREINSTALL --> APEXD["apexd daemon"]
GPLAY -->|"staged"| DATA_DIR["/data/apex/active/"]
DATA_DIR --> APEXD
APEXD --> REPO["ApexFileRepository<br/>(select best version)"]
REPO --> MOUNT["MountPackageImpl<br/>(loop + dm-verity + mount)"]
MOUNT --> ACTIVATE["ActivatePackageImpl<br/>(bind mount to /apex/name)"]
ACTIVATE --> READY["apexd.status = ready"]
end
subgraph "Runtime"
READY --> APEX_MOUNT["/apex/MODULE/"]
APEX_MOUNT --> DERIVE["derive_sdk<br/>(set extension versions)"]
APEX_MOUNT --> LINKER["linkerconfig<br/>(namespace config)"]
APEX_MOUNT --> CLASSPATH["derive_classpath<br/>(BOOTCLASSPATH)"]
DERIVE --> PROPS["System Properties<br/>(build.version.extensions.*)"]
PROPS --> APPS["Applications<br/>(SdkExtensions.getExtensionVersion())"]
end
Key Data Paths¶
| Path | Purpose |
|---|---|
/system/apex/ |
Pre-installed APEXes (factory image) |
/system_ext/apex/ |
System-ext partition APEXes |
/product/apex/ |
Product partition APEXes |
/vendor/apex/ |
Vendor partition APEXes |
/odm/apex/ |
ODM partition APEXes |
/data/apex/active/ |
Updated APEXes (from Play or adb) |
/data/apex/backup/ |
Backup for rollback |
/data/apex/decompressed/ |
Decompressed CAPEXes |
/data/app-staging/ |
Staged sessions (pending reboot) |
/apex/<name>/ |
Active mount point (bind mount to latest) |
/apex/<name>@<version>/ |
Versioned mount point |
/apex/apex-info-list.xml |
Metadata about all activated APEXes |
Key Source Files¶
Project Mainline represents one of the most significant architectural changes in Android's history. By packaging platform components into independently updatable APEX modules, Google can deliver security fixes and feature improvements to billions of devices without waiting for the traditional OEM update pipeline.
Key takeaways from this chapter:
-
APEX is a ZIP containing a dm-verity-signed filesystem image, enabling native code, Java libraries, and configuration to be updated as a single atomic unit.
-
apexd manages the full lifecycle: scanning partitions at boot, creating loop devices and dm-verity tables, bind-mounting active versions, processing staged updates, and supporting rollback.
-
40+ modules in
packages/modules/cover networking, security, media, telephony, ML, and more -- each with its own APEX name, signing key, and version lifecycle. -
SDK Extensions solve the runtime API-availability problem by deriving extension version numbers from actual installed module versions at boot time.
-
Module boundaries are enforced through
apex_available, API surface annotations (@SystemApi),min_sdk_version, and hidden-API policies.
The source files central to understanding this system:
| Component | Path |
|---|---|
| APEX build rules | build/soong/apex/apex.go, builder.go, key.go |
| APEX tool | system/apex/apexer/apexer.py |
| APEX manifest proto | system/apex/proto/apex_manifest.proto |
| apexd daemon | system/apex/apexd/apexd.cpp, apex_file.cpp, apex_constants.h |
| apexd init config | system/apex/apexd/apexd.rc |
| Module defaults | packages/modules/common/sdk/Android.bp |
| SDK Extensions API | packages/modules/SdkExtensions/java/android/os/ext/SdkExtensions.java |
| derive_sdk | packages/modules/SdkExtensions/derive_sdk/derive_sdk.cpp |
| APEX file repository | system/apex/apexd/apex_file_repository.h |
52.8 Deep Dive: HealthFitness (Health Connect)¶
The HealthFitness module (com.android.healthfitness) provides the Health
Connect platform -- a centralized, on-device repository for health and
fitness data. It allows apps from different publishers (fitness trackers,
sleep monitors, medical apps) to share health records through a unified API
with fine-grained, per-data-type permissions.
52.8.1 Module Structure¶
packages/modules/HealthFitness/
apex/ APEX packaging & bootclasspath fragment
apk/ HealthConnectController (Settings UI)
backuprestore/ HealthConnectBackupRestore (cloud B&R agent)
framework/ Public API (android.health.connect.*)
service/ System server implementation
flags/ Feature flags (aconfig)
lint/ Custom lint checks
testapps/ Development toolbox app
tests/ CTS, unit, and integration tests
The APEX bundles two APKs plus the framework and system server JARs:
// Source: packages/modules/HealthFitness/apex/Android.bp
apex {
name: "com.android.healthfitness",
apps: [
"HealthConnectBackupRestore",
"HealthConnectController",
],
bootclasspath_fragments: [
"com.android.healthfitness-bootclasspath-fragment"
],
systemserverclasspath_fragments: [
"com.android.healthfitness-systemserverclasspath-fragment"
],
min_sdk_version: "34",
updatable: true,
}
52.8.2 Architecture Overview¶
graph TB
subgraph "Client Apps"
APP1["Fitness Tracker"]
APP2["Sleep Monitor"]
APP3["Medical App"]
end
subgraph "framework-healthfitness (bootclasspath)"
HCM["HealthConnectManager<br/>(android.health.connect)"]
PERMS["HealthPermissions<br/>(per-data-type grants)"]
DT["Data Types<br/>(50+ Record classes)"]
end
subgraph "service-healthfitness (system_server)"
HCSI["HealthConnectServiceImpl<br/>(IHealthConnectService.Stub)"]
TXM["TransactionManager<br/>(SQLite operations)"]
FHIR["MedicalResourceHelper<br/>(FHIR / PHR)"]
AGG["FitnessRecordAggregateHelper"]
BKP["BackupRestore"]
EXP["ExportManager"]
PERM_H["PermissionHelper"]
end
subgraph "On-Device Storage"
DB[("HealthConnectDatabase<br/>(SQLite, per-user)")]
PREFS["PreferencesManager"]
end
APP1 --> HCM
APP2 --> HCM
APP3 --> HCM
HCM -->|Binder IPC| HCSI
HCSI --> TXM
HCSI --> FHIR
HCSI --> AGG
HCSI --> BKP
HCSI --> EXP
HCSI --> PERM_H
TXM --> DB
BKP --> DB
EXP --> DB
PERM_H --> PERMS
52.8.3 Data Types¶
Health Connect defines 50+ record types in the
android.health.connect.datatypes package. Every record class extends one of
two base classes:
| Base Class | Semantics | Examples |
|---|---|---|
InstantRecord |
Single point-in-time measurement | HeartRateRecord, BloodPressureRecord, BloodGlucoseRecord, OxygenSaturationRecord, BodyTemperatureRecord |
IntervalRecord |
Measurement over a time range | StepsRecord, ExerciseSessionRecord, SleepSessionRecord, NutritionRecord, HydrationRecord, DistanceRecord |
Data types span six categories defined by HealthDataCategory:
-
Activity -- Steps, distance, calories, exercise sessions, cycling cadence, floors climbed, elevation gained, exercise routes.
-
Body measurements -- Weight, height, body fat, bone mass, lean body mass, basal metabolic rate, body water mass.
-
Cycle tracking -- Menstruation flow, cervical mucus, ovulation test, intermenstrual bleeding, sexual activity.
-
Nutrition -- Nutrition records (per-nutrient detail), hydration, meal type.
-
Sleep -- Sleep sessions with per-stage breakdown (awake, light, deep, REM).
-
Vitals -- Heart rate, heart rate variability (RMSSD), blood pressure, blood glucose, oxygen saturation, respiratory rate, body temperature.
Each record carries Metadata (data origin, device info, client record ID,
last-modified time) enabling deduplication and priority ordering.
52.8.4 Personal Health Record (FHIR) Support¶
A major expansion in recent versions is the Personal Health Record (PHR) API, enabling storage of clinical medical data using the FHIR R4 standard:
// Source: framework/java/android/health/connect/datatypes/MedicalResource.java
// Source: framework/java/android/health/connect/datatypes/MedicalDataSource.java
// Source: framework/java/android/health/connect/datatypes/FhirResource.java
// Apps create a MedicalDataSource, then upsert FHIR resources:
CreateMedicalDataSourceRequest request =
new CreateMedicalDataSourceRequest.Builder(
"Hospital Portal", Uri.parse("https://fhir.hospital.example"))
.build();
UpsertMedicalResourceRequest upsert =
new UpsertMedicalResourceRequest.Builder(
dataSourceId,
FhirVersion.parseFhirVersion("4.0.1"),
fhirJsonString)
.build();
PHR data requires the WRITE_MEDICAL_DATA permission and is structurally
validated against FHIR R4 specifications using a binary protobuf spec bundled
in the service JAR.
52.8.5 Permission Model¶
Health Connect uses a two-level permission system:
Per-data-type permissions -- Each record type has a read and write
permission defined in HealthPermissions:
android.permission.health.READ_HEART_RATE
android.permission.health.WRITE_HEART_RATE
android.permission.health.READ_STEPS
android.permission.health.WRITE_STEPS
android.permission.health.READ_SLEEP
...
System-level permissions (signature/privileged):
| Permission | Level | Purpose |
|---|---|---|
MANAGE_HEALTH_PERMISSIONS |
signature | Grant/revoke health permissions |
MANAGE_HEALTH_DATA |
privileged | Delete records, manage priorities |
START_ONBOARDING |
signature | Launch client onboarding flows |
READ_HEALTH_DATA_IN_BACKGROUND |
privileged | Background reads |
READ_HEALTH_DATA_HISTORY |
privileged | Access historical records |
WRITE_MEDICAL_DATA |
dangerous | Write FHIR medical resources |
Apps must also declare an activity handling
ACTION_VIEW_PERMISSION_USAGE with the CATEGORY_HEALTH_PERMISSIONS
category to be eligible for health permission grants.
52.8.6 On-Device Storage¶
All health data is stored in a per-user SQLite database managed by
HealthConnectDatabase (extends SQLiteOpenHelper). The database lives in
credential-encrypted storage, ensuring it is inaccessible when the device is
locked.
Key storage components:
| Class | Responsibility |
|---|---|
TransactionManager |
Executes all SQLite read/write operations within transactions |
DatabaseHelper |
Schema creation and upgrades |
DatabaseUpgradeHelper |
Version migration logic |
FitnessRecordUpsertHelper |
Insert or update fitness records with deduplication |
FitnessRecordReadHelper |
Query records with time filters, pagination, data-origin filters |
FitnessRecordAggregateHelper |
Compute aggregations (sum, avg, min, max) over time windows |
FitnessRecordDeleteHelper |
Delete by ID, time range, or data origin |
The service enforces per-app rate limiting via RateLimiter -- each UID
has a sliding-window quota for read, write, and aggregate operations.
52.8.7 Data Priority and Aggregation¶
When multiple apps write the same data type (e.g., both a watch and a phone record steps), Health Connect uses a data origin priority order to resolve conflicts during aggregation. Users can reorder the priority in Settings.
// Source: framework/java/android/health/connect/HealthConnectManager.java
void updateDataOriginPriorityOrder(
UpdateDataOriginPriorityOrderRequest request,
Executor executor,
OutcomeReceiver<Void, HealthConnectException> callback);
void fetchDataOriginsPriorityOrder(
int dataCategory,
Executor executor,
OutcomeReceiver<FetchDataOriginsPriorityOrderResponse,
HealthConnectException> callback);
52.8.8 Backup, Restore, and Export¶
Health Connect supports three data-portability mechanisms:
-
Cloud backup/restore -- The
HealthConnectBackupRestoreAPK integrates with Android's backup infrastructure. Changes are tracked viaBackupChangeTokenHelperand serialized using Protocol Buffers. -
Device-to-device restore -- Migration from an older device uses
MigrationEntityrecords staged in a separate process. -
Export/import -- Users can export their data to external storage via
ExportManager, which compresses the database into a ZIP archive. TheImportManagerandDatabaseMergerhandle re-importing and deduplication.
52.8.9 Key Source Paths¶
| Component | Path |
|---|---|
| Public API | packages/modules/HealthFitness/framework/java/android/health/connect/ |
| Data types | packages/modules/HealthFitness/framework/java/android/health/connect/datatypes/ |
| AIDL interfaces | packages/modules/HealthFitness/framework/java/android/health/connect/aidl/ |
| System service | packages/modules/HealthFitness/service/java/com/android/server/healthconnect/ |
| Storage layer | packages/modules/HealthFitness/service/java/com/android/server/healthconnect/storage/ |
| Backup/restore | packages/modules/HealthFitness/service/java/com/android/server/healthconnect/backuprestore/ |
| Export/import | packages/modules/HealthFitness/service/java/com/android/server/healthconnect/exportimport/ |
| Settings UI (APK) | packages/modules/HealthFitness/apk/ |
| APEX config | packages/modules/HealthFitness/apex/Android.bp |
52.9 Deep Dive: Profiling Module¶
The Profiling module (com.android.profiling) is a Mainline module that
provides app-accessible system profiling through the ProfilingManager
API. It wraps Perfetto, heapprofd, and simpleperf behind a safe,
rate-limited, privacy-preserving interface that any app can call without root
access or special permissions.
52.9.1 Module Structure¶
packages/modules/Profiling/
aidl/ IProfilingService.aidl
apex/ APEX packaging (includes trace_redactor binary)
framework/ Public API (android.os.ProfilingManager)
service/ ProfilingService (system_server)
anomaly-detector/ AnomalyDetectorService (optional, flag-gated)
flags/ Feature flags (aconfig)
tests/ CTS tests
The APEX is feature-flag gated:
// Source: packages/modules/Profiling/apex/Android.bp
apex {
name: "com.android.profiling",
enabled: select(
release_flag("RELEASE_PACKAGE_PROFILING_MODULE"),
{ true: true, false: false }),
binaries: ["trace_redactor"],
bootclasspath_fragments: [
"com.android.profiling-bootclasspath-fragment"
],
systemserverclasspath_fragments: [
"com.android.profiling-systemserverclasspath-fragment"
],
}
52.9.2 Architecture¶
graph TB
subgraph "Application Process"
APP["App Code"]
PM["ProfilingManager"]
CB["IProfilingResultCallback<br/>(Binder stub)"]
end
subgraph "system_server (com.android.profiling)"
PS["ProfilingService<br/>(IProfilingService.Stub)"]
RL["RateLimiter<br/>(per-uid + system-wide)"]
TS["TracingSession"]
CFG["Configs<br/>(Perfetto config generation)"]
end
subgraph "Native Tools"
PERFETTO["perfetto CLI<br/>(trace collection)"]
REDACTOR["trace_redactor<br/>(privacy filtering)"]
end
subgraph "Output"
TEMP["/data/misc/perfetto-traces/profiling/"]
APPDIR["/data/data/pkg/files/profiling/"]
end
APP --> PM
PM -->|"requestProfiling()"| PS
PS --> RL
RL -->|allowed| CFG
CFG -->|config bytes| PERFETTO
PERFETTO --> TEMP
TEMP --> REDACTOR
REDACTOR --> APPDIR
PS -->|"sendResult()"| CB
CB --> PM
PM --> APP
52.9.3 Profiling Types¶
ProfilingManager supports four profiling types, each backed by a different
Perfetto data source:
| Type | Constant | Backend | Output suffix |
|---|---|---|---|
| Java Heap Dump | PROFILING_TYPE_JAVA_HEAP_DUMP |
JavaHprofConfig |
.perfetto-java-heap-dump |
| Heap Profile | PROFILING_TYPE_HEAP_PROFILE |
HeapprofdConfig |
.perfetto-heap-profile |
| Stack Sampling | PROFILING_TYPE_STACK_SAMPLING |
PerfEventConfig |
.perfetto-stack-sample |
| System Trace | PROFILING_TYPE_SYSTEM_TRACE |
FtraceConfig + ProcessStatsConfig |
.perfetto-trace |
The Configs class (service/java/.../Configs.java) translates the
ProfilingManager request parameters into Perfetto TraceConfig protobufs.
Each profiling type has DeviceConfig-controlled bounds for duration, buffer
size, and sampling rate:
// Source: packages/modules/Profiling/service/java/.../Configs.java
// Example: heap profile defaults
sHeapProfileDurationMsDefault // e.g. 60 seconds
sHeapProfileDurationMsMin // minimum allowed
sHeapProfileDurationMsMax // maximum allowed
sHeapProfileSizeKbDefault // buffer size
sHeapProfileSamplingIntervalBytesDefault // Poisson interval
52.9.4 Rate Limiting¶
The RateLimiter uses a cost-based sliding-window model to prevent
abuse:
| Window | System-wide default | Per-process default |
|---|---|---|
| 1 hour | 20 cost units | 10 cost units |
| 1 day | 50 cost units | 20 cost units |
| 1 week | 150 cost units | 30 cost units |
Each profiling session costs 10 units (app-initiated) or 5 units (system-triggered). Rate limiting can be disabled for local testing:
52.9.5 System-Triggered Profiling¶
Beyond on-demand requests, apps can register triggers -- system events that automatically produce profiling data:
| Trigger | Constant | When it fires |
|---|---|---|
| App fully drawn | TRIGGER_TYPE_APP_FULLY_DRAWN |
After Activity.reportFullyDrawn() on cold start |
| ANR | TRIGGER_TYPE_ANR |
When an ANR is detected for the app |
| App requests running trace | TRIGGER_TYPE_APP_REQUEST_RUNNING_TRACE |
On-demand snapshot of background trace |
| Force stop kill | TRIGGER_TYPE_KILL_FORCE_STOP |
User force-stops via Settings |
| Recents kill | TRIGGER_TYPE_KILL_RECENTS |
User swipes away in Recents |
The system maintains a background trace (configurable via DeviceConfig) and
clones a snapshot when a trigger fires. Results are delivered through
registerForAllProfilingResults() callbacks.
52.9.6 Trace Redaction and Privacy¶
All profiling output is redacted by the trace_redactor binary before
delivery to the app. Redaction strips data belonging to other processes,
leaving only information about the requesting app's own UID. This enables
unprivileged apps to safely receive system traces.
The redaction pipeline:
- Perfetto writes the raw trace to
/data/misc/perfetto-traces/profiling/. ProfilingServiceinvokestrace_redactorto filter the trace.-
The redacted output is written to the app's files directory via a
ParcelFileDescriptorsent over Binder. -
The temporary unredacted trace is deleted.
52.9.7 Anomaly Detector¶
The Profiling APEX optionally includes the AnomalyDetectorService, gated
behind the RELEASE_ANOMALY_DETECTOR flag. It provides a framework for
system-level anomaly detection through pluggable SignalCollector components:
// Source: packages/modules/Profiling/anomaly-detector/service/java/.../
// AnomalyDetectorService.java
public final class AnomalyDetectorService extends SystemService {
final Map<Class<? extends SignalCollectorConfig>,
CollectorEntry> mRegisteredCollectors;
}
Signal collectors (e.g., BinderSpamConfig / BinderSpamData) detect
anomalous patterns and can trigger profiling automatically.
52.9.8 Key Source Paths¶
| Component | Path |
|---|---|
| AIDL interface | packages/modules/Profiling/aidl/android/os/IProfilingService.aidl |
| Public API | packages/modules/Profiling/framework/java/android/os/ProfilingManager.java |
| Result class | packages/modules/Profiling/framework/java/android/os/ProfilingResult.java |
| Trigger class | packages/modules/Profiling/framework/java/android/os/ProfilingTrigger.java |
| Service impl | packages/modules/Profiling/service/java/com/android/os/profiling/ProfilingService.java |
| Config gen | packages/modules/Profiling/service/java/com/android/os/profiling/Configs.java |
| Rate limiter | packages/modules/Profiling/service/java/com/android/os/profiling/RateLimiter.java |
| Session state | packages/modules/Profiling/service/java/com/android/os/profiling/TracingSession.java |
| Anomaly detector | packages/modules/Profiling/anomaly-detector/service/java/.../AnomalyDetectorService.java |
| APEX config | packages/modules/Profiling/apex/Android.bp |
52.10 Deep Dive: UWB (Ultra-Wideband)¶
The UWB module (com.android.uwb) provides Android's Ultra-Wideband radio
stack -- a short-range, high-bandwidth wireless technology used for precise
ranging (distance measurement), angle-of-arrival positioning, and secure
device-to-device communication.
52.10.1 Module Structure¶
packages/modules/Uwb/
apex/ APEX packaging
framework/ Public API (android.uwb.*)
service/ UwbServiceCore, UwbSessionManager
fusion_lib/ Sensor fusion for positioning
multichip-parser/ Multi-chip UWB configuration
proto/ UWB stats logging protos
support_lib/ FiRA, CCC, ALIRO param builders
uci/ UCI command layer
libuwb-uci/ Rust UCI HAL implementation
src/rust/
uci_hal_android/ Android HAL binding
uwb_core/ Core UWB state machine
ranging/ Generic Ranging API (multi-technology)
framework/ android.ranging.*
uwb_backend/ UWB backend for generic ranging
rtt_backend/ Wi-Fi RTT backend for generic ranging
androidx_backend/ AndroidX UWB library backend
indev_uwb_adaptation/ In-development UWB adaptation
flags/ Feature flags
52.10.2 Protocol Stack Architecture¶
graph TB
subgraph "Application Layer"
APP["Application"]
UWB_MGR["UwbManager<br/>(@SystemApi)"]
RANGING_API["Generic Ranging API<br/>(android.ranging)"]
end
subgraph "Framework (com.android.uwb bootclasspath)"
RS["RangingSession"]
RM["RangingManager"]
RR["RangingReport<br/>(DistanceMeasurement,<br/>AngleOfArrivalMeasurement)"]
end
subgraph "Service (system_server)"
UWBSC["UwbServiceCore"]
UWBSM["UwbSessionManager"]
UWBCM["UwbConfigurationManager"]
UWBCC["UwbCountryCode"]
UWBADV["UwbAdvertiseManager"]
UWBMET["UwbMetrics"]
end
subgraph "UCI Layer (Rust)"
UCI_CORE["uwb_core<br/>(state machine)"]
UCI_HAL["uci_hal_android<br/>(JNI bridge)"]
end
subgraph "Hardware"
HAL["UWB HAL<br/>(IUwbChip AIDL)"]
RADIO["UWB Radio<br/>(IEEE 802.15.4z)"]
end
APP --> UWB_MGR
APP --> RANGING_API
UWB_MGR --> RS
RANGING_API --> RS
RS --> RM
RM -->|Binder IPC| UWBSC
UWBSC --> UWBSM
UWBSC --> UWBCM
UWBSC --> UWBCC
UWBSM --> UWBADV
UWBSM --> UWBMET
UWBSM -->|JNI| UCI_HAL
UCI_HAL --> UCI_CORE
UCI_CORE --> HAL
HAL --> RADIO
52.10.3 UWB Protocols¶
The module supports three protocol families, each with its own parameter builder in the support library:
| Protocol | Class | Use Case |
|---|---|---|
| FiRA | FiraOpenSessionParams, FiraParams |
Standardized IEEE 802.15.4z ranging (phones, tags, IoT) |
| CCC (Car Connectivity Consortium) | CccOpenRangingParams, CccParams |
Digital car keys |
| ALIRO | AliroOpenRangingParams, AliroParams |
Access control (door locks, gates) |
Additionally, RadarParams and RfTestParams support UWB radar sensing
and RF testing modes.
52.10.4 UCI (UWB Command Interface)¶
The UCI layer is implemented in Rust (libuwb-uci/src/rust/) and
provides the low-level command interface to UWB hardware:
libuwb-uci/src/rust/
uwb_core/src/
lib.rs Core library entry
service.rs UWB service state machine
params.rs Parameter definitions
params/
fira_app_config_params.rs
ccc_app_config_params.rs
aliro_app_config_params.rs
uci_packets.rs UCI packet parsing
uci_hal_android/
uci_hal_android.rs JNI bridge to Java service
UCI session states follow the standard state machine:
| State | Value | Description |
|---|---|---|
INIT |
0x00 | Session initialized |
DEINIT |
0x01 | Session deinitialized |
ACTIVE |
0x02 | Actively ranging |
IDLE |
0x03 | Configured but not ranging |
52.10.5 Ranging Measurements¶
A RangingReport contains one or more RangingMeasurement objects, each
providing:
| Measurement | Class | Data |
|---|---|---|
| Distance | DistanceMeasurement |
Distance in meters with confidence |
| Angle of Arrival | AngleOfArrivalMeasurement |
Azimuth and altitude angles |
| Two-Way Measurement | UwbTwoWayMeasurement |
Raw ToF data |
| OWR AoA | UwbOwrAoaMeasurement |
One-way ranging angle |
| DL TDoA | UwbDlTDoAMeasurement |
Downlink time-difference-of-arrival |
52.10.6 Generic Ranging API¶
The ranging/ subdirectory introduces a technology-agnostic Ranging API
(android.ranging.*) that abstracts over multiple ranging technologies:
ranging/
framework/ android.ranging.* API surface
uwb_backend/ UWB implementation of generic ranging
rtt_backend/ Wi-Fi RTT implementation of generic ranging
service/ RangingService
This allows apps to request ranging without binding to a specific technology.
The bootclasspath fragment conditionally includes framework-ranging:
// Source: packages/modules/Uwb/apex/Android.bp
soong_config_variables: {
release_ranging_stack: {
contents: [
"framework-uwb",
"framework-ranging",
],
},
},
52.10.7 Session Management¶
UwbSessionManager is the central class for UWB session lifecycle:
stateDiagram-v2
[*] --> INIT: openRangingSession
INIT --> IDLE: onOpened
IDLE --> ACTIVE: start
ACTIVE --> IDLE: stop
ACTIVE --> ACTIVE: onRangingResult
IDLE --> INIT: reconfigure
IDLE --> CLOSED: close
ACTIVE --> CLOSED: close
CLOSED --> [*]
note right of ACTIVE
Ranging reports delivered
via IUwbRangingCallbacks
end note
The session manager tracks per-session state, handles multicast list updates (adding/removing controlees), manages suspend/resume, and routes ranging notifications to the correct callback.
52.10.8 Country Code and Regulatory¶
UwbCountryCode determines the device's operating country and configures
channel restrictions accordingly. It listens for telephony and Wi-Fi country
code changes, defaulting to the SIM-based country. Regulatory compliance is
enforced through channel usage restrictions (ChannelUsage).
52.10.9 Key Source Paths¶
| Component | Path |
|---|---|
| Public API | packages/modules/Uwb/framework/java/android/uwb/ |
| UwbManager | packages/modules/Uwb/framework/java/android/uwb/UwbManager.java |
| RangingSession | packages/modules/Uwb/framework/java/android/uwb/RangingSession.java |
| Service core | packages/modules/Uwb/service/java/com/android/server/uwb/UwbServiceCore.java |
| Session manager | packages/modules/Uwb/service/java/com/android/server/uwb/UwbSessionManager.java |
| UCI constants | packages/modules/Uwb/service/java/com/android/server/uwb/data/UwbUciConstants.java |
| Rust UCI core | packages/modules/Uwb/libuwb-uci/src/rust/uwb_core/src/ |
| JNI bridge | packages/modules/Uwb/libuwb-uci/src/rust/uci_hal_android/ |
| Support library | packages/modules/Uwb/service/support_lib/ |
| Generic Ranging API | packages/modules/Uwb/ranging/framework/ |
| APEX config | packages/modules/Uwb/apex/Android.bp |