Chapter 24: Display System¶
Overview¶
The Android display system spans three major processes -- system_server,
surfaceflinger, and client applications -- and bridges two languages
(Java in the framework, C++ in the native compositor). Its responsibilities
range from discovering physical panels, through scheduling frame refresh at
precise VSYNC intervals, to compositing hundreds of graphical layers into a
single output image. This chapter examines every major subsystem: the Java-side
DisplayManagerService that owns display lifecycle; the DisplayArea
hierarchy that organises window Z-ordering; the VSYNC pipeline from hardware
interrupts through Choreographer; screen rotation and foldable display
management; display cutout and rounded-corner handling; the SurfaceFlinger
front-end refactor and CompositionEngine; buffer management through
BLASTBufferQueue; virtual displays and mirroring; colour management; and
display power control.
Readers who have worked through the graphics rendering pipeline in Chapter 9 and the system_server architecture in Chapter 14 will find this chapter naturally extends those foundations into the display-specific domain.
Key source files for this chapter:
| File | Description |
|---|---|
frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java |
Display lifecycle, adapter management (6601 lines) |
frameworks/base/services/core/java/com/android/server/display/LogicalDisplay.java |
Logical-to-physical mapping (1314 lines) |
frameworks/base/services/core/java/com/android/server/display/DisplayDevice.java |
Physical display abstraction |
frameworks/base/services/core/java/com/android/server/display/LogicalDisplayMapper.java |
Foldable display swapping |
frameworks/base/services/core/java/com/android/server/display/DisplayPowerController.java |
Display power state machine (3507 lines) |
frameworks/base/services/core/java/com/android/server/wm/DisplayArea.java |
DisplayArea container (882 lines) |
frameworks/base/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java |
Hierarchy construction (1052 lines) |
frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java |
Rotation policy (2255 lines) |
frameworks/base/services/core/java/com/android/server/wm/SeamlessRotator.java |
Zero-flicker rotation transform |
frameworks/base/services/core/java/com/android/server/wm/AsyncRotationController.java |
Async non-activity window rotation |
frameworks/native/services/surfaceflinger/Scheduler/Scheduler.h |
SurfaceFlinger frame scheduler |
frameworks/native/services/surfaceflinger/Scheduler/RefreshRateSelector.h |
Refresh rate selection policy |
frameworks/native/services/surfaceflinger/Scheduler/VSyncPredictor.h |
VSYNC timing model |
frameworks/native/services/surfaceflinger/Scheduler/EventThread.h |
VSYNC event distribution |
frameworks/native/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h |
Front-end layer lifecycle |
frameworks/native/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h |
Snapshot construction for composition |
frameworks/native/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h |
Composition orchestration |
frameworks/native/libs/gui/include/gui/BLASTBufferQueue.h |
BLAST buffer delivery |
frameworks/base/core/java/android/view/DisplayCutout.java |
Cutout geometry |
frameworks/base/services/core/java/com/android/server/display/color/ColorDisplayService.java |
Colour transform pipeline |
frameworks/base/services/core/java/com/android/server/display/color/DisplayTransformManager.java |
SurfaceFlinger colour matrix dispatch |
frameworks/base/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java |
Foldable device state machine |
24.1 Display System Architecture¶
24.1.1 The Three-Layer Model¶
Android's display subsystem is organised into three distinct layers, each running in a different process and address space:
graph TB
subgraph "Application Process"
VRI["ViewRootImpl"]
CH["Choreographer"]
BBQ["BLASTBufferQueue"]
end
subgraph "system_server"
DMS["DisplayManagerService<br/>(6601 lines)"]
WMS["WindowManagerService"]
DPC["DisplayPowerController"]
CDS["ColorDisplayService"]
DSMS["DeviceStateManagerService"]
end
subgraph "surfaceflinger"
SCH["Scheduler"]
FE["FrontEnd<br/>(LayerLifecycleManager)"]
CE["CompositionEngine"]
HWC["HWComposer (HAL)"]
end
subgraph "Kernel / Hardware"
DRM["DRM/KMS"]
PANEL["Display Panel"]
end
VRI -->|"relayout/setFrame"| WMS
CH -->|"VSYNC callback"| VRI
BBQ -->|"SurfaceControl.Transaction"| FE
DMS -->|"DisplayAdapter events"| WMS
DMS -->|"DisplayPowerRequest"| DPC
DMS <-->|"device state changes"| DSMS
WMS -->|"layer state"| FE
SCH -->|"VSYNC"| CE
FE -->|"LayerSnapshots"| CE
CE -->|"HWC calls"| HWC
HWC -->|"DRM ioctl"| DRM
DRM -->|"scanout"| PANEL
Layer 1 -- Framework (system_server). DisplayManagerService owns the
lifecycle of every display. It discovers physical displays through
DisplayAdapter implementations, creates LogicalDisplay objects that map
to physical DisplayDevice instances, and notifies WindowManagerService
of display additions, removals, and configuration changes.
Layer 2 -- Native compositor (surfaceflinger). SurfaceFlinger receives
buffer updates through SurfaceControl.Transaction, schedules composition
on VSYNC, and delegates the actual pixel blending to either the Hardware
Composer HAL (overlay planes) or the GPU (client composition via
RenderEngine).
Layer 3 -- Kernel (DRM/KMS). The Linux DRM subsystem manages display hardware: mode setting, CRTC/encoder/connector topology, and page-flip ioctls that trigger scanout of composed framebuffers.
24.1.2 DisplayManagerService¶
DisplayManagerService (DMS) is a SystemService registered during
system_server boot. At 6601 lines, it is one of the largest services in
the framework. Its Javadoc explains the architecture:
The DisplayManagerService manages the global lifecycle of displays, decides how to configure logical displays based on the physical display devices currently attached, sends notifications to the system and to applications when the state changes.
DMS uses the DisplayThread (a shared HandlerThread at
THREAD_PRIORITY_DISPLAY) for its main handler. All internal state is
protected by a single SyncRoot lock -- the same lock used by all display
adapters and logical display objects:
// frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java
private final SyncRoot mSyncRoot = new SyncRoot();
The lock ordering constraint is critical: DMS may hold mSyncRoot and call
into SurfaceFlinger (via SurfaceControl), but it must never call into
WindowManagerService while holding mSyncRoot because WMS holds its own
mGlobalLock and may call back into DMS. All potentially reentrant
out-calls are dispatched asynchronously through the handler.
24.1.3 Display Adapter Architecture¶
DMS discovers displays through a set of DisplayAdapter implementations:
classDiagram
class DisplayAdapter {
<<abstract>>
+registerLocked()
+Listener onDisplayDeviceEvent()
}
class LocalDisplayAdapter {
-mPhysicalDisplays: SparseArray
+hotplugEventLocked()
}
class VirtualDisplayAdapter {
-mVirtualDisplayDevices: ArrayMap
+createVirtualDisplayLocked()
}
class WifiDisplayAdapter {
-mWifiDisplayController
+requestConnectLocked()
}
class OverlayDisplayAdapter {
-mOverlays: ArrayList
+updateOverlayDisplayDevicesLocked()
}
DisplayAdapter <|-- LocalDisplayAdapter
DisplayAdapter <|-- VirtualDisplayAdapter
DisplayAdapter <|-- WifiDisplayAdapter
DisplayAdapter <|-- OverlayDisplayAdapter
class DisplayDeviceRepository {
-mDisplayDevices: List
+onDisplayDeviceEvent()
+Listener interface
}
DisplayAdapter --> DisplayDeviceRepository : reports events
-
LocalDisplayAdapter handles physical displays (built-in and external) reported by SurfaceFlinger's hotplug mechanism. It receives
EVENT_ADD,EVENT_REMOVE, andEVENT_CHANGEnotifications and createsLocalDisplayDeviceinstances backed by a SurfaceFlinger display token. -
VirtualDisplayAdapter creates virtual displays on behalf of applications, receiving a
VirtualDisplayConfigwith dimensions, density, flags, and anIVirtualDisplayCallbackfor lifecycle management. -
WifiDisplayAdapter manages Miracast (Wi-Fi Display / WFD) connections via
WifiDisplayController. -
OverlayDisplayAdapter creates developer overlay displays parsed from the
persist.sys.overlay_displaysystem property.
All adapters report to DisplayDeviceRepository, which maintains the
canonical list of active DisplayDevice objects and notifies DMS of changes.
24.1.4 LogicalDisplay and Physical Mapping¶
The separation between LogicalDisplay and DisplayDevice is fundamental.
A LogicalDisplay represents a display as seen by the rest of the system
(window manager, applications), while a DisplayDevice represents the
underlying physical or virtual hardware.
classDiagram
class LogicalDisplay {
-mDisplayId: int
-mLayerStack: int
-mPrimaryDisplayDevice: DisplayDevice
-mOverrideDisplayInfo: DisplayInfo
-mDesiredDisplayModeSpecs
-mIsEnabled: boolean
-mIsInTransition: boolean
+getDisplayInfoLocked()
+configureDisplayLocked()
+setDisplayInfoOverrideFromWindowManagerLocked()
}
class DisplayDevice {
<<abstract>>
-mDisplayAdapter: DisplayAdapter
-mDisplayToken: IBinder
-mUniqueId: String
-mCurrentLayerStack: int
+getDisplayDeviceInfoLocked()
+performTraversalLocked()
+getDisplaySurfaceDefaultSizeLocked()
}
class DisplayDeviceInfo {
+width: int
+height: int
+densityDpi: float
+xDpi: float
+yDpi: float
+refreshRate: float
+supportedModes: Display.Mode[]
+type: int
+flags: int
}
LogicalDisplay "1" --> "1" DisplayDevice : mPrimaryDisplayDevice
DisplayDevice --> DisplayDeviceInfo : getDisplayDeviceInfoLocked()
The key design insight, stated in the LogicalDisplay Javadoc:
Logical displays and display devices are orthogonal concepts. Some mapping will exist between logical displays and display devices but it can be many-to-many and some might have no relation at all.
In practice, for single-display phones the mapping is 1:1. For foldables,
the mapping becomes dynamic -- a single logical display (the default display,
ID 0) can be swapped between the inner and outer physical display devices
during fold/unfold transitions. This swapping is managed by
LogicalDisplayMapper.
24.1.5 Display Configuration Flow¶
When a display is first connected, the configuration flows through multiple components:
sequenceDiagram
participant HW as Hardware / SurfaceFlinger
participant LDA as LocalDisplayAdapter
participant DDR as DisplayDeviceRepository
participant LDM as LogicalDisplayMapper
participant DMS as DisplayManagerService
participant WMS as WindowManagerService
HW->>LDA: Hotplug callback (connected)
LDA->>LDA: Create LocalDisplayDevice
LDA->>DDR: sendDisplayDeviceEventLocked(device, ADDED)
DDR->>LDM: onDisplayDeviceAdded(device)
LDM->>LDM: Create LogicalDisplay with assigned displayId
LDM->>DMS: Listener.onLogicalDisplayEventLocked(ADDED)
DMS->>DMS: sendDisplayEventLocked(EVENT_ADDED)
DMS->>WMS: onDisplayAdded(displayId)
WMS->>WMS: Create DisplayContent, build DisplayArea hierarchy
DMS maintains two critical data structures for event delivery:
// All callback records indexed by calling process id
private final SparseArray<CallbackRecord> mCallbacks = new SparseArray<>();
// All callback records indexed by [uid][pid]
private final SparseArray<SparseArray<CallbackRecord>> mCallbackRecordByPidByUid =
new SparseArray<>();
Events are delivered via MSG_DELIVER_DISPLAY_EVENT posted to the handler,
ensuring asynchronous delivery without holding mSyncRoot.
24.1.6 Display Groups¶
Displays are organised into DisplayGroup instances that share power state
and brightness. The primary display group contains the built-in display(s);
virtual displays may create their own groups using
VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP or be part of the device display
group using VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP. The
DisplayGroupAllocator assigns group IDs:
// LogicalDisplayMapper events related to groups
public static final int DISPLAY_GROUP_EVENT_ADDED = 1;
public static final int DISPLAY_GROUP_EVENT_CHANGED = 2;
public static final int DISPLAY_GROUP_EVENT_REMOVED = 3;
Display groups affect power management -- when the default display group goes to sleep, all displays in that group turn off together.
24.1.7 DisplayInfo and Overrides¶
The DisplayInfo object visible to applications is constructed through a
layered override mechanism:
- Base info -- Derived from
DisplayDeviceInfoof the primary display device (physical size, density, supported modes). - DMS overrides -- Display mode selection, user-disabled HDR types, frame rate overrides.
- WMS overrides -- Window manager sets
DisplayInfofields for app-visible size (accounting for overscan, cutout, rotation). These are applied viasetDisplayInfoOverrideFromWindowManagerLocked().
The WM_OVERRIDE_FIELDS constant set in DisplayInfoOverrides defines
exactly which fields WMS is permitted to override, preventing accidental
clobbering of hardware-derived values.
24.1.8 DisplayBlanker: Power State Coordination¶
The DisplayBlanker interface provides the bridge between
DisplayPowerController and SurfaceFlinger for display power state
changes. DMS implements an anonymous DisplayBlanker that coordinates
state changes across multiple displays:
// frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java
private final DisplayBlanker mDisplayBlanker = new DisplayBlanker() {
@Override
public synchronized void requestDisplayState(int displayId, int state,
float brightness, float sdrBrightness) {
// Check if ALL displays are inactive or off
boolean allInactive = true;
boolean allOff = true;
// ... iterate over mDisplayStates
if (state == Display.STATE_OFF) {
requestDisplayStateInternal(displayId, state, brightness, sdrBrightness);
}
if (stateChanged) {
mDisplayPowerCallbacks.onDisplayStateChange(allInactive, allOff);
}
if (state != Display.STATE_OFF) {
requestDisplayStateInternal(displayId, state, brightness, sdrBrightness);
}
}
};
The ordering is critical: for OFF transitions, the display state is set before notifying PowerManager; for ON transitions, PowerManager is notified first. This prevents race conditions where the system thinks the display is on while it is still powering down.
24.1.9 Display Mode Director¶
DisplayModeDirector sits between DisplayManagerService and
RefreshRateSelector, translating high-level mode requests from various
sources (app, settings, performance hints) into DesiredDisplayModeSpecs:
graph TD
subgraph "Vote Sources"
APP["App Request<br/>(setFrameRate)"]
SET["Settings<br/>(peak refresh rate)"]
PERF["Performance Hint<br/>(game mode)"]
LOW["Low Power Mode"]
PROX["Proximity"]
SKIN["Skin Temperature"]
end
subgraph "DisplayModeDirector"
DIR["Vote Aggregation"]
SPEC["DesiredDisplayModeSpecs"]
end
APP --> DIR
SET --> DIR
PERF --> DIR
LOW --> DIR
PROX --> DIR
SKIN --> DIR
DIR --> SPEC
SPEC --> DMS_OUT["DisplayManagerService<br/>(applies to LogicalDisplay)"]
Each vote source has a priority, and the director resolves conflicts by prioritising system constraints (thermal, low power) over app requests.
24.1.10 Handler Message Protocol¶
DMS uses a handler-based message protocol for asynchronous operations:
| Message | Constant | Purpose |
|---|---|---|
| Register default adapters | MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS (1) |
Boot-time adapter setup |
| Register additional adapters | MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS (2) |
Post-boot adapter setup |
| Deliver display event | MSG_DELIVER_DISPLAY_EVENT (3) |
Notify callbacks of display changes |
| Request traversal | MSG_REQUEST_TRAVERSAL (4) |
Trigger SurfaceFlinger display configuration |
| Update viewport | MSG_UPDATE_VIEWPORT (5) |
Update input viewport mappings |
| Load brightness configs | MSG_LOAD_BRIGHTNESS_CONFIGURATIONS (6) |
Load brightness curves |
| Frame rate override event | MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE (7) |
Notify of FRO changes |
| Display group event | MSG_DELIVER_DISPLAY_GROUP_EVENT (8) |
Notify of group additions/removals |
| Device state received | MSG_RECEIVED_DEVICE_STATE (9) |
Process foldable state change |
| Dispatch pending events | MSG_DISPATCH_PENDING_PROCESS_EVENTS (10) |
Batch event delivery |
The MSG_REQUEST_TRAVERSAL message is particularly important: when
display configuration changes, DMS must schedule a traversal in
SurfaceFlinger to apply the new display parameters (layer stack
assignment, display projection, display mode).
24.2 DisplayArea Hierarchy¶
24.2.1 What Is a DisplayArea?¶
Below DisplayContent (the WindowContainer that represents a full logical
display), Android organises windows into a tree of DisplayArea containers.
Each DisplayArea groups windows that share a common feature or Z-order
region. The class hierarchy is:
classDiagram
class WindowContainer~T~ {
+getChildCount()
+getChildAt(index)
+positionChildAt()
}
class DisplayArea~T~ {
#mType: Type
#mName: String
#mFeatureId: int
#mOrganizer: IDisplayAreaOrganizer
+setIgnoreOrientationRequest()
}
class RootDisplayArea {
+mFeatureId: int
}
class TaskDisplayArea {
+mTasks: ArrayList
}
class DisplayContent {
+mDisplayId: int
}
class DisplayArea_Tokens {
+addChild(WindowToken)
}
class DisplayArea_Dimmable {
+dim layer support
}
WindowContainer <|-- DisplayArea
DisplayArea <|-- RootDisplayArea
DisplayArea <|-- TaskDisplayArea
RootDisplayArea <|-- DisplayContent
DisplayArea <|-- DisplayArea_Tokens
DisplayArea <|-- DisplayArea_Dimmable
The Javadoc for DisplayArea explains the three flavours that enforce
Z-order correctness:
DisplayAreas come in three flavors: - BELOW_TASKS: Can only contain BELOW_TASK DisplayAreas and WindowTokens that go below tasks. - ABOVE_TASKS: Can only contain ABOVE_TASK DisplayAreas and WindowTokens that go above tasks. - ANY: Can contain any kind of DisplayArea, and any kind of WindowToken or the Task container.
24.2.2 Feature IDs¶
Each DisplayArea carries a mFeatureId that identifies its purpose. The
standard feature IDs are defined in DisplayAreaOrganizer:
| Feature ID | Constant | Value | Purpose |
|---|---|---|---|
FEATURE_ROOT |
FEATURE_SYSTEM_FIRST |
0 | Root of the hierarchy |
FEATURE_DEFAULT_TASK_CONTAINER |
FEATURE_SYSTEM_FIRST + 1 |
1 | Default container for Tasks |
FEATURE_WINDOW_TOKENS |
FEATURE_SYSTEM_FIRST + 2 |
2 | Container for non-Task window tokens |
FEATURE_ONE_HANDED |
FEATURE_SYSTEM_FIRST + 3 |
3 | One-handed mode scaling |
FEATURE_WINDOWED_MAGNIFICATION |
FEATURE_SYSTEM_FIRST + 4 |
4 | Windowed accessibility magnification |
FEATURE_FULLSCREEN_MAGNIFICATION |
FEATURE_SYSTEM_FIRST + 5 |
5 | Fullscreen magnification |
FEATURE_HIDE_DISPLAY_CUTOUT |
FEATURE_SYSTEM_FIRST + 6 |
6 | Content below cutout |
FEATURE_IME_PLACEHOLDER |
FEATURE_SYSTEM_FIRST + 7 |
7 | IME container position |
FEATURE_IME |
FEATURE_SYSTEM_FIRST + 8 |
8 | Actual IME container |
FEATURE_WINDOWING_LAYER |
FEATURE_SYSTEM_FIRST + 9 |
9 | Fallback windowing layer |
FEATURE_APP_ZOOM_OUT |
FEATURE_SYSTEM_FIRST + 10 |
10 | App zoom-out support |
Vendor features use FEATURE_VENDOR_FIRST (10001) through
FEATURE_VENDOR_LAST (20001), allowing OEMs to define custom DisplayArea
hierarchy nodes (e.g., automotive rear-display areas, dual-screen features).
24.2.3 DisplayAreaPolicyBuilder¶
The DisplayAreaPolicyBuilder constructs the hierarchy tree by taking a set
of Feature definitions and building the necessary intermediate
DisplayArea nodes to satisfy the Z-ordering constraints.
A typical AOSP DefaultDisplayAreaPolicy builds this hierarchy:
graph TD
DC["DisplayContent<br/>(RootDisplayArea, FEATURE_ROOT)"]
WM["WindowedMagnification<br/>(FEATURE_WINDOWED_MAGNIFICATION)"]
BT["DisplayArea.Tokens<br/>(Wallpapers below tasks)"]
TDA["TaskDisplayArea<br/>(FEATURE_DEFAULT_TASK_CONTAINER)"]
ATI["DisplayArea.Tokens<br/>(Above tasks, below IME)"]
IME["ImeContainers<br/>(FEATURE_IME_PLACEHOLDER)"]
ATW["DisplayArea.Tokens<br/>(Above IME, below Magnification Overlay)"]
MAG["DisplayArea.Tokens<br/>(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY and above)"]
DC --> WM
DC --> MAG
WM --> BT
WM --> TDA
WM --> ATI
WM --> IME
WM --> ATW
The builder works by:
- Collecting all
Featuredefinitions, each targeting a range of window types (e.g., "WindowedMagnification" covers everything up toTYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY). - For each Z-order slot (the 36-layer model from window type constants), determining which features apply.
- Creating intermediate
DisplayAreanodes wherever a feature boundary crosses a Z-order boundary, splitting the tree to maintain correct ordering.
24.2.4 DisplayAreaGroup for Multi-Root Hierarchies¶
The builder supports multiple root hierarchies through
DisplayAreaGroup, which is critical for automotive and foldable devices.
The code example in DisplayAreaPolicyBuilder shows how to create
separate roots for front and rear displays:
// Example from DisplayAreaPolicyBuilder Javadoc:
RootDisplayArea firstRoot = new RootDisplayArea(wmService, "FirstRoot",
FEATURE_FIRST_ROOT);
DisplayAreaPolicyBuilder.HierarchyBuilder firstGroupHierarchy =
new DisplayAreaPolicyBuilder.HierarchyBuilder(firstRoot)
.setTaskDisplayAreas(firstTdaList);
return new DisplayAreaPolicyBuilder()
.setRootHierarchy(rootHierarchy)
.addDisplayAreaGroupHierarchy(firstGroupHierarchy)
.setSelectRootForWindowFunc(selectRootForWindowFunc)
.build(wmService, content);
The selectRootForWindowFunc is a BiFunction<Integer, Bundle,
RootDisplayArea> that routes each window token to the appropriate root
based on window type and launch options.
24.2.5 Hierarchy Validation Rules¶
The DisplayAreaPolicyBuilder.validate() method enforces strict structural
constraints on the hierarchy:
- Unique IDs for roots and TDAs: Every
RootDisplayAreaandTaskDisplayAreamust have a globally unique feature ID. - Unique feature IDs per root:
Featurenodes below the sameRootDisplayAreamust have unique IDs, but features below different roots may share IDs (enabling cross-root organizing). - Exactly one IME container: The IME container must exist in exactly one hierarchy builder.
- Exactly one default TDA: One
TaskDisplayAreamust have the IDFEATURE_DEFAULT_TASK_CONTAINER. - ID range limit: No ID may exceed
FEATURE_VENDOR_LAST(20001). - Valid windowing layer: The root hierarchy must contain a windowing
layer (
FEATURE_WINDOWED_MAGNIFICATIONorFEATURE_WINDOWING_LAYER) at the top level. If absent, the builder automatically inserts aFEATURE_WINDOWING_LAYER.
// frameworks/base/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
if (!mRootHierarchyBuilder.hasValidWindowingLayer()) {
mRootHierarchyBuilder.mFeatures.add(0 /* top level index */,
new Feature.Builder(wmService.mPolicy, "WindowingLayer",
FEATURE_WINDOWING_LAYER)
.setExcludeRoundedCornerOverlay(false).all().build());
}
24.2.6 Feature Definition and Window Type Targeting¶
Each Feature targets a set of window types using a builder pattern
that supports ranges and exceptions:
// Example: WindowedMagnification targets everything below
// the accessibility magnification overlay
new Feature.Builder(wmService.mPolicy, "WindowedMagnification",
FEATURE_WINDOWED_MAGNIFICATION)
.upTo(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY)
.except(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY)
.setNewDisplayAreaSupplier(DisplayArea.Dimmable::new)
.build()
The Feature.Builder methods:
all()-- Target all window typesupTo(type)-- Target all types up to and including the given typeexcept(type)-- Exclude a specific type from the rangeand(type)-- Add a specific type to the setsetNewDisplayAreaSupplier()-- Custom DisplayArea factory (e.g.,Dimmablefor magnification dimming support)setExcludeRoundedCornerOverlay()-- Whether to exclude rounded corner overlay windows
24.2.7 The Build Algorithm¶
The HierarchyBuilder.build() method implements the core algorithm for
generating the DisplayArea tree:
flowchart TD
A["Collect all Features"] --> B["Sort by Z-order precedence"]
B --> C["For each Z-order slot (0..36):"]
C --> D["Determine which Features<br/>include this slot"]
D --> E{"Same Feature set as<br/>previous slot?"}
E -->|"Yes"| F["Extend current leaf DA"]
E -->|"No"| G["Create new intermediate<br/>DisplayArea nodes"]
G --> H["Close DAs for Features<br/>that no longer apply"]
H --> I["Open DAs for Features<br/>that newly apply"]
I --> F
F --> J{"More slots?"}
J -->|"Yes"| C
J -->|"No"| K["Attach TDA at APPLICATION_LAYER"]
K --> L["Attach IME container"]
L --> M["Complete hierarchy"]
The algorithm ensures that:
- A
DisplayAreaexists for each contiguous range of Z-order slots that share the same Feature set. - Features that span only a subset of the Z-order space get their own
nested
DisplayAreanodes. - The
TaskDisplayAreais inserted at exactlyAPPLICATION_LAYER(the Z-order position between below-task and above-task windows).
24.2.8 DefaultSelectRootForWindowFunction¶
When multiple roots exist (e.g., automotive front/rear displays), the
DefaultSelectRootForWindowFunction routes window tokens:
// frameworks/base/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
public RootDisplayArea apply(Integer windowType, Bundle options) {
if (mDisplayAreaGroupRoots.isEmpty()) {
return mDisplayRoot;
}
if (options != null) {
final int rootId = options.getInt(KEY_ROOT_DISPLAY_AREA_ID, FEATURE_UNDEFINED);
if (rootId != FEATURE_UNDEFINED) {
for (RootDisplayArea root : mDisplayAreaGroupRoots) {
if (root.mFeatureId == rootId) return root;
}
}
}
return mDisplayRoot;
}
The routing key is KEY_ROOT_DISPLAY_AREA_ID in the ActivityOptions
bundle, allowing launchers and system components to direct windows to
specific roots.
24.2.9 DisplayArea Organizers¶
Shell and SystemUI can register IDisplayAreaOrganizer implementations to
receive callbacks when specific feature DisplayAreas appear, change, or
vanish. This is the mechanism that enables:
- One-handed mode: Registers for
FEATURE_ONE_HANDED, then scales and translates the DisplayArea. - Windowed magnification: Registers for
FEATURE_WINDOWED_MAGNIFICATION. - App zoom-out: Registers for
FEATURE_APP_ZOOM_OUT.
The DisplayAreaOrganizerController manages the registration and dispatches
onDisplayAreaAppeared, onDisplayAreaInfoChanged, and
onDisplayAreaVanished callbacks. The organizer receives a
SurfaceControl leash that it can reparent or transform.
24.2.10 Orientation Handling in DisplayAreas¶
DisplayArea has a critical role in orientation management through the
mSetIgnoreOrientationRequest flag. When set, the DisplayArea ignores
fixed-orientation requests from apps below it, showing them in letterbox
instead of rotating the entire display:
// frameworks/base/services/core/java/com/android/server/wm/DisplayArea.java
boolean setIgnoreOrientationRequest(boolean ignoreOrientationRequest) {
if (mSetIgnoreOrientationRequest == ignoreOrientationRequest) {
return false;
}
mSetIgnoreOrientationRequest = ignoreOrientationRequest;
// Check whether we should notify Display to update orientation
// ...
}
This is used on large-screen devices (tablets, foldables in open posture) where rotating the entire display for a portrait-only app would be undesirable. The DisplayArea suppresses the orientation request, and the app is shown letterboxed within the current display orientation.
24.3 Display Refresh and VSYNC¶
24.3.1 The VSYNC Pipeline¶
VSYNC (Vertical Synchronization) is the heartbeat of the display system. Every frame displayed on screen begins with a VSYNC signal from the display hardware. Android's VSYNC pipeline transforms raw hardware interrupts into precisely timed callbacks at multiple points in the rendering chain.
graph LR
subgraph "Hardware"
HWV["HW VSYNC<br/>(display panel interrupt)"]
end
subgraph "SurfaceFlinger Scheduler"
VP["VSyncPredictor<br/>(timing model)"]
VD["VSyncDispatchTimerQueue<br/>(callback scheduling)"]
ET_SF["EventThread<br/>(sf)"]
ET_APP["EventThread<br/>(app)"]
end
subgraph "system_server"
CH["Choreographer"]
VRI2["ViewRootImpl"]
end
subgraph "App Process"
APP["Application<br/>onDraw()"]
end
HWV -->|"timestamp samples"| VP
VP -->|"predicted VSYNC"| VD
VD -->|"sf callback"| ET_SF
VD -->|"app callback"| ET_APP
ET_APP -->|"VSYNC event via BitTube"| CH
CH -->|"doFrame()"| VRI2
VRI2 -->|"measure/layout/draw"| APP
ET_SF -->|"wakeup"| CE2["CompositionEngine<br/>present()"]
24.3.2 VSyncPredictor: The Timing Model¶
VSyncPredictor maintains a linear regression model of VSYNC timing.
Rather than relying solely on the latest hardware timestamp, it collects a
history of timestamps and fits a line (slope + intercept) to predict future
VSYNC events:
// frameworks/native/services/surfaceflinger/Scheduler/VSyncPredictor.h
struct Model {
nsecs_t slope; // period between VSYNCs
nsecs_t intercept; // phase offset
};
The predictor accepts timestamps via addVsyncTimestamp(), filters outliers
(using outlierTolerancePercent), and requires a minimum number of samples
(minimumSamplesForPrediction) before generating predictions. This
filtering is essential because hardware VSYNC timestamps can jitter by
tens of microseconds due to display controller timing granularity.
The nextAnticipatedVSyncTimeFrom() method returns the next predicted
VSYNC time from a given timepoint, which is used by the dispatch system to
schedule callbacks precisely.
24.3.3 VSyncDispatchTimerQueue: Callback Scheduling¶
VSyncDispatchTimerQueue translates predicted VSYNC times into actual
timer-based wakeups. Each registered callback is represented by a
VSyncDispatchTimerQueueEntry with three states:
stateDiagram-v2
[*] --> Disarmed
Disarmed --> Armed : schedule
Armed --> Running : timer fires, executing
Running --> Disarmed : callback completes
Armed --> Disarmed : cancel
Each entry carries ScheduleTiming that specifies:
- workDuration -- how long before the VSYNC the callback needs to wake up (e.g., app rendering might need 16ms)
- readyDuration -- additional time needed after work completes before the VSYNC deadline
- earliestVsync -- the earliest VSYNC this callback is interested in
The timer queue coalesces callbacks that are close in time (within
timerSlack) into a single timer wakeup, reducing the number of context
switches.
24.3.4 EventThread: VSYNC Distribution to Clients¶
Two EventThread instances run in SurfaceFlinger:
- sf EventThread -- Wakes SurfaceFlinger's main loop for composition.
- app EventThread -- Distributes VSYNC events to applications via
IDisplayEventConnection/BitTube.
EventThreadConnection wraps a BitTube (a socket pair) for zero-copy
VSYNC event delivery to the client. The connection supports three request
modes:
// frameworks/native/services/surfaceflinger/Scheduler/EventThread.h
enum class VSyncRequest {
None = -2, // No VSYNC events
Single = -1, // Wake for next two frames (avoid scheduler overhead)
SingleSuppressCallback = 0, // Wake for next frame only
Periodic = 1, // Continuous VSYNC delivery
// Values > 1 specify a divisor (every Nth VSYNC)
};
24.3.5 Choreographer: Java-Side VSYNC Consumption¶
On the Java side, Choreographer receives VSYNC events from the app
EventThread through a DisplayEventReceiver and dispatches them to
registered callbacks in priority order:
- CALLBACK_INPUT -- Input event processing
- CALLBACK_ANIMATION -- Property animations, Transitions
- CALLBACK_INSETS_ANIMATION -- WindowInsets animations
- CALLBACK_TRAVERSAL -- View measure/layout/draw
- CALLBACK_COMMIT -- Post-draw commit
Each Activity's ViewRootImpl registers a CALLBACK_TRAVERSAL with
Choreographer. When requestLayout() or invalidate() is called, the
ViewRootImpl schedules itself with Choreographer, which waits for the next
VSYNC before executing the traversal.
24.3.6 RefreshRateSelector: Display Mode Selection¶
RefreshRateSelector is the policy engine that selects the optimal display
refresh rate from the modes supported by the hardware. It considers
multiple inputs:
graph TD
subgraph "Layer Votes"
LR1["Layer 1: ExplicitExact 60Hz"]
LR2["Layer 2: Heuristic 90Hz"]
LR3["Layer 3: NoVote"]
LR4["Layer 4: ExplicitCategory Normal"]
end
subgraph "Global Signals"
IDLE["Idle timer expired"]
TOUCH["Touch active"]
PWR["Power hint"]
end
subgraph "Policy"
DMP["DisplayManager Policy<br/>(primaryRanges, appRequestRanges)"]
OVR["Override Policy<br/>(CTS testing)"]
end
LR1 --> RRS["RefreshRateSelector"]
LR2 --> RRS
LR3 --> RRS
LR4 --> RRS
IDLE --> RRS
TOUCH --> RRS
PWR --> RRS
DMP --> RRS
OVR --> RRS
RRS --> MODE["Selected FrameRateMode<br/>(e.g., 120Hz display, 60Hz render)"]
The LayerVoteType enum captures the different ways a layer can express
its refresh rate preference:
| Vote Type | Description |
|---|---|
NoVote |
Layer does not care about refresh rate |
Min |
Requests minimum available rate (power saving) |
Max |
Requests maximum available rate (smoothness) |
Heuristic |
Platform-calculated rate from content analysis |
ExplicitDefault |
App-requested rate with Default compatibility |
ExplicitExactOrMultiple |
App-requested rate, exact or integer multiple |
ExplicitExact |
App-requested rate, exact match only |
ExplicitGte |
App-requested rate, greater-than-or-equal |
ExplicitCategory |
App-requested frame rate category (Normal/High) |
The selector operates under a Policy that defines:
defaultMode-- the mode to use when there are no strong preferencesprimaryRanges-- the physical and render FPS rangesappRequestRanges-- the app-visible FPS rangesallowGroupSwitching-- whether to switch between mode groupsidleScreenConfigOpt-- idle timer configuration
The kMinSupportedFrameRate is 20 Hz -- below this, content would appear visibly choppy. Frame rate categories define thresholds: Normal starts at 60 Hz, High starts at 90 Hz.
24.3.7 Scheduler: Orchestrating Frame Production¶
The Scheduler class is the top-level coordinator that ties together VSYNC
prediction, event threading, and mode selection. It inherits from both
IEventThreadCallback and MessageQueue:
// frameworks/native/services/surfaceflinger/Scheduler/Scheduler.h
class Scheduler : public IEventThreadCallback, android::impl::MessageQueue {
// ...
};
Key concepts:
-
Pacesetter display: In multi-display configurations, one physical display is designated the "pacesetter" that drives the composition cadence. The scheduler uses
designatePacesetterDisplay()to select it automatically, orforcePacesetterDisplay()to override. -
VsyncModulator: Adjusts VSYNC offsets dynamically based on workload. When a frame is about to miss its deadline, the modulator can advance the app VSYNC phase to give more rendering time.
-
LayerHistory: Tracks per-layer frame production rates using heuristics to provide
LayerRequirementinputs toRefreshRateSelector.
24.3.8 VsyncConfiguration: Phase Offsets¶
VsyncConfiguration maps refresh rates to VSYNC offset configurations.
Each configuration defines timing for three scenarios:
// frameworks/native/services/surfaceflinger/Scheduler/VsyncConfiguration.h
struct VsyncConfigSet {
VsyncConfig early; // During transaction processing
VsyncConfig earlyGpu; // During GPU composition
VsyncConfig late; // Normal steady-state
};
Each VsyncConfig contains:
- sfOffset / sfWorkDuration: When SurfaceFlinger wakes relative to VSYNC
- appOffset / appWorkDuration: When apps wake relative to VSYNC
The offset strategy:
- Late (normal): App wakes early in the VSYNC period, renders, then SF wakes later to composite and present. This maximises the time available for app rendering.
- Early (transaction heavy): Both app and SF wake earlier to handle the extra transaction processing work.
- Early GPU (GPU composition): SF wakes earlier because GPU composition takes longer than HWC overlay composition.
The legacy PhaseOffsets implementation used fixed nanosecond offsets.
The modern WorkDuration implementation uses duration-based scheduling
that adapts better to different refresh rates:
gantt
title VSYNC Phase Offsets (16.67ms period)
dateFormat X
axisFormat %sms
section Late Config
App work :app, 0, 10
SF work :sf, 10, 14
HWC present :hwc, 14, 16
VSYNC :milestone, 16, 16
section Early Config
App work (early) :app2, 0, 8
SF work (early) :sf2, 6, 12
HWC present :hwc2, 12, 16
VSYNC :milestone, 16, 16
24.3.9 VsyncModulator: Dynamic Offset Switching¶
VsyncModulator switches between Early, EarlyGpu, and Late VSYNC
configurations based on runtime conditions:
// frameworks/native/services/surfaceflinger/Scheduler/VsyncModulator.h
class VsyncModulator : public IBinder::DeathRecipient {
static constexpr int MIN_EARLY_TRANSACTION_FRAMES = 2;
static constexpr int MIN_EARLY_GPU_FRAMES = 2;
// ...
};
The modulator maintains frame counters:
- Early transaction frames: After a transaction is scheduled, keep
early offsets for at least
MIN_EARLY_TRANSACTION_FRAMES(2) frames plus a time delay (MIN_EARLY_TRANSACTION_TIME) to avoid races with transaction commit. - Early GPU frames: After GPU composition is used, keep early GPU
offsets for
MIN_EARLY_GPU_FRAMES(2) frames as a low-pass filter against alternating composition strategies.
The state transitions:
stateDiagram-v2
Late --> Early : setTransactionSchedule
Early --> Late : frames elapsed,<br/>no pending tx
Late --> EarlyGpu : onDisplayRefresh<br/>usedGpu=true
EarlyGpu --> Late : frames elapsed,<br/>no GPU comp
Early --> EarlyGpu : onDisplayRefresh<br/>usedGpu=true
EarlyGpu --> Early : setTransactionSchedule
Late --> Early : onRefreshRateChangeInitiated
Early --> Late : onRefreshRateChangeCompleted
24.3.10 VsyncSchedule: Per-Display VSYNC¶
VsyncSchedule encapsulates the complete VSYNC infrastructure for a
single physical display:
- A
VSyncTracker(usuallyVSyncPredictor) for timing model - A
VSyncDispatch(usuallyVSyncDispatchTimerQueue) for callback scheduling - A
VSyncControllerfor receiving hardware VSYNC timestamps
In multi-display configurations, each physical display has its own
VsyncSchedule. The pacesetter display's schedule drives the main
composition loop, while secondary displays use their own schedules
for VSYNC event distribution.
24.3.11 Frame Timeline¶
Android's FrameTimeline (in the Scheduler directory) tracks the lifecycle
of every frame through the system, recording:
- Expected and actual app render start times
- Expected and actual presentation times (VSYNC)
- GPU completion fences
- Present fences from the display
This data powers the dumpsys SurfaceFlinger --frametimeline debugging
output and feeds into perfetto traces for performance analysis.
24.3.12 Idle Timer and Power Optimization¶
The RefreshRateSelector supports an idle screen configuration that
reduces refresh rate when the display content is static:
// RefreshRateSelector.h
struct Policy {
// ...
std::optional<gui::DisplayModeSpecs::IdleScreenRefreshRateConfig>
idleScreenConfigOpt;
};
The OneShotTimer in the Scheduler fires after a configurable idle
period, signalling the RefreshRateSelector to lower the refresh rate.
Any new content update (buffer queue activity, touch event) resets the
timer. This is a significant power optimization: a phone showing a
static document drops from 120 Hz to 60 Hz (or lower) after a few
seconds of inactivity.
24.3.13 SmallAreaDetection¶
SmallAreaDetectionAllowMappings enables per-UID small-area detection
thresholds. When enabled, SurfaceFlinger can reduce the refresh rate for
layers that update only a small percentage of the screen (e.g., a blinking
cursor), preventing those layers from forcing the entire display to run at
a high refresh rate. The SmallAreaDetectionController in
DisplayManagerService manages the allow-list of UIDs.
24.4 Screen Rotation¶
24.4.1 DisplayRotation: The Policy Engine¶
DisplayRotation (2255 lines) owns the mapping between the requested
orientation (from the topmost Activity) and the actual physical rotation
of the display. It resides in WindowManagerService and is instantiated
per-DisplayContent:
// frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java
public class DisplayRotation {
private final WindowManagerService mService;
private final DisplayContent mDisplayContent;
private final DisplayPolicy mDisplayPolicy;
private final FoldController mFoldController;
private final DeviceStateController mDeviceStateController;
private final DisplayRotationCoordinator mDisplayRotationCoordinator;
// ...
}
The rotation decision pipeline:
flowchart TD
A["Activity requests orientation<br/>(screenOrientation attribute)"] --> B["DisplayRotation.updateOrientationFromApp()"]
B --> C{"Orientation locked<br/>by user setting?"}
C -->|Yes| D["Use user rotation"]
C -->|No| E["rotationForOrientation()"]
E --> F{"Fixed to user<br/>rotation mode?"}
F -->|Yes| G["User rotation"]
F -->|No| H["Sensor-based rotation"]
H --> I{"Lid open?<br/>Dock mode?"}
I -->|Lid/dock| J["Use lid/dock rotation"]
I -->|Normal| K["Sensor orientation"]
K --> L["OrientationListener<br/>(SensorManager)"]
L --> M["proposedRotation"]
M --> N["Apply rotation"]
24.4.2 Rotation Lifecycle¶
When a rotation occurs, the system must coordinate multiple subsystems:
sequenceDiagram
participant Sensor as OrientationListener
participant DR as DisplayRotation
participant DC as DisplayContent
participant WMS as WindowManagerService
participant SF as SurfaceFlinger
participant Shell as Shell Transitions
Sensor->>DR: onProposedRotationChanged(rotation)
DR->>DC: updateOrientation()
DC->>DC: Calculate new rotation
DC->>WMS: sendNewConfiguration()
WMS->>Shell: Request CHANGE transition
Shell->>SF: Set display projection (new rotation)
SF->>SF: Apply rotation transform
Shell->>Shell: Play rotation animation
Note over Shell: Wait for windows to redraw
Shell->>WMS: Transition complete
24.4.3 SeamlessRotator: Zero-Flicker Rotation¶
SeamlessRotator enables rotation without a blackout screen by applying
counter-transforms to individual windows. During seamless rotation, each
window's SurfaceControl is transformed to undo the display rotation,
so from the user's perspective, the content appears stationary while the
display orientation changes underneath.
The constructor computes the transform matrix:
// frameworks/base/services/core/java/com/android/server/wm/SeamlessRotator.java
public SeamlessRotator(@Rotation int oldRotation, @Rotation int newRotation,
DisplayInfo info, boolean applyFixedTransformationHint) {
// Convert from old logical coords -> physical coords -> new logical coords
CoordinateTransforms.transformLogicalToPhysicalCoordinates(
oldRotation, pW, pH, mTransform);
CoordinateTransforms.transformPhysicalToLogicalCoordinates(
newRotation, pW, pH, tmp);
mTransform.postConcat(tmp);
}
The unrotate() method applies this transform to each window's
SurfaceControl:
public void unrotate(Transaction transaction, WindowContainer win) {
applyTransform(transaction, win.getSurfaceControl());
float[] winSurfacePos = {win.mLastSurfacePosition.x, win.mLastSurfacePosition.y};
mTransform.mapPoints(winSurfacePos);
transaction.setPosition(win.getSurfaceControl(), winSurfacePos[0], winSurfacePos[1]);
}
Additionally, mApplyFixedTransformHint sets a buffer transform hint on
the SurfaceControl so that graphic producers (e.g., the app's
Surface) do not allocate buffers in the new orientation prematurely --
the hint pins the expected buffer orientation to the old rotation until
the producer catches up.
24.4.4 AsyncRotationController: Non-Activity Windows¶
While activities can redraw in the new orientation, non-activity windows
(status bar, navigation bar, screen decor overlays) may take additional
frames to update. AsyncRotationController manages their appearance during
the transition:
// frameworks/base/services/core/java/com/android/server/wm/AsyncRotationController.java
class AsyncRotationController extends FadeAnimationController
implements Consumer<WindowState> {
private final ArrayMap<WindowToken, Operation> mTargetWindowTokens;
// ...
}
The controller supports four transition operations:
| Op | Constant | Behavior |
|---|---|---|
OP_LEGACY |
0 | Legacy non-transition path |
OP_APP_SWITCH |
1 | App open/close with rotation (fade out, then fade in) |
OP_CHANGE |
2 | Normal rotation (hide via parent leash, fade in when redrawn) |
OP_CHANGE_MAY_SEAMLESS |
3 | Potentially seamless (shell decides) |
For seamless rotation of system windows (e.g., screen decor overlays that
must be seamless), the controller requests individual sync transactions and
applies the SeamlessRotator counter-transform to each window token.
24.4.5 Foldable Rotation Coordination¶
The FoldController (inner class of DisplayRotation) handles rotation
during fold/unfold events. It introduces a FOLDING_RECOMPUTE_CONFIG_DELAY_MS
(800ms) delay when folding to closed state, preventing configuration
changes and visual jumps during the mechanical folding motion.
DisplayRotationCoordinator synchronises rotation across multiple displays
(e.g., inner and outer displays of a foldable). When the default display
changes rotation, it notifies other displays through a callback mechanism
so they can coordinate their own rotation responses.
24.4.6 Rotation History and Debugging¶
DisplayRotation maintains a RotationHistory ring buffer that records
every rotation change with timestamp, source (sensor, user, policy), old
rotation, and new rotation. This is invaluable for debugging rotation
issues:
Similarly, RotationLockHistory tracks when rotation lock was toggled
and by which mechanism (user setting, device state, camera compat).
24.4.7 DisplayRotationReversionController¶
The DisplayRotationReversionController handles cases where the display
rotation should be temporarily overridden:
| Reversion Type | Constant | Trigger |
|---|---|---|
| Camera compat | REVERSION_TYPE_CAMERA_COMPAT |
Camera app needs specific orientation |
| Half fold | REVERSION_TYPE_HALF_FOLD |
Device in tabletop posture |
| No sensor | REVERSION_TYPE_NOSENSOR |
Sensor disabled/unavailable |
When a reversion is active, DisplayRotation uses the reverted rotation
instead of the sensor-detected rotation. Reversions are stacked and
unwound in order.
24.4.8 Rotation and Transitions Integration¶
Screen rotation is deeply integrated with Shell Transitions (Chapter 16). When rotation changes, the transition system:
- Captures a screenshot of the pre-rotation state (or uses
SeamlessRotatorfor seamless transitions). - Starts a CHANGE transition that includes the DisplayContent.
- Coordinates with
AsyncRotationControllerto handle non-activity windows. - Plays the rotation animation (usually a crossfade from screenshot to live content).
- Waits for all windows to redraw in the new orientation before completing the transition.
The legacy rotation path (pre-Shell Transitions) used a
ScreenRotationAnimation that rendered a GPU-accelerated rotation
of the pre-rotation screenshot. The new path delegates this entirely
to Shell, which can apply more sophisticated animations.
24.5 Foldable Display Support¶
24.5.1 DeviceStateManagerService: The State Machine¶
DeviceStateManagerService manages the physical configuration of
variable-state devices like foldables. It is the central authority for
answering "what posture is the device in right now?"
// frameworks/base/services/core/java/com/android/server/devicestate/
// DeviceStateManagerService.java
public final class DeviceStateManagerService extends SystemService {
private final DeviceStatePolicy mDeviceStatePolicy;
private final BinderService mBinderService;
// ...
}
The service defines device states using properties:
| Property | Description |
|---|---|
PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED |
Device is fully folded |
PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN |
Tabletop/tent posture |
PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN |
Fully unfolded |
PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY |
Inner display is primary |
PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY |
Outer display is primary |
PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT |
Dual display mode |
PROPERTY_FEATURE_REAR_DISPLAY |
Rear display mode |
PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP |
This state triggers sleep |
PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE |
This state triggers wake |
24.5.2 Device State Providers¶
The DeviceStateProvider interface supplies the physical device state.
FoldableDeviceStateProvider is the standard implementation that reads
from the hinge angle sensor and hall effect sensor to determine the fold
posture. The provider reports state changes to DeviceStateManagerService,
which then consults the DeviceStatePolicy to determine the appropriate
system response.
graph TD
subgraph "Hardware"
HA["Hinge Angle Sensor"]
HE["Hall Effect Sensor"]
end
subgraph "DeviceStateManagerService"
FDSP["FoldableDeviceStateProvider"]
DSMS2["DeviceStateManagerService"]
DSP["DeviceStatePolicy<br/>(BookStyleDeviceStatePolicy)"]
end
subgraph "Consumers"
DMS2["DisplayManagerService"]
WMS2["WindowManagerService"]
PM["PowerManager"]
end
HA --> FDSP
HE --> FDSP
FDSP -->|"new base state"| DSMS2
DSMS2 --> DSP
DSP -->|"committed state"| DMS2
DSP -->|"committed state"| WMS2
DSP -->|"wake/sleep trigger"| PM
24.5.3 LogicalDisplayMapper: Display Swapping¶
LogicalDisplayMapper is the key component that makes foldable display
transitions work. When the device transitions between states (e.g., from
CLOSED to OPEN), the mapper must:
-
Identify which physical displays are enabled in the new state using
DeviceStateToLayoutMap(a mapping from device state identifiers toLayoutobjects describing which displays are active and their positions). -
Swap the underlying
DisplayDevicefor the defaultLogicalDisplay. The logical display ID (0) stays the same, but its backing physical display changes from outer to inner (or vice versa). -
Manage the transition with
mIsInTransitionflags and a timeout (TIMEOUT_STATE_TRANSITION_MILLIS = 500ms) to handle cases where the transition takes too long.
sequenceDiagram
participant DSM as DeviceStateManagerService
participant DMS3 as DisplayManagerService
participant LDM2 as LogicalDisplayMapper
participant LD as LogicalDisplay (ID=0)
DSM->>DMS3: onStateChanged(STATE_OPEN)
DMS3->>LDM2: setDeviceStateLocked(STATE_OPEN)
LDM2->>LDM2: Look up Layout for STATE_OPEN
LDM2->>LDM2: Mark displays in transition
Note over LDM2: Layout says: inner display enabled,<br/>outer display disabled
LDM2->>LD: setPrimaryDisplayDeviceLocked(innerDevice)
LDM2->>LDM2: Send LOGICAL_DISPLAY_EVENT_SWAPPED
LDM2->>LDM2: Disable outer LogicalDisplay
LDM2->>DMS3: Listener.onLogicalDisplayEvent(SWAPPED)
DMS3->>DMS3: Notify WindowManager of display change
The mapper emits specific events for different scenarios:
public static final int LOGICAL_DISPLAY_EVENT_SWAPPED = 1 << 3;
public static final int LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION = 1 << 5;
24.5.4 BookStyleDeviceStatePolicy¶
For book-style foldables (where the fold axis is vertical, like a book),
BookStyleDeviceStatePolicy implements DeviceStatePolicy to manage:
-
Outer-to-inner transitions: When unfolding, the outer display content is migrated to the inner display. The policy coordinates with
DisplayManagerServiceandWindowManagerServiceto ensure apps see a smooth transition. -
Rear display mode: Allows using the outer display as a viewfinder while the device is open, with the inner display facing away from the user. This mode is enabled by
PROPERTY_FEATURE_REAR_DISPLAY. -
Dual display mode: Both inner and outer displays active simultaneously, enabled by
PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT.
24.5.5 Concurrent Displays¶
Modern foldables can run both displays simultaneously. The
DisplayTopologyCoordinator manages the spatial relationship between
displays, and DisplayTopologyStore persists the topology configuration.
When concurrent displays are active, the system:
- Assigns separate
DisplayGroupinstances if the displays serve different purposes - Applies stricter thermal brightness throttling (the thermal data ID changes in concurrent mode)
- Routes input events to the correct display based on touch coordinates
24.5.6 DeviceStateToLayoutMap¶
DeviceStateToLayoutMap provides the mapping from device state identifiers
to Layout objects that describe which displays are active and their
positions. The default state STATE_DEFAULT maps to the initial layout
with a single default display. Each layout specifies:
- Which
DisplayDevices are enabled - The position of each display (front, rear, unknown)
- The
DisplayGroupname for each display - Lead/follower relationships between displays (for brightness)
graph TD
subgraph "Device State: CLOSED (0)"
LC["Layout: outer display enabled<br/>Position: FRONT"]
end
subgraph "Device State: HALF_OPEN (1)"
LH["Layout: inner display enabled<br/>Position: FRONT"]
end
subgraph "Device State: OPEN (2)"
LO["Layout: inner display enabled<br/>Position: FRONT"]
end
subgraph "Device State: REAR (3)"
LR["Layout: outer display enabled<br/>Position: REAR"]
end
subgraph "Device State: DUAL (4)"
LD["Layout: both displays enabled<br/>Inner: FRONT, Outer: REAR"]
end
24.5.7 Display Swapping Events¶
During a display swap, the LogicalDisplayMapper emits a carefully
ordered sequence of events:
sequenceDiagram
participant LDM3 as LogicalDisplayMapper
participant DMS6 as DisplayManagerService
participant WMS3 as WindowManagerService
participant APPS as Applications
LDM3->>DMS6: LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION
Note over DMS6: Transition begins, display may flicker
LDM3->>DMS6: LOGICAL_DISPLAY_EVENT_SWAPPED
DMS6->>WMS3: Display configuration changed
WMS3->>WMS3: Recalculate DisplayContent bounds
WMS3->>APPS: Configuration change callbacks
LDM3->>DMS6: LOGICAL_DISPLAY_EVENT_BASIC_CHANGED
Note over DMS6: Transition complete, new display active
The TIMEOUT_STATE_TRANSITION_MILLIS (500ms) guard ensures that a stalled
transition does not leave the system in an indeterminate state.
24.5.8 FoldSettingProvider¶
FoldSettingProvider manages user preferences for foldable behavior:
- Whether to mirror the default display when folded
- Whether to include the default display in the display topology
- Resolution mode preferences per display
These settings are read through Settings.Secure and influence the
LogicalDisplayMapper's layout decisions.
24.6 Display Cutout and Rounded Corners¶
24.6.1 DisplayCutout: Modelling Non-Rectangular Screens¶
DisplayCutout represents the area of the display that is not functional
for displaying content -- typically a notch, hole-punch camera, or dynamic
island. It is immutable and carried through the system as part of
DisplayInfo and WindowInsets.
// frameworks/base/core/java/android/view/DisplayCutout.java
public final class DisplayCutout {
private final Rect mSafeInsets;
private final Insets mWaterfallInsets;
private final Rect[] mBounds; // One rect per side (left, top, right, bottom)
// ...
}
The cutout defines:
- Safe insets -- the rectangular region that is guaranteed to be free of cutouts, expressed as insets from each edge
- Bounding rectangles -- the precise bounds of the cutout on each of the four sides
- Waterfall insets -- for curved-edge (waterfall) displays, the insets where the display curves away from the flat plane
24.6.2 CutoutSpecification: Configuration DSL¶
The cutout shape is defined in the device overlay resource
R.string.config_mainBuiltInDisplayCutout using a custom specification
language parsed by CutoutSpecification:
The specification supports SVG-like path commands (M, L, C, Q, Z, etc.) that define the cutout shape relative to the display dimensions. The parser handles:
@dpsuffix: Values in density-independent pixels@bottom,@right,@center_vertical: Positioning shortcuts@leftkeyword: Binds the path to the left side of the display
A typical specification for a centered punch-hole camera:
M 0,0
L -24,0
C -24,0 -24,24 0,24
L 0,48
C 0,48 24,48 24,24
L 24,0
C 24,0 0,0 0,0
@dp
@center_horizontal
24.6.3 Cutout Modes¶
Apps declare their cutout handling preference via
WindowManager.LayoutParams.layoutInDisplayCutoutMode:
| Mode | Constant | Behavior |
|---|---|---|
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT |
0 | Content avoids cutout in portrait, uses full screen in landscape |
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES |
1 | Content extends into cutout on short edges |
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER |
2 | Content never extends into cutout area |
LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS |
3 | Content always extends into cutout area |
The window manager evaluates these modes in DisplayPolicy when computing
window frames. For ALWAYS, the window receives the full display area;
for NEVER, the window is inset by the cutout safe insets.
24.6.4 WmDisplayCutout¶
WmDisplayCutout is the window-manager-internal wrapper that adds rotation
awareness to DisplayCutout. When the display rotates, the cutout bounds
must be rotated accordingly. WmDisplayCutout caches rotated variants to
avoid recomputation:
graph LR
DC2["DisplayCutout<br/>(from device config)"] --> WDC["WmDisplayCutout<br/>(rotation-aware)"]
WDC --> R0["Rotation 0<br/>(original)"]
WDC --> R90["Rotation 90<br/>(rotated bounds)"]
WDC --> R180["Rotation 180"]
WDC --> R270["Rotation 270"]
24.6.5 RoundedCorners and DisplayShape¶
Modern displays have rounded corners that must be accounted for in layout:
-
RoundedCornersdescribes the radius of each corner (top-left, top-right, bottom-left, bottom-right). Apps access this throughWindowInsets.getRoundedCorner(). -
DisplayShapeprovides the full outline path of the display, accounting for both cutouts and rounded corners. This is used by SystemUI to draw decorations that precisely follow the display edge. -
PrivacyIndicatorBoundsdefines the region reserved for privacy indicators (camera, microphone) that may overlap with the cutout area.
The framework provides DecorCaptionView corner radius information through
WindowDecoration so that window decorations (caption bars in freeform
mode) can match the display corner radius.
24.6.6 Cutout Rotation¶
When the display rotates, the cutout must rotate with it. The
DisplayCutout class provides rotation through CutoutPathParserInfo:
// frameworks/base/core/java/android/view/DisplayCutout.java
private static final class CutoutPathParserInfo {
final int displayWidth;
final int physicalDisplayHeight;
final int displayHeight;
final float density;
final String cutoutSpec;
final int rotation;
final float scale;
final float physicalPixelDisplaySizeRatio;
}
The path is re-parsed at each rotation, with the parser applying the
rotation transform to the SVG path coordinates. The result is cached
per (spec, width, height, density, rotation) tuple to avoid repeated
parsing:
// Static cache fields in DisplayCutout
@GuardedBy("CACHE_LOCK")
private static String sCachedSpec;
@GuardedBy("CACHE_LOCK")
private static int sCachedDisplayWidth;
@GuardedBy("CACHE_LOCK")
private static int sCachedDisplayHeight;
@GuardedBy("CACHE_LOCK")
private static float sCachedDensity;
@GuardedBy("CACHE_LOCK")
private static Pair<Path, DisplayCutout> sCachedCutout = NULL_PAIR;
24.6.7 Side Overrides¶
For devices with cutouts on multiple sides (e.g., a camera notch on top
and a sensor housing on the bottom), DisplayCutout supports side
overrides that remap cutout bounds to different sides:
Side overrides allow OEMs to correct cutout positioning when the physical cutout location does not match the default mapping assumed by the spec parser.
24.6.8 Emulation Overlays¶
For development and testing, the cutout can be emulated on devices without
physical cutouts using Runtime Resource Overlays (RROs) in the category
com.android.internal.display_cutout_emulation. AOSP includes several
emulation overlays (tall cutout, wide cutout, corner cutout, double cutout)
that can be enabled through:
24.7 SurfaceFlinger Composition¶
24.7.1 Architecture Overview¶
SurfaceFlinger is Android's native display compositor. It runs as a
standalone service (/system/bin/surfaceflinger) and is responsible for
collecting graphical layers from all applications and compositing them
into the final display output.
The composition architecture has undergone a significant "front-end refactor" that separated layer state management from the composition pipeline:
graph TD
subgraph "Front-End (New)"
TH["TransactionHandler<br/>(receives SurfaceControl.Transaction)"]
LLM["LayerLifecycleManager<br/>(layer creation/destruction)"]
LH["LayerHierarchy<br/>(parent-child tree)"]
LSB["LayerSnapshotBuilder<br/>(immutable snapshots)"]
end
subgraph "Composition Engine"
CE3["CompositionEngine::present()"]
OUT["Output<br/>(per-display)"]
OL["OutputLayer<br/>(per-layer-per-display)"]
RS["RenderSurface<br/>(framebuffer)"]
end
subgraph "Hardware"
HWC2["HWComposer"]
RE["RenderEngine<br/>(GPU fallback)"]
end
TH --> LLM
LLM --> LH
LH --> LSB
LSB -->|"LayerSnapshots"| CE3
CE3 --> OUT
OUT --> OL
OUT --> RS
OL --> HWC2
OL --> RE
24.7.2 LayerLifecycleManager¶
LayerLifecycleManager owns the collection of RequestedLayerState
objects and manages their lifecycle:
// frameworks/native/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
class LayerLifecycleManager {
public:
void addLayers(std::vector<std::unique_ptr<RequestedLayerState>>);
void applyTransactions(const std::vector<QueuedTransactionState>&,
bool ignoreUnknownLayers = false);
void onHandlesDestroyed(
const std::vector<std::pair<uint32_t, std::string>>&,
bool ignoreUnknownHandles = false);
void commitChanges();
// ...
};
The lifecycle model is straightforward:
- addLayers -- New layers are added with their initial
RequestedLayerState. - applyTransactions -- Queued transactions update layer properties (position, size, buffer, alpha, etc.).
- onHandlesDestroyed -- When a client releases its layer handle,
the manager marks the layer for destruction. Layers without a parent
and without a handle are destroyed on
commitChanges(). - commitChanges -- Invokes
ILifecycleListenercallbacks (onLayerAdded,onLayerDestroyed) and clears change flags.
The manager tracks changes through ftl::Flags<RequestedLayerState::Changes>
that accumulate between commits, enabling the snapshot builder to perform
incremental updates.
24.7.3 LayerSnapshotBuilder¶
LayerSnapshotBuilder walks the LayerHierarchy tree and produces an
ordered list of LayerSnapshot objects suitable for CompositionEngine:
// frameworks/native/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
class LayerSnapshotBuilder {
public:
void update(const Args&);
std::vector<std::unique_ptr<LayerSnapshot>>& getSnapshots();
void forEachVisibleSnapshot(const ConstVisitor& visitor) const;
// ...
};
The builder implements two update paths:
- Fast path (
tryFastUpdate): When only buffer updates have occurred (no hierarchy or geometry changes), the builder can update snapshots in-place without re-walking the tree. - Full update (
updateSnapshots): Re-walks the hierarchy, updating inherited properties (visibility, alpha, color transform, crop) from parent to child.
Snapshots are immutable once built, providing a consistent view of layer state for the composition pipeline without holding locks.
24.7.4 CompositionEngine¶
CompositionEngine orchestrates the actual composition:
// frameworks/native/services/surfaceflinger/CompositionEngine/include/
// compositionengine/CompositionEngine.h
class CompositionEngine {
public:
virtual std::shared_ptr<Display> createDisplay(const DisplayCreationArgs&) = 0;
virtual void present(CompositionRefreshArgs&) = 0;
virtual void preComposition(CompositionRefreshArgs&) = 0;
virtual void postComposition(CompositionRefreshArgs&) = 0;
virtual HWComposer& getHwComposer() const = 0;
virtual renderengine::RenderEngine& getRenderEngine() const = 0;
};
The composition cycle:
sequenceDiagram
participant S as Scheduler
participant SF2 as SurfaceFlinger
participant CE4 as CompositionEngine
participant OUT2 as Output (per display)
participant HWC3 as HWComposer
participant RE2 as RenderEngine
S->>SF2: VSYNC callback
SF2->>SF2: Collect transactions
SF2->>SF2: Build LayerSnapshots
SF2->>CE4: present(refreshArgs)
CE4->>CE4: preComposition (release fences)
CE4->>OUT2: prepare() - assign layers to HWC
OUT2->>HWC3: validate()
HWC3-->>OUT2: composition strategy
alt Client composition needed
OUT2->>RE2: drawLayers()
RE2-->>OUT2: client target buffer
end
OUT2->>HWC3: presentDisplay()
HWC3-->>OUT2: present fence
CE4->>CE4: postComposition (resolve fences)
24.7.5 HWComposer Validation¶
The Hardware Composer (HWC) HAL determines which layers can be handled by dedicated overlay hardware and which must be composited by the GPU. The validation cycle:
- prepare/validate: SurfaceFlinger sends all layers to HWC.
- HWC returns per-layer composition type:
DEVICE-- HWC handles this layer directly (DMA overlay plane)CLIENT-- SurfaceFlinger must render this layer via GPUSIDEBAND-- Sideband stream (e.g., hardware video decoder)CURSOR-- Cursor overlay plane- acceptChanges: SurfaceFlinger accepts HWC's decisions.
- Client composition: Layers marked
CLIENTare drawn byRenderEngineinto the client target buffer. - presentDisplay: HWC composites all planes and presents.
This two-pass strategy minimises GPU usage -- on capable hardware, many or all layers can be handled by overlay planes, saving power and reducing latency.
24.7.6 RequestedLayerState and Change Tracking¶
Each layer's state is captured in a RequestedLayerState that mirrors
the properties set through SurfaceControl.Transaction:
classDiagram
class RequestedLayerState {
+layerId: uint32_t
+name: string
+parentId: uint32_t
+relativeParentId: uint32_t
+z: int32_t
+position: vec2
+bufferSize: Size
+crop: Rect
+alpha: float
+color: half4
+flags: uint32_t
+transform: uint32_t
+cornerRadius: float
+backgroundBlurRadius: int
+changes: Flags~Changes~
}
class Changes {
<<flags>>
Created
Destroyed
Hierarchy
Geometry
Content
AffectsChildren
FrameRate
Visibility
Buffer
}
RequestedLayerState --> Changes
The Changes flags are critical for the snapshot builder's incremental
update path. When only Buffer has changed (no geometry, hierarchy, or
visibility changes), the fast path can update just the buffer reference
in existing snapshots without re-walking the hierarchy tree.
24.7.7 LayerHierarchy: Parent-Child Tree¶
LayerHierarchy builds an ordered tree from RequestedLayerState objects:
graph TD
ROOT["Root"]
ROOT --> D0["Display 0 Root"]
D0 --> APP1["App Layer (z=0)"]
D0 --> APP2["App Layer (z=1)"]
D0 --> SYS["System Overlay (z=100)"]
APP1 --> CHILD1["Child Surface (z=0)"]
APP1 --> CHILD2["Child Surface (z=1)"]
APP2 --> REL["Relative Layer<br/>(relativeParent=SYS)"]
The hierarchy handles:
- Z-ordering: Children sorted by Z within their parent
- Relative layers: Layers positioned relative to a non-parent layer (used for PopupWindows, tooltips)
- Mirror layers: Layers that reference another layer's subtree for display mirroring
- Cycle detection: The hierarchy builder detects and breaks relative-Z
loops via
fixRelativeZLoop()
24.7.8 LayerSnapshot Properties¶
Each LayerSnapshot computed by the builder contains:
- Resolved geometry (combined transform, crop, and position from all ancestors)
- Resolved visibility (taking parent alpha, flags, and crop into account)
- Resolved color transform (combined from all ancestors)
- Buffer reference and acquisition fence
- Shadow settings
- Per-layer color space
- Rounded corner radius (combining layer's own radius with parent's)
- Composition type hint
The snapshots are sorted in Z-order and filtered for visibility before
being passed to CompositionEngine.
24.7.9 Output and OutputLayer¶
For each display, CompositionEngine creates an Output that manages:
- The display's
RenderSurface(framebuffer or virtual display surface) - The
DisplayColorProfile(native gamut, HDR capabilities) - A list of
OutputLayerobjects (one per visible layer on this display)
OutputLayer tracks per-display composition state:
- The HWC composition type (
DEVICE,CLIENT,CURSOR) - The display-local geometry (after display projection)
- The buffer handle and fence for this output
- Whether the layer needs sRGB-to-display-gamut conversion
24.7.10 Performance: GPU vs HWC Composition¶
The choice between GPU and HWC composition has significant power and latency implications:
| Aspect | HWC (Overlay) | GPU (Client) |
|---|---|---|
| Power | Low (DMA from buffer) | High (shader execution) |
| Latency | 1 VSYNC | 1-2 VSYNC (GPU + present) |
| Capacity | Limited planes (4-8) | Unlimited |
| Transforms | Limited (scale, rotate, crop) | Arbitrary |
| Blending | Limited modes | Full shader support |
| Per-pixel alpha | Sometimes | Always |
The HWC validation determines the optimal split. Modern SoCs expose 4-8 overlay planes, each capable of scan-out from a different buffer. Layers that exceed the hardware capacity, or that require unsupported transforms (e.g., complex blur), fall back to GPU composition.
24.8 BufferQueue and BLASTBufferQueue¶
24.8.1 BufferQueue: The Producer-Consumer Model¶
BufferQueue is the fundamental data structure for passing graphical
buffers between producers (apps, camera, video decoder) and consumers
(SurfaceFlinger, ImageReader, video encoder). It implements a slot-based
buffer pool:
stateDiagram-v2
[*] --> FREE : Allocated
FREE --> DEQUEUED : dequeueBuffer
DEQUEUED --> QUEUED : queueBuffer
DEQUEUED --> FREE : cancelBuffer
QUEUED --> ACQUIRED : acquireBuffer
ACQUIRED --> FREE : releaseBuffer
Each BufferQueue has a fixed number of slots (typically 3 for triple buffering). The states:
| State | Owner | Description |
|---|---|---|
FREE |
BufferQueue | Available for producer to dequeue |
DEQUEUED |
Producer | Producer is rendering into this buffer |
QUEUED |
BufferQueue | Waiting for consumer to acquire |
ACQUIRED |
Consumer | Consumer is reading/compositing this buffer |
24.8.2 Triple Buffering¶
Android uses triple buffering by default: while the display is scanning out buffer A and SurfaceFlinger is compositing buffer B, the application can render into buffer C. This pipeline maximises throughput at the cost of one additional frame of latency:
gantt
title Triple Buffering Pipeline
dateFormat X
axisFormat %s
section Display
Buffer A scanout :a1, 0, 16
Buffer B scanout :b1, 16, 32
Buffer C scanout :c1, 32, 48
section SurfaceFlinger
Compose B :sf1, 0, 16
Compose C :sf2, 16, 32
Compose A :sf3, 32, 48
section App
Render C :app1, 0, 16
Render A :app2, 16, 32
Render B :app3, 32, 48
The buffer count can be adjusted. Double buffering reduces latency but risks frame drops when rendering takes longer than one VSYNC period.
24.8.3 BLASTBufferQueue: Transaction-Based Delivery¶
BLASTBufferQueue (Buffer Lifecycle And Sync Transfer) replaced the legacy
BufferLayer approach of having SurfaceFlinger directly acquire buffers.
Instead, the client acquires buffers from the BufferItemConsumer and
delivers them to SurfaceFlinger through SurfaceControl.Transaction:
sequenceDiagram
participant App as Application
participant BBQ2 as BLASTBufferQueue
participant BIC as BLASTBufferItemConsumer
participant SC as SurfaceComposerClient
participant SF3 as SurfaceFlinger
App->>BBQ2: Surface.lockCanvas() / EGL swap
BBQ2->>BBQ2: dequeueBuffer() from IGraphicBufferProducer
App->>App: Render content
App->>BBQ2: queueBuffer()
BBQ2->>BIC: onFrameAvailable()
BIC->>BBQ2: acquireBuffer()
BBQ2->>SC: Transaction.setBuffer(surfaceControl, buffer)
BBQ2->>SC: Transaction.setBufferCrop(...)
BBQ2->>SC: Transaction.apply()
SC->>SF3: Binder call with transaction
SF3->>SF3: Apply in next composition cycle
SF3-->>BBQ2: transactionCallback (latch time, present fence)
BBQ2->>BIC: releaseBuffer (with release fence)
Key advantages of BLAST:
- Atomic updates: Buffer submission is bundled with geometry changes (position, crop, matrix) in a single transaction, eliminating tearing between buffer content and window position.
- Client-side control: The client decides when to submit buffers,
enabling synchronisation with other operations (e.g.,
SyncGroup). - Fence management: Release fences flow back through transaction
callbacks, and the
ReleaseBufferCallbackensures proper fence propagation.
24.8.4 BLASTBufferQueue Internals¶
The BLASTBufferQueue class manages several maps:
// frameworks/native/libs/gui/include/gui/BLASTBufferQueue.h
class BLASTBufferQueue : public ConsumerBase::FrameAvailableListener {
sp<IGraphicBufferProducer> mProducer;
sp<BLASTBufferItemConsumer> mConsumer;
// Submitted buffers awaiting release
// Size hint: kSubmittedBuffersMapSizeHint = 8
ftl::SmallMap<...> mSubmitted;
// Dequeue timestamps for frame timing
// Size hint: kDequeueTimestampsMapSizeHint = 32
ftl::SmallMap<...> mDequeueTimestamps;
};
The syncNextTransaction() method allows callers to intercept the next
transaction before it is applied, enabling operations like
ViewRootImpl's synchronised buffer submission during relayout.
mergeWithNextTransaction() allows merging additional transaction
operations (e.g., position changes) with the next buffer submission.
24.8.5 Frame Timestamps¶
BLASTBufferItemConsumer extends BufferItemConsumer with frame event
history tracking. The updateFrameTimestamps() method records:
- Refresh start time
- GPU composition done fence
- Present fence
- Previous release fence
- Compositor timing
- Latch time
- Dequeue ready time
These timestamps flow back to the application through
FrameEventHistoryDelta for EGL_ANDROID_get_frame_timestamps and
Choreographer.FrameInfo.
24.8.6 Fence Synchronization¶
The buffer pipeline uses fence objects (backed by Linux sync files) to synchronize access between CPU, GPU, and display hardware:
sequenceDiagram
participant App4 as Application (CPU)
participant GPU as GPU
participant BBQ3 as BLASTBufferQueue
participant SF8 as SurfaceFlinger
participant HWC4 as HWComposer
App4->>GPU: Submit draw commands
GPU-->>BBQ3: Acquire fence (GPU will signal when done)
BBQ3->>SF8: Transaction with buffer + acquire fence
SF8->>SF8: Wait for acquire fence before compositing
SF8->>HWC4: presentDisplay()
HWC4-->>SF8: Present fence (display will signal when scanout starts)
SF8->>BBQ3: Release callback with release fence
BBQ3->>App4: Buffer released, safe to draw again
Note over App4: Wait for release fence before<br/>dequeuing same buffer
Three types of fences:
- Acquire fence: Signalled when the GPU finishes rendering. SurfaceFlinger must wait for this before reading the buffer.
- Release fence: Signalled when SurfaceFlinger/HWC is done with the buffer. The producer must wait for this before reusing the buffer.
- Present fence: Signalled when the composed frame starts scanning out on the display. Used for frame timing measurements.
24.8.7 Gralloc Buffer Allocation¶
Buffer memory is allocated through the Gralloc HAL (Graphics Allocator),
which returns GraphicBuffer objects backed by hardware-specific memory
(contiguous DRAM for HWC scanout, tiled memory for GPU, etc.).
The IGraphicBufferProducer and IGraphicBufferConsumer interfaces use
Binder to share GraphicBuffer handles between producer and consumer
processes. The actual buffer memory is shared via file descriptors (dmabuf),
so both processes map the same physical memory.
24.8.8 The BLAST Migration Story¶
Before BLAST, SurfaceFlinger directly acquired buffers from the BufferQueue on its own timeline. This created synchronization problems:
-
Buffer-geometry desync: An app could queue a buffer at size 800x600 while simultaneously requesting a window resize to 1024x768. The buffer and the window geometry would be applied in different SurfaceFlinger frames, causing visible tearing.
-
No atomic updates: Multiple related changes (buffer + position + crop + alpha) could not be applied atomically.
-
Consumer-side latency: SurfaceFlinger had to poll each BufferQueue for new buffers, adding latency.
BLAST solved all three by moving buffer acquisition to the client side
and bundling buffer submission with geometry changes in a single
SurfaceControl.Transaction. The migration was gradual, controlled by
the BLASTBufferQueue flag, and is now the only supported path.
24.8.9 SyncGroup and Cross-Surface Synchronization¶
BLASTBufferQueue.syncNextTransaction() supports cross-surface
synchronization. When ViewRootImpl needs to synchronize a buffer
submission with a WindowContainerTransaction (e.g., during
relayout), it registers a sync callback:
// In ViewRootImpl
mBlastBufferQueue.syncNextTransaction(transaction -> {
// Merge buffer transaction with the relayout transaction
mergedTransaction.merge(transaction);
mergedTransaction.apply();
});
This ensures that the new buffer and the new window bounds appear in the same SurfaceFlinger frame, eliminating flicker during resizes.
24.9 Virtual Display and Mirroring¶
24.9.1 Creating Virtual Displays¶
Virtual displays enable rendering into an off-screen surface for screen
recording, presentation, Miracast, and the Virtual Device Framework.
They are created through DisplayManager.createVirtualDisplay(), which
calls into DisplayManagerService:
sequenceDiagram
participant App2 as Application
participant DM2 as DisplayManager
participant DMS4 as DisplayManagerService
participant VDA as VirtualDisplayAdapter
participant SF4 as SurfaceFlinger
App2->>DM2: createVirtualDisplay(config)
DM2->>DMS4: createVirtualDisplay(config, callback)
DMS4->>DMS4: Permission checks
DMS4->>VDA: createVirtualDisplayLocked(callback, config, ...)
VDA->>SF4: SurfaceControl.createDisplay(name, secure)
SF4-->>VDA: Display token
VDA->>VDA: Create VirtualDisplayDevice
VDA->>DMS4: sendDisplayDeviceEventLocked(ADDED)
DMS4-->>App2: displayId
24.9.2 Virtual Display Flags¶
VirtualDisplayConfig supports a rich set of flags that control behavior:
| Flag | Description |
|---|---|
VIRTUAL_DISPLAY_FLAG_PUBLIC |
Display visible to all apps |
VIRTUAL_DISPLAY_FLAG_PRESENTATION |
Suitable for Presentation API |
VIRTUAL_DISPLAY_FLAG_SECURE |
Content protected; requires CAPTURE_SECURE_VIDEO_OUTPUT |
VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY |
Never mirrors; only shows own content |
VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR |
Mirrors default display when no content |
VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP |
Own DisplayGroup for power management |
VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP |
Joins the device's primary DisplayGroup |
VIRTUAL_DISPLAY_FLAG_OWN_FOCUS |
Manages its own focus chain |
VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS |
StatusBar, NavBar on this display |
VIRTUAL_DISPLAY_FLAG_TRUSTED |
System-trusted display (requires INTERNAL_SYSTEM_WINDOW) |
VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED |
Bypass keyguard on this display |
VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED |
Do not steal focus from other displays |
24.9.3 VirtualDisplaySurface and Three-BQ Routing¶
For virtual displays that mirror or compose content, the VirtualDisplaySurface
manages a three-BufferQueue routing system within SurfaceFlinger:
graph LR
subgraph "Producer Side"
SF5["SurfaceFlinger<br/>(GPU composition output)"]
end
subgraph "VirtualDisplaySurface"
SBQ["Source BQ<br/>(from GPU composition)"]
SINK["Sink BQ<br/>(to consumer)"]
VDS["Routing Logic"]
end
subgraph "Consumer Side"
ENC["MediaCodec / Consumer"]
end
SF5 -->|"client composition target"| SBQ
SBQ --> VDS
VDS -->|"routed buffer"| SINK
SINK --> ENC
The routing logic handles three cases:
- GPU composition only: The GPU-composed output goes directly from the source BQ to the sink BQ.
- HWC composition only: HWC writes directly to the sink BQ.
- Mixed: GPU composes client layers into the source BQ, then HWC composites everything (including GPU output) into the sink BQ.
SinkSurfaceHelper manages the sink-side BufferQueue, handling buffer
allocation, format negotiation, and fence synchronization with the
consumer.
24.9.4 Display Mirroring with Mirror Layers¶
Display mirroring in SurfaceFlinger is implemented through mirror layers. When a virtual display mirrors another display, SurfaceFlinger creates a mirror layer that references the source display's layer stack:
graph TD
subgraph "Source Display (ID=0)"
LS0["LayerStack 0"]
L1["App Layer"]
L2["StatusBar Layer"]
L3["NavBar Layer"]
end
subgraph "SurfaceFlinger"
ML["Mirror Layer<br/>(references LayerStack 0)"]
end
subgraph "Virtual Display (ID=2)"
LS2["LayerStack 2"]
ML2["Mirror of LayerStack 0"]
end
LS0 --> L1
LS0 --> L2
LS0 --> L3
L1 -.->|"mirrored"| ML
L2 -.->|"mirrored"| ML
L3 -.->|"mirrored"| ML
ML --> ML2
ML2 --> LS2
The LayerLifecycleManager.updateDisplayMirrorLayers() method manages
mirror layer references when layer hierarchy changes occur.
24.9.5 MediaProjection Integration¶
MediaProjection is the framework API for screen capture and recording.
It creates a virtual display with the AUTO_MIRROR flag and routes the
output to a MediaCodec encoder or ImageReader:
sequenceDiagram
participant App3 as Screen Recorder
participant MP as MediaProjectionManager
participant DMS5 as DisplayManagerService
participant CR as ContentRecorder
participant VD as Virtual Display
App3->>MP: getMediaProjection(resultCode, data)
MP-->>App3: MediaProjection token
App3->>DMS5: createVirtualDisplay(surface, AUTO_MIRROR)
DMS5->>VD: Create virtual display
DMS5->>CR: setContentRecordingSession(session)
CR->>CR: Start mirroring source display
Note over VD: Mirrors default display content<br/>to the virtual display surface
App3->>App3: Read from Surface via MediaCodec
ContentRecorder in WindowManagerService manages the ongoing recording
session, handling display changes, rotation, and the FLAG_SECURE
exclusion (secure windows appear black in the recording).
24.9.6 Virtual Device Framework Integration¶
The Virtual Device Framework (VDF) extends virtual displays with full
device semantics. A VirtualDeviceImpl manages:
- One or more virtual displays
- Virtual input devices (keyboard, mouse, touchscreen)
- Virtual audio devices
- Window policy controllers
DisplayWindowPolicyController (stored in DMS's
mDisplayWindowPolicyControllers) enforces per-display window policies:
which apps can run, whether the keyguard is shown, whether activities
can be launched on the virtual display.
// DisplayManagerService.java
final SparseArray<Pair<IVirtualDevice, DisplayWindowPolicyController>>
mDisplayWindowPolicyControllers = new SparseArray<>();
24.9.7 WifiDisplayAdapter and Miracast¶
WifiDisplayAdapter manages Wi-Fi Display (Miracast) connections:
sequenceDiagram
participant User as User
participant DMS7 as DisplayManagerService
participant WDA as WifiDisplayAdapter
participant WDC as WifiDisplayController
participant Sink as Miracast Sink
User->>DMS7: Connect to WFD display
DMS7->>WDA: requestConnectLocked(address)
WDA->>WDC: requestConnect(address)
WDC->>Sink: RTSP negotiation
Sink-->>WDC: Connected
WDC->>WDA: Create WifiDisplayDevice
WDA->>DMS7: sendDisplayDeviceEventLocked(ADDED)
DMS7->>DMS7: Create LogicalDisplay
Note over DMS7: Virtual display mirrors<br/>default display to WFD sink
The WFD connection uses RTSP for session management and RTP for video stream delivery. The video is captured through the standard virtual display surface and encoded in H.264.
24.9.8 OverlayDisplayAdapter for Development¶
OverlayDisplayAdapter creates overlay displays from the system property:
This creates a virtual display that appears as a window on the primary display. It is invaluable for multi-display development without physical hardware. The format supports multiple displays:
24.9.9 External Display Policy¶
ExternalDisplayPolicy manages the behavior when external displays
are connected (via HDMI, USB-C, DisplayPort). It coordinates with
DisplayManagerService to:
- Determine whether to mirror or extend
- Apply user preferences for the display
- Handle the
DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYSsetting - Manage
ExternalDisplayStatsServicefor tracking external display usage telemetry
24.10 Display Color Management¶
24.10.1 ColorDisplayService¶
ColorDisplayService manages all display color transforms through a
priority-ordered pipeline of TintController instances:
// frameworks/base/services/core/java/com/android/server/display/color/
// ColorDisplayService.java
public final class ColorDisplayService extends SystemService {
// Color modes
static final int COLOR_MODE_NATURAL = 0;
static final int COLOR_MODE_BOOSTED = 1;
static final int COLOR_MODE_SATURATED = 2;
static final int COLOR_MODE_AUTOMATIC = 3;
// ...
}
24.10.2 TintController Hierarchy¶
Each color transformation is implemented as a TintController subclass:
classDiagram
class TintController {
<<abstract>>
+getMatrix(): float[]
+setMatrix(int cct)
+isActivated(): boolean
}
class ColorTemperatureTintController {
-mMatrix: float[16]
+Night Display (warm tint)
}
class DisplayWhiteBalanceTintController {
-mCurrentColorTemperature
+Ambient white balance
}
class GlobalSaturationTintController {
-mMatrix: float[16]
+Display saturation level
}
class ReduceBrightColorsTintController {
-mMatrix: float[16]
+Reduce bright colors (a11y)
}
class AppSaturationController {
-mAppsMap: SparseArray
+Per-app saturation (a11y)
}
TintController <|-- ColorTemperatureTintController
TintController <|-- DisplayWhiteBalanceTintController
TintController <|-- GlobalSaturationTintController
TintController <|-- ReduceBrightColorsTintController
TintController <|-- AppSaturationController
24.10.3 DisplayTransformManager: The Priority Matrix¶
DisplayTransformManager maintains a priority-ordered sparse array of
4x4 colour matrices that are multiplied together and sent to SurfaceFlinger
as a single combined transform:
// frameworks/base/services/core/java/com/android/server/display/color/
// DisplayTransformManager.java
public static final int LEVEL_COLOR_MATRIX_NIGHT_DISPLAY = 100;
public static final int LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE = 125;
public static final int LEVEL_COLOR_MATRIX_SATURATION = 150;
public static final int LEVEL_COLOR_MATRIX_GRAYSCALE = 200;
public static final int LEVEL_COLOR_MATRIX_REDUCE_BRIGHT_COLORS = 250;
public static final int LEVEL_COLOR_MATRIX_INVERT_COLOR = 300;
The levels define the composition order. When multiple transforms are active (e.g., Night Display + Grayscale), the matrices are multiplied in level order:
graph LR
ND["Night Display<br/>(level 100)"] --> WB["White Balance<br/>(level 125)"]
WB --> SAT["Saturation<br/>(level 150)"]
SAT --> GRAY["Grayscale<br/>(level 200)"]
GRAY --> RBC["Reduce Bright Colors<br/>(level 250)"]
RBC --> INV["Invert Color<br/>(level 300)"]
INV --> FINAL["Combined Matrix<br/>(sent to SurfaceFlinger)"]
The combined matrix is sent to SurfaceFlinger via Binder transaction codes:
private static final int SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX = 1015;
private static final int SURFACE_FLINGER_TRANSACTION_DALTONIZER = 1014;
private static final int SURFACE_FLINGER_TRANSACTION_SATURATION = 1022;
private static final int SURFACE_FLINGER_TRANSACTION_DISPLAY_COLOR = 1023;
24.10.4 Night Display¶
Night Display (blue light filter) uses ColorTemperatureTintController to
shift the display toward warmer tones. It supports three activation modes:
| Mode | Constant | Behavior |
|---|---|---|
| Disabled | AUTO_MODE_DISABLED |
Manual on/off only |
| Custom schedule | AUTO_MODE_CUSTOM_TIME |
User-defined start/end times |
| Twilight | AUTO_MODE_TWILIGHT |
Automatic based on sunrise/sunset |
The twilight mode integrates with TwilightManager to compute local
sunrise and sunset times based on the device's location.
The colour temperature is converted to a 4x4 matrix using a CCT (Correlated
Colour Temperature) to RGB transform. The CctEvaluator class maps CCT
values to matrix coefficients using a Spline interpolation of calibration
data.
24.10.5 Display White Balance¶
DisplayWhiteBalanceTintController uses ambient light sensor data to
maintain consistent white appearance under different lighting conditions.
The DisplayWhiteBalanceController reads from the colour temperature
sensor (or derived from the ambient light sensor) and computes a correction
matrix that shifts the display white point to compensate for ambient
lighting.
24.10.6 SurfaceFlinger Color Pipeline¶
On the SurfaceFlinger side, color management involves:
-
Per-layer color space: Each layer declares its color space (sRGB, Display P3, BT.2020). SurfaceFlinger converts to the output color space during composition.
-
Display color profiles:
DisplayColorProfiledescribes the display's native color gamut and supported HDR types. -
HDR handling: HDR content (HDR10, HLG, Dolby Vision) receives tone-mapping through RenderEngine when the display does not natively support the HDR format.
-
Color modes: The HAL supports multiple color modes (e.g., sRGB, Display P3, Native) that SurfaceFlinger can switch between based on content requirements.
24.10.7 HDR Output Control¶
DMS provides HDR output control, allowing users to disable specific HDR types:
// DisplayManagerService.java
private int[] mUserDisabledHdrTypes = {};
private boolean mAreUserDisabledHdrTypesAllowed = true;
The HdrConversionMode controls system-wide HDR format conversion:
- Passthrough: HDR content sent to display as-is
- System-selected: System chooses optimal output format
- Force SDR: All content tone-mapped to SDR
24.10.8 Per-App Color Transforms¶
AppSaturationController applies per-app desaturation for accessibility.
When an accessibility service requests reduced saturation for specific
apps, the controller maintains a per-UID saturation level:
graph LR
A11Y["AccessibilityManager"] -->|"setAppSaturation(uid, level)"| ASC["AppSaturationController"]
ASC -->|"per-layer colorTransform"| SF7["SurfaceFlinger<br/>(per-layer matrix)"]
Unlike the global transforms that apply to all content, per-app transforms are applied as per-layer colour matrices in SurfaceFlinger, allowing different apps to have different saturation levels simultaneously.
24.10.9 Daltonizer (Color Blindness Correction)¶
The daltonizer applies a colour-correction matrix for users with colour vision deficiency. It supports three types:
- Protanomaly -- Red-weak
- Deuteranomaly -- Green-weak
- Tritanomaly -- Blue-weak
The correction matrix is sent to SurfaceFlinger via the
SURFACE_FLINGER_TRANSACTION_DALTONIZER (1014) transaction code. It
operates independently of the colour matrix pipeline -- the daltonizer
is applied in SurfaceFlinger's shader as a separate transform.
24.10.10 Even Dimmer¶
"Even Dimmer" is an accessibility feature (formerly "Extra Dim") that
reduces display brightness below the minimum hardware brightness by
applying a dimming colour matrix. The
ReduceBrightColorsTintController generates a matrix that scales all
colour channels:
The percentage is set through Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL
and converted to a matrix with diagonal values less than 1.0. This works
in conjunction with (not instead of) the hardware brightness control,
allowing the display to appear dimmer than the backlight minimum.
24.10.11 Color Mode Selection¶
The user-facing "Display" settings provide colour mode selection:
| Mode | Constant | Description |
|---|---|---|
| Natural | COLOR_MODE_NATURAL (0) |
Calibrated sRGB |
| Boosted | COLOR_MODE_BOOSTED (1) |
Slightly enhanced saturation |
| Saturated | COLOR_MODE_SATURATED (2) |
Wide gamut, vivid colours |
| Automatic | COLOR_MODE_AUTOMATIC (3) |
Content-aware switching |
In Automatic mode, the system switches between sRGB and the display's
native wide gamut based on the colour space of the visible content. This
is communicated to SurfaceFlinger via the SURFACE_FLINGER_TRANSACTION_DISPLAY_COLOR (1023) transaction code.
24.11 Display Power¶
24.11.1 DisplayPowerController: The State Machine¶
DisplayPowerController (3507 lines) manages the power state of a single
display. It runs on its own handler and communicates asynchronously with
both PowerManagerService (via DisplayPowerCallbacks) and the display
hardware.
// frameworks/base/services/core/java/com/android/server/display/
// DisplayPowerController.java
final class DisplayPowerController implements
AutomaticBrightnessController.Callbacks,
DisplayWhiteBalanceController.Callbacks {
// Message types
private static final int MSG_UPDATE_POWER_STATE = 1;
private static final int MSG_SCREEN_ON_UNBLOCKED = 2;
private static final int MSG_SCREEN_OFF_UNBLOCKED = 3;
// ...
}
24.11.2 Display Power States¶
The display follows a strict state machine:
stateDiagram-v2
[*] --> OFF
OFF --> ON : POLICY_BRIGHT or POLICY_DIM
ON --> DOZE : POLICY_DOZE
ON --> OFF : POLICY_OFF
DOZE --> DOZE_SUSPEND : timeout
DOZE --> ON : user interaction
DOZE_SUSPEND --> DOZE : proximity wakeup
DOZE_SUSPEND --> OFF : POLICY_OFF
DOZE --> OFF : POLICY_OFF
ON --> ON_SUSPEND : suspend request
state ON {
BRIGHT --> DIM : timeout
DIM --> BRIGHT : user interaction
}
The DisplayPowerRequest from PowerManagerService specifies the
desired policy:
| Policy | Description |
|---|---|
POLICY_OFF |
Display completely off |
POLICY_DOZE |
Low-power always-on display (AOD) |
POLICY_DIM |
Display dimmed (approaching sleep) |
POLICY_BRIGHT |
Normal brightness |
24.11.3 Brightness Control¶
The brightness pipeline in DisplayPowerController involves multiple
strategies:
graph TD
subgraph "Brightness Inputs"
USER["User Setting<br/>(brightness slider)"]
AUTO["AutomaticBrightnessController<br/>(light sensor)"]
CLAMP["BrightnessClamperController<br/>(thermal, power, HBM)"]
TEMP["DisplayWhiteBalanceController"]
end
subgraph "DisplayBrightnessController"
DBC["Strategy Selection"]
STRAT["DisplayBrightnessStrategy"]
end
subgraph "Output"
ANIM["RampAnimator<br/>(smooth transitions)"]
DPS["DisplayPowerState<br/>(screen brightness)"]
SF6["SurfaceFlinger<br/>(setDisplayBrightness)"]
end
USER --> DBC
AUTO --> DBC
CLAMP --> DBC
TEMP --> DBC
DBC --> STRAT
STRAT --> ANIM
ANIM --> DPS
DPS --> SF6
AutomaticBrightnessController reads from the ambient light sensor and
applies the user's brightness curve configuration (BrightnessConfiguration)
to determine the target brightness. It supports multiple modes:
| Mode | Constant | Description |
|---|---|---|
| Default | AUTO_BRIGHTNESS_MODE_DEFAULT |
Standard auto-brightness |
| Idle | AUTO_BRIGHTNESS_MODE_IDLE |
Lower brightness when idle |
| Doze | AUTO_BRIGHTNESS_MODE_DOZE |
AOD brightness curve |
| Bedtime Wear | AUTO_BRIGHTNESS_MODE_BEDTIME_WEAR |
Wear OS bedtime mode |
BrightnessClamperController enforces brightness limits from:
- Thermal throttling (reduce brightness when device is hot)
- High Brightness Mode (HBM) restrictions
- Power saving mode constraints
- Even Dimmer accessibility feature
24.11.4 Always-On Display (AOD)¶
AOD support requires coordination between DisplayPowerController,
DreamManagerService, and SurfaceFlinger:
- DreamManagerService starts the AOD dream (a
DreamServicewithACTIVITY_TYPE_DREAM). - DisplayPowerController transitions to
POLICY_DOZE, setting the display to a low-power state. - SurfaceFlinger may switch to a special display mode with reduced refresh rate and limited colour depth.
- DisplayPowerState manages the screen brightness to the AOD level.
The ColorFade animation (the screen-off effect) is rendered using
OpenGL ES, creating a smooth fade-to-black or fade-to-AOD transition.
24.11.5 Sleep Tokens¶
Sleep tokens are the mechanism by which display power state interacts with
the Activity lifecycle. ActivityTaskManagerService acquires sleep tokens
when displays go to sleep, which freezes the activity lifecycle -- no
activity resumes or pauses while the display is off.
When DisplayPowerController signals screen-off, it triggers:
PowerManager.goToSleep()-- Initiates the sleep sequenceActivityTaskManagerInternal.acquireSleepToken()-- Freezes activity lifecycle for the display- Activities in the RESUMED state are paused
- The window manager applies the
DISPLAY_STATE_OFFflag
When the display wakes, the sleep token is released, and the foreground activity is resumed.
24.11.6 Proximity Sensor¶
DisplayPowerProximityStateController manages the proximity sensor that
turns off the display during phone calls:
stateDiagram-v2
[*] --> Unknown
Unknown --> Near : sensor reports NEAR
Unknown --> Far : sensor reports FAR
Near --> Far : sensor reports FAR
Far --> Near : sensor reports NEAR
Near --> Unknown : timeout / call ended
When proximity is NEAR and a phone call is active, the display is forced off. A debounce mechanism prevents flickering when the sensor reading oscillates.
24.11.7 The updatePowerState() Pipeline¶
The core of DisplayPowerController is the updatePowerState() method,
triggered by MSG_UPDATE_POWER_STATE. This is a large, single-pass method
that evaluates the current state and computes the desired display power
configuration:
flowchart TD
A["MSG_UPDATE_POWER_STATE"] --> B["Read pending power request"]
B --> C["Compute desired screen state<br/>(ON, DOZE, OFF)"]
C --> D["Initialize display power state<br/>if first time"]
D --> E["Handle proximity sensor"]
E --> F["Determine brightness source<br/>(user, auto, override)"]
F --> G["Apply brightness clamping<br/>(thermal, HBM, RBC)"]
G --> H["Compute brightness ramp<br/>(fast for user, slow for auto)"]
H --> I["Apply color fade animation<br/>(screen on/off effect)"]
I --> J["Set display brightness<br/>via DisplayBlanker"]
J --> K["Report screen state<br/>to policy (TURNING_ON, ON, etc.)"]
K --> L{"State settled?"}
L -->|"No"| M["Re-post MSG_UPDATE_POWER_STATE"]
L -->|"Yes"| N["Report ready to PowerManager"]
The method uses a state machine for tracking screen-on/off reporting:
| State | Constant | Meaning |
|---|---|---|
| Unreported | REPORTED_TO_POLICY_UNREPORTED (-1) |
Initial state |
| Screen off | REPORTED_TO_POLICY_SCREEN_OFF (0) |
Display confirmed off |
| Turning on | REPORTED_TO_POLICY_SCREEN_TURNING_ON (1) |
Display powering up |
| Screen on | REPORTED_TO_POLICY_SCREEN_ON (2) |
Display confirmed on |
| Turning off | REPORTED_TO_POLICY_SCREEN_TURNING_OFF (3) |
Display powering down |
24.11.8 Brightness Ramp Animations¶
DisplayPowerController uses DualRampAnimator (an extension of
RampAnimator) to smoothly transition brightness. The dual ramp handles
both the HDR brightness and SDR brightness simultaneously:
- Increase ramp: Maximum time
mBrightnessRampIncreaseMaxTimeMillis(e.g., 2000ms for a gentle brightening when going outdoors) - Decrease ramp: Maximum time
mBrightnessRampDecreaseMaxTimeMillis(e.g., 5000ms for a gentle dimming when going indoors) - Idle ramps: Separate, typically longer ramp times for when the device is idle
The ramp skipping logic (RAMP_STATE_SKIP_INITIAL,
RAMP_STATE_SKIP_AUTOBRIGHT) allows the initial brightness set on
screen-on to be applied instantly without animation, avoiding a visible
brightness ramp when the screen turns on.
24.11.9 High Brightness Mode (HBM)¶
HighBrightnessModeController manages the display's peak brightness
capability, which is typically limited by thermal constraints:
stateDiagram-v2
[*] --> Normal
Normal --> HBM_SV : Sunlight detected, lux > threshold
HBM_SV --> Normal : Sunlight absent or thermal limit
Normal --> HBM_HDR : HDR content displayed
HBM_HDR --> Normal : No HDR content
HBM_SV --> Throttled : Thermal warning
Throttled --> Normal : Temperature drops
HBM metadata (HighBrightnessModeMetadata) is maintained per-display by
HighBrightnessModeMetadataMapper, tracking running time in HBM to
enforce time-in-state limits that protect the display hardware.
24.11.10 Brightness Nit Ranges¶
DisplayPowerController supports a detailed nit-based brightness range
for telemetry, with 37 buckets from 0-1 nits through 2750-3000 nits:
private static final float[] BRIGHTNESS_RANGE_BOUNDARIES = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80,
90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1200,
1400, 1600, 1800, 2000, 2250, 2500, 2750, 3000
};
Similarly, ambient light levels are bucketed into 14 lux ranges from
0-0.1 through 30000-100000 lux for brightness event tracking. These
statistics feed into BrightnessTracker for adaptive brightness model
improvement and FrameworkStatsLog for platform telemetry.
24.11.11 Lead-Follower Brightness¶
For devices with multiple displays that should share brightness (e.g.,
a foldable where inner and outer displays should have consistent brightness),
DisplayPowerController supports a lead-follower model:
When mLeadDisplayId is set, the follower display mirrors the leader's
brightness decisions rather than running its own auto-brightness
algorithm. The leader-follower relationship is defined in the Layout
configuration from DeviceStateToLayoutMap.
24.11.12 Display Offload¶
DisplayOffloadSession enables offloading display updates to a
co-processor (e.g., for watch faces on Wear OS). When offload is active,
the main processor can enter deep sleep while the co-processor handles
simple display updates (time, complications). The session is managed
through DisplayOffloadSessionImpl in DisplayManagerService.
When offloading is active and the screen needs to turn on (e.g., wrist
raise), the MSG_OFFLOADING_SCREEN_ON_UNBLOCKED message coordinates
the handoff from the co-processor back to the main display pipeline,
tracked via the SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME
trace marker.
24.12 Detailed Reference¶
This chapter has covered the major subsystems of Android's display pipeline,
from the framework-level DisplayManagerService down to the native-level
SurfaceFlinger compositor and buffer management. For readers seeking
additional depth, the companion reports provide exhaustive analysis of
every topic introduced here:
Companion Report Series¶
The AOSP Window and Display System Architecture Report is a three-part document totalling over 100 sections:
Part 1: Foundations, Architecture, and Rendering Pipeline (Sections 1-45)
- WindowManagerService architecture and threading model
- WindowContainer hierarchy and Z-ordering
- Shell Transitions and animation system
- SurfaceFlinger rendering pipeline
- Window insets and system bars
Part 2: Window Features and Subsystems (Sections 46-75)
- Section 51: BufferQueue and BLASTBufferQueue Architecture -- Slot state machine, triple buffering, BLAST transaction-based delivery
- Section 52: Buffer Sharing Architecture and Lifecycle -- Gralloc HAL, fence synchronization, cross-process sharing
- Section 53: Virtual Display Composition Pipeline -- VirtualDisplaySurface, three-BQ routing, SinkSurfaceHelper
- Section 55: Display Refresh Architecture -- VSYNC pipeline, Choreographer, RefreshRateSelector, frame timeline, Linux DRM/KMS comparison
- Section 56: Screen Rotation and Orientation -- DisplayRotation, SeamlessRotator, AsyncRotationController, FixedRotationTransformState
- Section 57: Foldable Display Support -- DeviceStateManagerService, FoldableDeviceStateProvider, LogicalDisplayMapper display swapping
- Section 62: Display Color Management -- ColorDisplayService, night display, white balance, saturation, daltonizer, SurfaceFlinger color pipeline
Part 3: System Integration and Platform Variants (Sections 76-100)
- Section 77: Power Management and Window System -- AWAKE through ASLEEP states, DreamManagerService, AOD, DisplayPowerController, sleep tokens
- Section 88: Display Cutout and Rounded Corners -- DisplayCutout, CutoutSpecification, WmDisplayCutout, cutout modes, RoundedCorners, DisplayShape
- Section 89: SurfaceFlinger Front-End Refactor and Composition -- LayerLifecycleManager, LayerSnapshotBuilder, CompositionEngine
- Section 93: Display Mirroring and Screen Casting -- Mirror layers, MediaProjection, ContentRecorder, MediaRouter, WifiDisplayAdapter
Quick Reference: Key Source Paths¶
| Component | Path |
|---|---|
| DisplayManagerService | frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java |
| LogicalDisplay | frameworks/base/services/core/java/com/android/server/display/LogicalDisplay.java |
| LogicalDisplayMapper | frameworks/base/services/core/java/com/android/server/display/LogicalDisplayMapper.java |
| DisplayPowerController | frameworks/base/services/core/java/com/android/server/display/DisplayPowerController.java |
| ColorDisplayService | frameworks/base/services/core/java/com/android/server/display/color/ColorDisplayService.java |
| DisplayTransformManager | frameworks/base/services/core/java/com/android/server/display/color/DisplayTransformManager.java |
| VirtualDisplayAdapter | frameworks/base/services/core/java/com/android/server/display/VirtualDisplayAdapter.java |
| DeviceStateManagerService | frameworks/base/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java |
| DisplayArea | frameworks/base/services/core/java/com/android/server/wm/DisplayArea.java |
| DisplayAreaPolicyBuilder | frameworks/base/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java |
| DisplayAreaPolicy | frameworks/base/services/core/java/com/android/server/wm/DisplayAreaPolicy.java |
| DisplayRotation | frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java |
| SeamlessRotator | frameworks/base/services/core/java/com/android/server/wm/SeamlessRotator.java |
| AsyncRotationController | frameworks/base/services/core/java/com/android/server/wm/AsyncRotationController.java |
| DisplayCutout | frameworks/base/core/java/android/view/DisplayCutout.java |
| CutoutSpecification | frameworks/base/core/java/android/view/CutoutSpecification.java |
| Scheduler | frameworks/native/services/surfaceflinger/Scheduler/Scheduler.h |
| RefreshRateSelector | frameworks/native/services/surfaceflinger/Scheduler/RefreshRateSelector.h |
| VSyncPredictor | frameworks/native/services/surfaceflinger/Scheduler/VSyncPredictor.h |
| VSyncDispatchTimerQueue | frameworks/native/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h |
| EventThread | frameworks/native/services/surfaceflinger/Scheduler/EventThread.h |
| LayerLifecycleManager | frameworks/native/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h |
| LayerSnapshotBuilder | frameworks/native/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h |
| CompositionEngine | frameworks/native/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h |
| BLASTBufferQueue | frameworks/native/libs/gui/include/gui/BLASTBufferQueue.h |
Debugging Commands¶
| Command | Purpose |
|---|---|
dumpsys display |
DisplayManagerService state |
dumpsys SurfaceFlinger |
SurfaceFlinger layer tree, composition stats |
dumpsys SurfaceFlinger --frametimeline |
Frame timing data |
dumpsys SurfaceFlinger --list |
List all layers |
dumpsys window displays |
WindowManagerService display info |
dumpsys window display-areas |
DisplayArea hierarchy |
dumpsys color_display |
ColorDisplayService state |
dumpsys device_state |
DeviceStateManagerService state |
cmd display set-brightness <0.0-1.0> |
Set display brightness |
cmd display reset-brightness-configuration |
Reset auto-brightness |
wm size |
Display logical size |
wm density |
Display density |
settings put system accelerometer_rotation 0/1 |
Lock/unlock rotation |
Summary¶
The Android display system is a deep vertical stack that begins with hardware VSYNC interrupts and extends through native C++ composition, Java framework services, and application-level APIs. The key architectural decisions that define this system are:
-
Logical/Physical separation:
LogicalDisplaydecouples the system-visible display from the underlying hardware, enabling foldable display swapping, virtual displays, and future multi-panel configurations. -
DisplayArea tree: The
DisplayAreaPolicyBuildercreates a flexible container hierarchy that enforces Z-ordering while allowing features (magnification, one-handed mode, cutout hiding) to target specific window-type ranges. -
VSYNC-driven pipeline: Every frame starts with a predicted VSYNC from
VSyncPredictor, flows throughVSyncDispatchTimerQueuetoEventThread, crosses into Java-land viaChoreographer, and culminates inCompositionEngine::present(). -
Transaction-based buffer delivery:
BLASTBufferQueuebundles buffer submission with geometry changes in atomicSurfaceControl.Transactionoperations, eliminating the class of bugs that arose from buffer-geometry desynchronisation. -
Front-end/back-end split: SurfaceFlinger's refactored architecture separates layer state management (
LayerLifecycleManager,LayerSnapshotBuilder) from composition (CompositionEngine,HWComposer), enabling better testing, incremental updates, and reduced lock contention. -
Priority-ordered colour transforms:
DisplayTransformManagercomposes multiple 4x4 colour matrices (night display, white balance, saturation, accessibility) in a defined priority order, producing a single combined transform for SurfaceFlinger. -
State-driven foldable support:
DeviceStateManagerServiceprovides a clean state-machine abstraction for foldable postures, withLogicalDisplayMapperhandling the complex display swapping that makes fold/unfold transitions appear seamless to applications.
These subsystems interact constantly during normal device operation. A single frame touches the VSYNC predictor, Choreographer, ViewRootImpl, BLASTBufferQueue, the SurfaceFlinger front-end, CompositionEngine, HWComposer, and the kernel DRM driver -- a pipeline that completes in under 16 milliseconds at 60 Hz, or under 8 milliseconds at 120 Hz.
End-to-End Frame Lifecycle¶
To illustrate how these subsystems interact, consider the lifecycle of a single frame from touch to photon:
sequenceDiagram
participant HW2 as Display Panel
participant VP2 as VSyncPredictor
participant VDT as VSyncDispatchTimerQueue
participant ET2 as EventThread (app)
participant CH2 as Choreographer
participant VRI3 as ViewRootImpl
participant APP2 as Application View
participant BBQ4 as BLASTBufferQueue
participant GPU2 as GPU (RenderThread)
participant ET3 as EventThread (sf)
participant SF9 as SurfaceFlinger
participant CE5 as CompositionEngine
participant HWC5 as HWComposer
participant DRM2 as DRM/KMS
HW2->>VP2: HW VSYNC timestamp
VP2->>VP2: Update linear model
VP2->>VDT: Predicted next VSYNC
VDT->>ET2: Timer fires (app wakeup)
ET2->>CH2: VSYNC event via BitTube
CH2->>CH2: CALLBACK_INPUT (process touch)
CH2->>CH2: CALLBACK_ANIMATION
CH2->>VRI3: CALLBACK_TRAVERSAL
VRI3->>APP2: onMeasure/onLayout/onDraw
APP2->>VRI3: Canvas draw commands
VRI3->>GPU2: Flush to RenderThread
GPU2->>BBQ4: queueBuffer (with acquire fence)
BBQ4->>SF9: Transaction.setBuffer(...)
VDT->>ET3: Timer fires (sf wakeup)
ET3->>SF9: VSYNC callback
SF9->>SF9: Apply pending transactions
SF9->>SF9: Build LayerSnapshots
SF9->>CE5: present(refreshArgs)
CE5->>HWC5: validate() + presentDisplay()
HWC5->>DRM2: Atomic commit (page flip)
DRM2->>HW2: Scanout new frame
HW2->>HW2: Photons reach user's eye
Typical latency breakdown (at 120 Hz, 8.33ms period):
| Phase | Duration | Notes |
|---|---|---|
| VSYNC to app wakeup | 0.5-1ms | Timer scheduling overhead |
| Input + animation | 0.5-2ms | Depends on callback complexity |
| Measure/layout/draw | 1-4ms | View tree complexity |
| GPU render | 1-3ms | Shader complexity, overdraw |
| SurfaceFlinger compose | 0.5-2ms | Depends on GPU vs HWC |
| HWC commit | 0.2-0.5ms | DRM atomic commit |
| Total | 3.7-12.5ms | Must fit in 8.33ms for 120Hz |
When the total exceeds the VSYNC period, the frame misses its deadline
and is presented one period late (a "jank" frame). The FrameTimeline
records these misses, and tools like perfetto and dumpsys SurfaceFlinger
--frametimeline expose them for performance analysis.
Source Code Statistics¶
The display system codebase is substantial:
| Component | Lines of Code |
|---|---|
| DisplayManagerService.java | 6,601 |
| DisplayPowerController.java | 3,507 |
| DisplayRotation.java | 2,255 |
| LogicalDisplay.java | 1,314 |
| LogicalDisplayMapper.java | ~1,200 |
| DisplayAreaPolicyBuilder.java | 1,052 |
| DisplayArea.java | 882 |
| ColorDisplayService.java | ~1,800 |
| DeviceStateManagerService.java | ~1,500 |
| RefreshRateSelector.cpp/.h | ~2,000 |
| Scheduler.cpp/.h | ~1,500 |
| LayerLifecycleManager.cpp/.h | ~800 |
| LayerSnapshotBuilder.cpp/.h | ~1,200 |
| BLASTBufferQueue.cpp/.h | ~1,500 |
| Estimated total | ~25,000+ |
This does not include SurfaceFlinger's main loop, CompositionEngine implementation, HWComposer HAL interface, RenderEngine, or the client-side Surface/Canvas/OpenGL stack, which collectively add another 50,000+ lines.