Zygote
AOSP source tourintermediate

How to read AOSP

AOSP is tens of millions of lines across thousands of repositories. Reading it is a skill with a method: find the entry point, follow the Binder boundary, read the build file, and ignore the generated code. This page teaches the method.

13 minAPI 1–37 · 17interactiveverified 2026-10-27

Nobody reads AOSP from the beginning. It has no beginning: it is a collection of thousands of Git repositories, built by a build system that generates most of the interesting glue, and the code you care about is usually three layers away from the code you started with. Reading it is a navigation skill, and the skill is teachable.

TL;DR#

  • Start from an entry point, never from a directory. The entry point is the API you call, the command you ran, or the log line you saw.
  • Follow the Binder boundary. A Manager facade is almost always a thin client for a service, and the policy lives on the other side of Stub/Proxy.
  • Read the build file before the code. Android.bp tells you what a module is, what it depends on, and whether it is a library, a binary or an app.
  • Skip generated code. aidl_api/, *.pb.h, R.java and the build output are consequences, not sources.
  • Use the tools. cs.android.com for cross-references, git log for history, and repo for the multi-repository view.

Mental model#

Think of reading a large city without a map.

You do not walk every street. You start at a landmark you already know — a station, a river, a building — and you learn the few routes that connect it to the places you need. Over time, the routes generalise: every district has a main street, a station and a market.

AOSP is that city. The landmarks are the APIs and the processes. The routes are Binder calls and build dependencies. The generalisation is that every subsystem has the same shape: a public facade, a service, a native layer, a HAL, and a build file that names all of it.

Where the analogy breaks#

A city's districts grew organically; AOSP's shape was designed and then partly redesigned by Treble and Mainline. So the same shape does not apply everywhere: a Mainline module has an explicit boundary that an old system component does not, and a vendor HAL is deliberately outside the platform tree. When the shape does not fit, check whether the component predates Treble.

The big picture#

Loading diagram…
Text description of this diagram

A treemap of the AOSP top-level directories. frameworks is the largest group, containing base with the Java framework and system services, native with SurfaceFlinger and the native services, av with the media stack, and libs. packages is second, containing apps such as Settings, Launcher3 and SystemUI, providers, input methods and modules. system contains init, logd, the sepolicy sources and small helper libraries. art contains the runtime and the compiler. hardware contains the versioned HAL interfaces and the older C HALs. bionic contains libc and the dynamic linker. build contains Soong and the legacy Make build. device and vendor contain the device configurations, mostly reference targets in AOSP itself.

The treemap answers the first question of any source dive: where does this live? The answer is a group, then a directory, then a module, then a file. Four levels is usually enough.

Deep dive#

Start from an entry point#

There are four kinds of entry point, and which one you use depends on the question.

An API you call. If you are looking at PackageManager.getPackageInfo, the entry point is the public class in frameworks/base/core/java/android/content/pm/PackageManager.java. That class is a facade; the implementation is behind a Binder call.

A command you ran. If you ran adb shell dumpsys activity, the entry point is the onDump (or dump) method of the service that registered the activity name. dumpsys is a direct line into the service's internals.

A log line you saw. If a log line names a class, that class is a legitimate entry point. Search for the exact string, then read the method that emitted it.

A process you are curious about. If you want to know what surfaceflinger does, start at its main() and follow the initialisation. Services are easier to read bottom-up than top-down.

The mistake to avoid is starting from a directory listing and reading files in order. AOSP has no meaningful order, and the first file you open is probably not on the path to your answer.

Follow the Binder boundary#

Most of the framework is a client-server pair. The client is a Manager object in your process; the server is a service, usually in system_server. Between them is a generated Stub and Proxy and a parcel.

A concrete shape, in four steps:

// 1. The public facade, in the app's process.
public PackageInfo getPackageInfo(String packageName, int flags) {
    // ... argument checks ...
}
 
// 2. The facade holds an IPC interface, not the service.
private final IPackageManager mPM;

The IPackageManager interface is generated from AIDL. On the client side, the generated Proxy marshals the arguments into a Parcel and calls transact. On the server side, the generated Stub.onTransact unmarshals them and calls the real method, which lives in PackageManagerService.

Two habits follow.

When you see a Manager, look for the service. The method you are reading probably forwards. The interesting code is on the other side.

When you see transact, look for the interface. The method code in the parcel is generated from the AIDL declaration, and the declaration is the contract. Reading the AIDL file is faster than reading the generated Java.

Read the build file first#

Every module has an Android.bp. It is short, declarative, and it answers questions that the code does not:

android_app {
    name: "Settings",
    srcs: ["src/**/*.java"],
    platform_apis: true,
    certificate: "platform",
    privileged: true,
}

That snippet says: this is an app, it compiles against the platform APIs rather than the SDK, it is signed with the platform certificate, and it is a privileged app. Each of those facts changes how you read the code. A privileged app can hold permissions an ordinary app cannot; a platform-signed app has a different UID relationship with the system.

Soong is the build system, and Android.bp files are its input.1 The files are deliberately simple — no conditionals, no arbitrary code — because simplicity is what makes the build graph analysable. When you need conditional behaviour, it is expressed with select or with variants, not with an if.

What to skip#

Generated code is the largest source of wasted reading time in AOSP. Skip:

  • aidl_api/ and generated Stub/Proxy classes. Read the .aidl file instead.
  • Protobuf generated headers and sources. Read the .proto file.
  • R.java and the resource table. Read the XML resources.
  • The build output tree. It is a consequence, not a source.
  • Vendored third-party trees unless the bug is in the third party.

Generated code is not worthless: when a stack trace points into it, you need to map back to the declaration. But it is never the place to start.

Use the tools#

cs.android.com is a cross-referenced code search over the whole tree. It resolves symbols, shows call sites and lets you jump from an interface to its implementations.2 For "who calls this?", it is faster than reading.

git log -p <path> in a repo checkout gives the history of a file, with commit messages that often explain a design decision better than a comment.

repo forall runs a command across every repository, which is the only practical way to answer "which projects changed this symbol?".

git blame is useful on AOSP for a specific reason: the commit that introduced a line often carries the bug number and the rationale.

A worked example: reading a dumpsys output back to the source#

The method is easier to see with a concrete question. Suppose dumpsys activity services shows a service in a state you did not expect. The route back to the source is short:

  1. The service name in the output is the registration name, not the class name. Find the class that registers it by searching for the name as a string in system_server's tree.
  2. The dump method in that class prints the line you are reading. The line's wording is a searchable string, which takes you to the exact method.
  3. The state field it prints is a field on a record object. Find the enum or constant that assigns it.
  4. The assignment site is the policy. That is where the decision was made.

Four hops: name, line, field, assignment. Almost every dumpsys question resolves in that many steps, and each hop is a search rather than a guess.

Reading a subsystem end to end#

When you need to understand a whole subsystem rather than answer a question, use a fixed order:

  1. The interface. The AIDL or public API. What can a client ask for?
  2. The service's main loop and initialisation. What does it own, and when does it start?
  3. The state. What data structures hold the truth, and who mutates them?
  4. The concurrency. Which lock protects the state, and which threads enter it?
  5. The failure paths. What happens on a timeout, a death, or an invalid argument?

That order mirrors how the subsystem is built, and it produces a mental model you can debug from. Reading in file order produces a pile of facts with no structure.

How it evolved#

Architecture changes12 releases
APIVersionWhat changed
268.0 Oreo
  • Project Treble: the vendor interface separates vendor and system code
  • Background execution limits
  • Broadcast and service restrictions
  • HIDL for HALs
278.1 Oreo
  • Android Go: a lighter configuration of the same platform
289 Pie
  • App Standby Buckets
  • Mainline groundwork
  • Non-SDK interface restrictions (greylists) begin
  • BiometricPrompt
2910
  • Project Mainline: APEX and updatable system modules
  • Scoped storage
  • Bubbles
  • Project Mainline starts shipping through Play
3011
  • GKI: the Generic Kernel Image splits the kernel into core + vendor modules
  • App hibernation
  • Package visibility filtering
  • Incremental app installs
3112
  • Bluetooth permissions split into the nearby-devices family
  • Exact alarm permissions
  • Foreground service launch restrictions
3212L
  • Window size classes and activity embedding
3313 Tiramisu
  • Granular media permissions replace READ_EXTERNAL_STORAGE
  • The notification permission becomes a runtime permission
  • Predictive back gesture
3414 Upside Down Cake
  • Foreground service types are mandatory
  • Selected photos access via READ_MEDIA_VISUAL_USER_SELECTED
3515 Vanilla Ice Cream
  • 16 KB page size support for native libraries
  • Edge-to-edge is enforced when targeting 35
3616 Baklava
  • The release moves to Q2 (June) with a Q4 feature drop
  • Two SDK releases per year starting with 36
  • Desktop windowing arrives in developer options then stable
3717 Cinnamon Bun
  • Strict per-device app memory limits are enforced by the system
  • Generational garbage collection is added to ART's Concurrent Mark-Compact collector
  • A lock-free MessageQueue for apps targeting API 37
  • Compose-first: new platform and Jetpack APIs target Compose only; View libraries enter maintenance mode
  • Hybrid APK signing (v3.2) combines classical and ML-DSA post-quantum signatures
  • Native library dynamic code loading must be read-only

The rows show why the reading method changed: after Treble and Mainline, a subsystem may live in a module with an explicit boundary rather than in frameworks/base. The AOSP of 2026 is less monolithic than the AOSP of 2014, and the boundary you are reading across is part of the answer.

Under the hood#

Curiosity corner#

  • AOSP is not one repository. It is thousands, managed by repo. A single git clone gets you one project, not the platform.
  • The build system is mostly Go. Soong replaced Make for the platform build, and Android.bp files are its declarative input.
  • cs.android.com resolves symbols across repositories, which plain GitHub search cannot do for this tree.

Try it#

Find a class from the command line
repo init -u https://android.googlesource.com/platform/manifest -b android-latest-release
repo sync -c -j8
grep -rn "class ActivityThread" frameworks/base/core/java/android/app/
Example output
Illustrative — requires a local checkout.

Build it#

N01 — Build and boot AOSP is optional, but a checkout makes the rest of this module much more concrete.

Check yourself#

Three questions to answer without looking back. Open each one only after you have tried.

1. What does a Manager object in your app actually hold? A Binder proxy to a service, not the service itself. The policy lives on the service side, usually in system_server, and every call crosses a process boundary.

2. Why does the build use a declarative Android.bp instead of a script? Because a declarative module definition can be analysed. Soong can build a complete dependency graph before running anything, which is what makes Ninja parallel and incremental. Conditionals in a script would make the graph depend on execution.

3. What is the difference between reading a facade and reading a service? The facade defines the public contract — validation, defaults, deprecation. The service implements the behaviour and holds the state. A bug in behaviour is almost always on the service side; a compatibility question is almost always on the facade side.

Sources#

Sources

Every version number, API level, class name and date on this page is traced to one of these. Pages are re-verified against them; last_verified above says when.

  1. docsBuild Androidsource.android.comaccessed 2026-10-27↩
  2. sourceAndroid Code Search (cs.android.com)cs.android.comaccessed 2026-10-27↩
  3. docsAndroid platform architecture (AOSP)source.android.comaccessed 2026-10-27↩
  4. docsConfiguration (aconfig) overviewsource.android.comaccessed 2026-10-27↩
  5. sourceAndroid Gerrit code reviewandroid-review.googlesource.comaccessed 2026-10-27↩
  6. sourcesystem/core/init/README.mdandroid.googlesource.comaccessed 2026-10-27↩
  7. docsBinder overviewsource.android.comaccessed 2026-10-27↩