Still under review.
Table of Contents
Part I: Foundations and Overview
- Executive Summary
- System Architecture Overview
- Android vs Linux Graphics Stack
- Display Servers: Android SurfaceFlinger vs Linux Wayland/X11
Part II: Core Objects — Activity, Window, and Task
Part III: Window Management System
- WM Core (Server-Side)
- WM Shell Library
- SystemUI and Launcher3 Integration
- Dependency Injection Architecture
- Threading Model
- Shell Feature Modules
- Custom Window Manager and Shell Development
- SurfaceControlViewHost and Embedded Windows
Part IV: Transitions, Animation, and Surface Leash
- Transition System
- Surface Leash — Deep Dive
- Animation Framework Architecture
- Window Animation System
- Shell Transition Animations
Part V: Display System
- Display System Architecture
- DisplayManagerService
- DisplayArea Hierarchy and Policy
- Insets System
- Surface System and Composition
- Window-Display Interaction Model
Part VI: Multi-Window System
- Multi-Window Architecture
- Windowing Modes and Bounds
- Multi-Window Operations
- Window Manager Paradigm Analysis: Stacking vs Tiling
- Multi-Window Evolution
- Embedded Activity
Part VII: Multi-Display System
- Multi-Display Architecture
- Virtual Displays
- Display Topology and Spatial Layout
- Cross-Display Window Movement
Part VIII: Rendering Pipeline
- Graphics Buffer Pipeline: From Buffer to Activity
- HWUI Performance Architecture
- Window Shadow System
- HDR for Window and Surface
- Android’s Direct Rendering Infrastructure
- Hardware Composer (HWC)
- Gralloc — Graphics Memory Allocator
- RenderEngine — SurfaceFlinger’s GPU Compositor
Part IX: Cross-Cutting Topics
- Input System and Focus Management — InputFlinger pipeline, SurfaceFlinger↔InputFlinger relationship, window targeting, ViewRootImpl InputStage chain, keyboard shortcuts
- Per-Variant Shell and WM Customization
- SystemUI Multi-Display Architecture
- Launcher3 and Recents Multi-Display Architecture
- Multi-Window and Multi-Display Test Infrastructure
- Caption Bar Architecture — Legacy DecorView vs Shell-based, SurfaceControl hierarchy, WindowDecoration, drag-to-move/resize, view host pooling
- Drag and Drop Architecture — View API, WMS DragDropController/DragState, Shell drop targets, ClipData permissions, multi-display drag
Part X: Buffer Management and Virtual Display Internals
- BufferQueue and BLASTBufferQueue Architecture — Slot state machine, triple buffering, producer-consumer, BLAST transaction-based delivery
- Buffer Sharing Architecture and Lifecycle — Gralloc HAL, fence synchronization, cross-process sharing, end-to-end flow
- Virtual Display Composition Pipeline — SurfaceFlinger internals, VirtualDisplaySurface, three-BQ routing, SinkSurfaceHelper
- CompanionDeviceManager and Virtual Device Framework — Device profiles, VirtualDeviceImpl, window policy control, input routing, mirror streaming
Part XI: Display Refresh and Frame Scheduling
- Display Refresh Architecture — VSYNC pipeline, Choreographer, RefreshRateSelector, frame timeline, Android vs Linux DRM/KMS comparison
Part XII: Rotation, Foldables, Keyguard, Starting Windows, and App Compatibility
- Screen Rotation and Orientation — DisplayRotation, SeamlessRotator, AsyncRotationController, FixedRotationTransformState, sensor orientation detection
- Foldable Display Support — DeviceStateManagerService, FoldableDeviceStateProvider, LogicalDisplayMapper display swapping, BookStyleDeviceStatePolicy
- Keyguard and Lock Screen Window Management — KeyguardController, KeyguardViewMediator, KeyguardTransitionHandler, occlude/unocclude, AOD integration
- Starting Windows and Splash Screens — StartingWindowController, StartingSurfaceDrawer, SplashScreenView, SnapshotController, exit animations
- Letterboxing and App Compatibility — AppCompatController, 12 policy classes, Letterbox surfaces, size compat mode, camera compat, reachability
Part XIII: Wallpaper, Color, Window Types, Accessibility, and IME
- Wallpaper Window System — WallpaperController, wallpaper targeting, parallax offsets, freeze mechanism, multi-display wallpaper
- Display Color Management — ColorDisplayService, night display, white balance, saturation, daltonizer, SurfaceFlinger color pipeline
- Window Types and Z-Order Policy — TYPE_* constants, 36-layer model, sub-window ordering, permission requirements
- Accessibility Window Management — AccessibilityController, magnification, window change detection, input interception, security
- IME Window Management — Three-target architecture, ImeInsetsSourceProvider, SOFT_INPUT flags, multi-display IME routing
Part XIV: Shell Feature Implementations
- Picture-in-Picture (PIP) Architecture — PipTaskOrganizer, PipTransition state machine, PipAnimationController, touch/stash, TV PIP, PIP v2
- Bubbles System Architecture — BubbleController, BubbleStackView, BubbleData, expanded views, overflow, Shell transitions integration
- Split Screen Implementation — StageCoordinator, SplitLayout, divider mechanics, enter/exit transitions, task snapping
- Desktop Windowing Mode — DesktopModeController, DesktopTasksController, freeform window management, caption integration, task positioning
- Predictive Back and Back Navigation — BackNavigationController, BackAnimationController, CrossTaskBackAnimation, gesture flow, custom app callbacks
Part XV: Client-Side Architecture
- ViewRootImpl and View-to-WMS Bridge — IWindowSession, relayout protocol, Choreographer, InputStage chain, RenderThread, measure-layout-draw
Part XVI: System Configuration and Multi-User
- Configuration Change Propagation — ConfigurationContainer hierarchy, three-tier config, cascade chain, triggers, callback vs restart
- Multi-User Window Isolation — Per-user state tracking, user switch protocol, task filtering, display assignment, work profiles
Part XVII: Task Lifecycle and Snapshots
- Recent Tasks and Task Snapshots — RecentTasks management, SnapshotController, capture pipeline, persistence, cache, starting windows
Part XVIII: Security and Privacy
- Window Privacy and Security — FLAG_SECURE, SensitiveContentPackages, DisplayHash, screen recording callbacks, MDM, secure composition
Part XIX: System UI Windows
- Notification Shade and Status Bar Windows — ShadeWindowLayoutParams, NotificationShadeWindowState, gesture handling, Z-order, input control
Part XX: Power and Performance
- Power Management and Window System — AWAKE→DREAMING→DOZING→ASLEEP, DreamManagerService, AOD, DisplayPowerController, sleep tokens
- Game Mode and Performance Hints — GameManagerService, SystemPerformanceHinter, RefreshRatePolicy, TaskFpsCallbackController
Part XXI: Task Management and System Alerts
- Task Affinity and Launch Modes — 5 launch modes, ActivityStarter pipeline, task affinity, intent flags, task reparenting
- Toast and System Alert Windows — TYPE_TOAST constraints, SYSTEM_ALERT_WINDOW permission, AlertWindowNotification, overlay opacity, tapjacking defense
Part XXII: Accessibility and Input Features
- One-Handed Mode — DisplayAreaOrganizer scaling, OneHandedController, Y-offset animation, touch/timeout handling
- System Gestures and Navigation Bar — Edge swipe detection, gesture exclusion zones, 3 navigation modes, NavBarFadeAnimationController, transient bars
Part XXIII: Foldable Shell Features
- Unfold Animation System — Dual-path architecture, UnfoldAnimationController, UnfoldTransitionHandler, FullscreenUnfoldTaskAnimator, SplitTaskUnfoldAnimator
Part XXIV: Emerging Platforms
- XR and Spatial Window Management — XrWindowProperties, VrController, XR app operations, window management integration
Part XXV: Debugging and Observability
- Window Tracing and Debugging Infrastructure — Legacy Winscope, Perfetto, TransitionTracer, LayerTracing, TransactionTracing, dumpsys
Part XXVI: Content Capture and Protection
- Screen Recording and Content Protection — ScreenRecordingCallbackController, ContentRecordingController, ContentRecorder, FLAG_SECURE, virtual display interaction
Part XXVII: Reference and Evolution
Part I: Foundations and Overview
1. Executive Summary
The AOSP Window and Display System is a multi-layered architecture spanning from native compositor (SurfaceFlinger) to Java framework services (WindowManagerService, DisplayManagerService) to presentation libraries (WM Shell). It manages how windows are created, laid out, animated, and composited onto physical and virtual displays.
1.1 Scope
This report provides a comprehensive analysis of Android’s window and display subsystem architecture, covering:
- Core window management: How windows are created, tracked, laid out, and destroyed (§5-§14)
- Transition and animation: The surface leash mechanism, shell-driven transitions, and animation pipelines (§15-§19)
- Display management: Display hardware abstraction, DisplayArea hierarchy, insets, and surface composition (§20-§25)
- Multi-window: Split-screen, PiP, freeform, desktop mode, and embedded activities (§26-§31)
- Multi-display: Virtual displays, topology, and cross-display operations (§32-§35)
- Rendering pipeline: Buffer management, HWUI render pipeline, shadows, HDR, DRI, HWC, Gralloc, and RenderEngine (§36-§43)
- Input system: InputFlinger pipeline architecture, SurfaceFlinger↔InputFlinger window geometry sync, input dispatch and window targeting, ViewRootImpl InputStage chain, keyboard shortcut subsystem, focus management (§23, §44)
- Per-variant customization: How Phone, TV, Auto, and Desktop customize Shell/WM via Dagger DI (§45)
- SystemUI and Launcher3 multi-display: Per-display system decorations, secondary display launcher, and recents routing (§46-§47)
- Testing infrastructure: CTS, unit, and Shell tests for multi-window and multi-display verification (§48)
- Caption bar architecture: Legacy DecorView vs Shell-based caption, SurfaceControl hierarchy, drag-to-move/resize, view host pooling, transparent/customizable captions (§49)
- Drag and drop architecture: View.startDragAndDrop() API, WMS DragDropController/DragState lifecycle, Shell drop target handling, ClipData permission model, multi-display drag with coordinate transformation (§50)
- Buffer management: BufferQueue slot state machine, triple buffering, BLASTBufferQueue transaction-based delivery, Gralloc HAL, fence synchronization, cross-process buffer sharing (§51-§52)
- Virtual display composition: SurfaceFlinger VirtualDisplaySurface three-BufferQueue routing, SinkSurfaceHelper async output, composition modes (§53)
- Companion device framework: CompanionDeviceManager, VirtualDeviceManager, virtual device display creation, window policy control, input routing (§54)
- Display refresh architecture: VSYNC pipeline from HWC through SurfaceFlinger Scheduler to Choreographer, refresh rate selection, frame timeline and jank detection, Android vs Linux DRM/KMS comparison (§57)
- Comparative analysis: Android vs Linux graphics stack, stacking vs tiling paradigms (§3-§4, §29)
1.2 Architectural Overview
The system is organized into four major layers:
| Layer | Components | Responsibility |
|---|---|---|
| Native Compositor | SurfaceFlinger, HWC HAL, Gralloc | Buffer composition, display output, hardware abstraction |
| Framework Services | WindowManagerService, DisplayManagerService, InputManagerService | Window policy, display management, input routing |
| Shell Library | WM Shell, TaskOrganizer, TransitionHandler | Window layout presentation, animation orchestration |
| Client Framework | ViewRootImpl, WindowManager, SurfaceControl | App-side window creation, rendering, input handling |
1.3 Key Architectural Pillars
- Window-Display Separation: WindowManagerService manages window policy; DisplayManagerService manages physical/virtual display hardware. They communicate via
DisplayManagerInternal - Organizer Pattern: TaskOrganizer, DisplayAreaOrganizer, TaskFragmentOrganizer allow Shell to control window layout without modifying core WM
- Shell-Driven Transitions: A new transition system replaces legacy AppTransition, enabling Shell to orchestrate complex multi-window animations
- Desktop-First Evolution: 133 feature flags signal a shift toward full desktop windowing with multi-display, multi-desk, and freeform window support
- Surface Hierarchy: Every WindowContainer maps 1:1 to a SurfaceControl, creating a parallel tree synchronized via BLAST transactions to SurfaceFlinger
- Multi-Display as First-Class: Display groups, topology management, per-display focus, and virtual display support enable true multi-display computing
- Input-Window Coupling: InputDispatcher uses the window hierarchy from WMS to determine input routing, with per-display focus tracking for multi-display scenarios
1.4 Reading Guide
The report follows a top-down structure: foundational concepts and comparisons first (Part I), then core objects (Part II), window management (Part III), transitions and animation (Part IV), display systems (Part V), multi-window (Part VI), multi-display (Part VII), and the rendering pipeline including HWUI, HWC, Gralloc, and RenderEngine (Part VIII). Part IX covers cross-cutting topics (input system, per-variant customization, SystemUI/Launcher3 multi-display, testing infrastructure, and caption bar architecture, and drag and drop system). Part X covers buffer management and virtual display internals (BufferQueue, buffer sharing, virtual display composition, companion device framework). Part XXVII (final) provides evolution context and key file references. Part XI covers the display refresh pipeline, VSYNC scheduling, and a comparison with Linux DRM/KMS. Sections use §N cross-references to connect related topics.
2. System Architecture Overview
2.1 Concept Architecture: From Buffer to Application
The following diagram shows Android’s complete graphics concept stack, tracing how a pixel buffer flows from hardware allocation through composition and windowing up to the application — for both the standard Java/Kotlin path and the NDK (NativeActivity) path:
graph BT
subgraph "Hardware & HAL"
BUF["Buffer<br/>(DMA-BUF / ION)"]
GRALLOC["Gralloc<br/>(IAllocator + AIMapper)"]
HWC["HWC<br/>(Hardware Composer)"]
end
subgraph "Native Compositor"
RE["RenderEngine<br/>(GPU CLIENT composition)"]
SF_LAYER["SurfaceFlinger Layer"]
SF["SurfaceFlinger"]
end
subgraph "Buffer Interface"
ANW_NDK["ANativeWindow<br/>(NDK C API)"]
ANW_SDK["ANativeWindow<br/>(internal)"]
end
subgraph "Standard (Java/Kotlin) Path"
SURFACE_SDK["Surface<br/>(Java)"]
HWUI["HWUI<br/>(HardwareRenderer)"]
VRI["ViewRootImpl"]
WINDOW_SDK["Window<br/>(PhoneWindow)"]
ACTIVITY["Activity"]
APP_SDK["Application<br/>(Java/Kotlin)"]
end
subgraph "NDK Path"
SURFACE_NDK["ASurface / Surface<br/>(NDK)"]
EGL_VK["EGL / Vulkan<br/>(App GPU rendering)"]
NWIN["NativeWindow"]
NACT["NativeActivity"]
APP_NDK["Application<br/>(C/C++)"]
end
BUF --> GRALLOC
GRALLOC --> HWC
GRALLOC --> RE
HWC --> SF
RE --> SF
SF --> SF_LAYER
SF_LAYER --> ANW_SDK
SF_LAYER --> ANW_NDK
ANW_SDK --> SURFACE_SDK
SURFACE_SDK --> HWUI
HWUI --> VRI
VRI --> WINDOW_SDK
WINDOW_SDK --> ACTIVITY
ACTIVITY --> APP_SDK
ANW_NDK --> SURFACE_NDK
SURFACE_NDK --> EGL_VK
EGL_VK --> NWIN
NWIN --> NACT
NACT --> APP_NDK
Standard path (Java/Kotlin): Buffer → Gralloc → SurfaceFlinger Layer → ANativeWindow → Surface (Java) → HWUI (HardwareRenderer + RenderThread) → ViewRootImpl → PhoneWindow → Activity → Application
NDK path (C/C++): Buffer → Gralloc → SurfaceFlinger Layer → ANativeWindow (NDK) → ASurface → EGL/Vulkan (app renders directly) → NativeWindow → NativeActivity → Application
Key differences:
- Standard path: HWUI interposes between the Surface and the View system, recording draw operations into display lists and rendering them on a dedicated RenderThread via Skia (GL or Vulkan)
- NDK path: The application renders directly to the Surface via EGL or Vulkan — no HWUI, no View system, no display list recording. The app is its own renderer.
- Shared infrastructure: Both paths share the same Buffer → Gralloc → SurfaceFlinger → HWC pipeline for composition and display output. Both produce buffers that SurfaceFlinger composites as layers.
2.2 Full System Model
graph TB
subgraph "Applications"
APP1[App Process 1]
APP2[App Process 2]
end
subgraph "System Server Process"
subgraph "Display Management"
DMS[DisplayManagerService]
LDM[LogicalDisplayMapper]
LDA[LocalDisplayAdapter]
VDA[VirtualDisplayAdapter]
end
subgraph "WM Core - Policy Layer"
WMS[WindowManagerService]
ATMS[ActivityTaskManagerService]
RWC[RootWindowContainer]
TC[TransitionController]
WOC[WindowOrganizerController]
BSE[BLASTSyncEngine]
end
subgraph "WM Shell - Presentation Layer"
STO[ShellTaskOrganizer]
TR[Transitions<br/>Master Orchestrator]
SC[ShellController]
SI[ShellInterface]
end
end
subgraph "SystemUI Process"
WMSh[WMShell]
end
subgraph "Launcher Process"
QS[Quickstep]
end
subgraph "Native Layer"
SF[SurfaceFlinger<br/>Compositor]
HWC[HWComposer]
RE[RenderEngine]
end
subgraph "Hardware"
DISP1[Internal Display]
DISP2[External Display]
end
APP1 -->|ViewRootImpl<br/>IWindowSession| WMS
APP2 -->|ViewRootImpl<br/>IWindowSession| WMS
DMS -->|DisplayManagerInternal| WMS
LDM --> DMS
LDA --> LDM
VDA --> LDM
WOC -->|Organizer Callbacks| STO
TC -->|TransitionInfo| TR
BSE -->|Sync Ready| TC
SI -->|Dagger Injection| WMSh
SI -->|AIDL Binder| QS
WMS -->|SurfaceControl.Transaction| SF
STO -->|SurfaceControl.Transaction| SF
TR -->|SurfaceControl.Transaction| SF
SF --> HWC
SF --> RE
HWC --> DISP1
HWC --> DISP2
2.3 Architectural Separation Principle
| Layer | Location | Responsibility |
|---|---|---|
| SurfaceFlinger | frameworks/native/services/surfaceflinger/ |
Native compositor, layer composition, display output |
| DisplayManagerService | frameworks/base/services/core/.../server/display/ |
Physical/virtual display discovery, logical display mapping, display groups |
| WM Core | frameworks/base/services/core/.../server/wm/ |
Window policy, task lifecycle, container hierarchy, layout computation |
| WM Shell | frameworks/base/libs/WindowManager/Shell/ |
Surface presentation, animations, feature UIs (PIP, bubbles, split-screen, desktop mode) |
| SystemUI | frameworks/base/packages/SystemUI/ |
Status bar, notifications, keyguard, user-facing system chrome |
| Launcher3 | packages/apps/Launcher3/quickstep/ |
Home screen, recents view, gesture navigation |
3. Android vs Linux Graphics Stack
3.1 Linux Graphics Stack Architecture
The modern Linux graphics stack (post-Wayland) consists of five layers:
graph TB
subgraph "Applications"
LAPP["Applications<br/>(OpenGL/Vulkan API calls via Mesa)"]
end
subgraph "Mesa / GPU Drivers"
MESA["Gallium3D state tracker / GPU-specific driver<br/>(radeonsi, i915, nouveau, lima, freedreno, ...)<br/>Translates GL/Vulkan to GPU command streams"]
end
subgraph "Wayland Compositor (display server)"
WCOMP["Weston / KWin / Mutter / sway / labwc / ...<br/>Manages windows, input, display, animation<br/>Uses KMS/DRM for display output<br/>Uses GBM for buffer allocation<br/>Composites client buffers (DMA-BUF) onto screen"]
end
subgraph "Linux Kernel — DRM/KMS Subsystem"
DRM["DRM<br/>(GPU commands)"]
KMS["KMS<br/>(display control)"]
GEM["GEM<br/>(buffer mgmt)"]
SYNC["sync_file<br/>(explicit sync fences)"]
end
LAPP --> MESA
MESA --> WCOMP
WCOMP --> DRM
WCOMP --> KMS
WCOMP --> GEM
WCOMP --> SYNC
Key properties:
- Mesa is the open-source GL/Vulkan implementation; vendor drivers (NVIDIA binary, etc.) can replace it
- GBM (Generic Buffer Management) is Mesa’s buffer allocator, backed by DRM GEM
- DMA-BUF (file descriptor) is the inter-process buffer sharing mechanism
- Wayland compositor is both display server and window manager
- X11: legacy; XWayland provides compatibility via Wayland protocol
3.2 Android Graphics Stack Architecture
graph TB
subgraph "Applications"
AAPP["Applications<br/>(OpenGL ES / Vulkan via vendor drivers + HWUI/Skia)"]
end
subgraph "HWUI + Skia"
HWUI["SkiaOpenGLPipeline / SkiaVulkanPipeline<br/>RenderNode / DisplayList recording and replay<br/>Translates View drawing to GPU command streams"]
end
subgraph "SurfaceFlinger (display server + compositor)"
SF_A["Manages layers (SurfaceControl), composites<br/>Uses RenderEngine (Skia GL/Vulkan) for GPU layers<br/>Calls HWC for hardware overlay planes"]
end
subgraph "HWComposer HAL"
HWC_A["Vendor implementation (or DRM-based open impl)<br/>validate / present (atomic commit)<br/>DEVICE layers: hardware overlay planes<br/>CLIENT layers: GPU composited framebuffer"]
end
subgraph "Linux Kernel — DRM/KMS (same as Linux stack)"
ADRM["DRM<br/>(GPU)"]
AKMS["KMS<br/>(display)"]
AION["ION / DMA-BUF"]
ASYNC["sync_file<br/>(fences)"]
end
AAPP --> HWUI
HWUI --> SF_A
SF_A --> HWC_A
HWC_A --> ADRM
HWC_A --> AKMS
HWC_A --> AION
HWC_A --> ASYNC
3.3 Side-by-Side Comparison
| Aspect | Linux (Wayland) | Android |
|---|---|---|
| GPU driver | Mesa (open-source) + vendor binary | Vendor binary only (Adreno, Mali, etc.) |
| GL/Vulkan implementation | Mesa / libGL / libvulkan | libEGL.so + libGLESv2.so (vendor) via HWUI/Skia |
| Buffer allocator | GBM (libgbm) |
Gralloc HAL (IAllocator) |
| Buffer handle | gbm_bo* → DMA-BUF fd |
native_handle_t → DMA-BUF fd |
| Display control | KMS via libdrm directly | HWC2 HAL (often DRM-backed) |
| Compositor | Wayland compositor (user-chosen) | SurfaceFlinger (fixed) |
| Window manager | Compositor implements WM | WindowManagerService (separate) |
| Buffer sharing | DMA-BUF fd passed via Wayland protocol | native_handle_t fd via Binder |
| Sync | sync_file / dma_fence |
Fence wrapping sync_file |
| Vsync | drmWaitVblank / EGL_KHR_swap_buffers_with_damage |
Choreographer / HWC vsync callback |
| Kernel DRM | Direct (compositor opens /dev/dri/card*) |
Via HWC HAL (indirect for most devices) |
| Shader compilation | Mesa/LLVM at runtime or AOT | Vendor-specific + Skia ShaderCache |
| Inter-process compositing | Wayland protocol (wl_surface) | Binder + SurfaceControl |
| Input system | evdev / libinput → compositor | InputDispatcher → InputChannel (Binder) |
| Open-source driver | Yes (Mesa for AMD, Intel, ARM Mali) | drm_hwcomposer (open HWC2 on DRM) |
3.4 Key Architectural Differences
1. Monolithic vs Layered Compositor
- Linux/Wayland: The Wayland compositor is an all-in-one component. It reads from evdev (input), sets up KMS (display), composites client surfaces, and sends vsync signals. The compositor IS the window manager.
- Android: SurfaceFlinger (compositor) and WindowManagerService (window manager) are separate components. SurfaceFlinger composes surfaces; WMS manages window policy, focus, and layout. They share state via
SurfaceControlreferences.
2. Protocol vs Binder
- Linux/Wayland: Clients communicate with compositor via the Wayland socket protocol (
wl_display,wl_surface, etc.) — a Unix domain socket with shared memory for buffers. - Android: Clients communicate with SurfaceFlinger and WMS via Binder IPC.
SurfaceControlobjects are Binder-referenced; buffer handles are transmitted as Binder FDs.
3. GPU Driver Access
- Linux/Wayland: Clients can directly open
/dev/dri/renderD128for GPU rendering without compositor involvement. The compositor only sees the output buffer. - Android: Apps do not directly access DRM devices. GPU rendering goes through HWUI → vendor EGL/GL library → vendor kernel driver. Direct DRM access is reserved for HWC HAL implementations.
4. Buffer Protocol
- Wayland:
wl_bufferwraps a DMA-BUF fd passed directly via the Wayland socket. Zero copy: client renders into a GBM buffer, hands fd to compositor. - Android:
BufferQueueis a circular buffer ofGraphicBufferslots. Producer and consumer share aBufferQueuevia Binder references. Buffer handles (native_handle_twith DMA-BUF fds) are transferred via Binder fd passing.
5. Open-source Driver Situation
- Linux: Mesa provides open-source drivers for AMD (radeonsi), Intel (iris/crocus), ARM Mali (panfrost), Qualcomm (freedreno), etc. Fully mainline.
- Android: GPU drivers are vendor binaries.
drm_hwcomposerprovides an open-source HWC2 implementation for DRM-capable hardware (used in AOSP emulator and some development boards). Google Pixel 5 and earlier (Qualcomm Snapdragon) used proprietary Adreno drivers; Pixel 6+ (Google Tensor/Samsung Exynos) use ARM Mali drivers.
Reference files:
device/generic/goldfish/hals/hwc3/DrmClient.cpp— Android DRM usageframeworks/native/services/surfaceflinger/DisplayHardware/HWC2.cpp— HWC2 compositionframeworks/base/libs/hwui/pipeline/skia/— HWUI rendering pipelines- Graphics Architecture — AOSP Docs
- SurfaceFlinger and WM — AOSP Docs
- Running Android on mainline graphics — LWN
- Android enabling mainline graphics — Collabora
- Wayland Architecture
- DRM — Wikipedia
- The Linux Graphics Stack — blog.mecheye.net
4. Display Servers: Android SurfaceFlinger vs Linux Wayland/X11
A display server mediates between client applications and display hardware — receiving rendering output from clients, compositing it, and sending the result to the physical display. It also routes input events back to clients.
4.1 X11: The Legacy Display Server
X11 (X Window System) follows a client-server model where:
- The X server owns display hardware and input devices
- Clients connect over a network socket and request the server to draw
- The server does all rendering (or delegates via DRI to clients)
- A window manager is a separate X client that controls window layout
Key limitations of X11:
- Legacy rendering requests flow through the X server CPU; modern X11 uses DRI/DRI2/DRI3 for direct client GPU access, but the protocol overhead remains
- Network transparency was a goal but creates latency
- Security: any X client can read any other client’s window content (no isolation)
- Compositing requires a separate compositor extension (Composite/AIGLX), layered on top
4.2 Wayland: The Modern Linux Display Server
In Wayland, the compositor IS the display server:
- No separate server binary; each compositor implementation (Weston, KWin, Mutter, sway) is a standalone process
- Clients render directly to GPU memory (GBM buffer) and hand a DMA-BUF fd to the compositor
- Compositor textures from the DMA-BUF fd → zero copy
- Compositor owns KMS/evdev directly
graph LR
subgraph "X11 Flow"
X_APP[App] -->|"X socket"| X_SRV[X Server]
X_SRV -->|"draws to FB"| X_COMP[Compositor]
X_COMP -->|"textures X FB"| X_OUT[Display]
end
subgraph "Wayland Flow"
W_APP[App] -->|"render to GBM buffer"| W_SURF["wl_surface.attach(wl_buffer)"]
W_SURF -->|"textures DMA-BUF"| W_COMP[Compositor]
W_COMP -->|"KMS atomic commit"| W_OUT[Display]
end
Source: Wayland Architecture
“In Wayland, the compositor is the display server. We transfer control of KMS and evdev to the Wayland compositor. The compositor is in a better position to make decisions about things like which application should receive input events and how to route them.”
4.3 SurfaceFlinger: Android’s Display Server
SurfaceFlinger is Android’s compositor and display server, analogous to a Wayland compositor. It combines compositing, display management, and vsync synchronization.
Source: frameworks/native/services/surfaceflinger/SurfaceFlinger.h
Source: SurfaceFlinger and WindowManager — AOSP Docs
graph TB
APPS["Applications<br/>render to Surface (backed by BufferQueue)"]
SF_B["SurfaceFlinger<br/>Receives SurfaceControl layer updates<br/>Acquires rendered buffers from producers<br/>Calls HWC for layer assignment<br/>GPU-composites CLIENT layers<br/>Submits final frame to HWC present()<br/>Returns VSYNC signal to Choreographer"]
HWC_B["HWComposer / DRM/KMS Display"]
APPS -->|"BufferQueue + SurfaceControl"| SF_B
SF_B -->|"HWC HAL (AIDL/HIDL)"| HWC_B
4.4 Comparison: SurfaceFlinger vs Wayland Compositor vs X11 Server
| Feature | X11 Server | Wayland Compositor | Android SurfaceFlinger |
|---|---|---|---|
| Architecture | Centralized server | Decentralized (each impl is standalone) | Separate native process (/system/bin/surfaceflinger) |
| Window management | Separate WM process | WM is part of compositor | Separate WMS process (WindowManagerService) |
| Buffer transfer | X protocol (CPU copy) | DMA-BUF fd via Wayland socket (zero copy) | BufferQueue via Binder (zero copy) |
| GPU access by clients | Via DRI (direct rendering, bypasses server) | Direct (client renders to GBM buffer) | Via HWUI → vendor EGL (no direct DRM) |
| Input routing | Through X server | Through compositor | Through InputDispatcher (separate) |
| Security isolation | Low (any client reads any window) | High (compositor mediates all access) | High (Binder permission model) |
| Display control | X server owns display | Compositor owns KMS/evdev | HWC2 HAL (not SurfaceFlinger directly) |
| Vsync source | X server | Compositor via DRM vblank | HWC2 vsync callback → Choreographer |
| Compositing | Optional extension (AIGLX/Composite) | Built-in (compositor = comping) | Built-in (SurfaceFlinger always composites) |
| Network transparency | Yes (design goal) | No (local only) | No (Binder is local IPC) |
| Multiple screens | Xinerama / RandR | Multiple outputs per compositor | Multi-display via DisplayManagerService |
| Open protocol | X11 protocol (standardized) | Wayland protocol (extensible) | Proprietary (Binder + SurfaceControl) |
| Vendor flexibility | Any WM runs under any X server | Any compositor on any display hardware | Fixed SF + WMS; HWC2 is vendor-customized |
4.5 Key Structural Similarity: Android ≈ Wayland Model
Despite different protocols, Android and Wayland are architecturally similar:
-
Clients own their buffers: Both Android apps (via
BufferQueue/Surface) and Wayland clients (via GBM) allocate and render into their own buffers. The compositor never touches client-rendered pixel data directly — it only textures from it. -
Zero-copy compositing: Both use kernel buffer-sharing primitives (DMA-BUF fd in Wayland,
native_handle_t/DMA-BUF in Android) to share buffers with the compositor without pixel copies. -
Compositor owns display: Both SurfaceFlinger and Wayland compositors control the actual display hardware (KMS in Linux; HWC2 in Android).
-
Separate input routing: Both systems route input through a privileged intermediary (Wayland compositor owns evdev; Android
InputDispatcherdistributes to apps).
Key structural difference: Android splits compositor (SurfaceFlinger) and window manager (WMS) into separate system services with stable AIDL interfaces between them. Wayland compositors are monolithic — window management is embedded in the compositor.
4.6 XWayland — The X11 Compatibility Layer
Linux provides XWayland to run legacy X11 applications under Wayland. XWayland is an X server that speaks Wayland to the compositor:
- X11 clients connect to XWayland as normal
- XWayland renders their windows and forwards them to the Wayland compositor as
wl_surfaces
Android has no equivalent X11 compatibility layer (apps target Android APIs directly), but some Android-x86 projects have experimented with running X apps via XWayland on top of Android’s SurfaceFlinger.
Reference files:
frameworks/native/services/surfaceflinger/SurfaceFlinger.h— main compositorframeworks/native/services/surfaceflinger/DisplayHardware/HWC2.cpp— display submission- SurfaceFlinger and WindowManager — AOSP Docs
- Wayland Architecture
- High-level Wayland design
- DRM/KMS overview — ST Wiki
- What does EGL do in the Wayland stack — ppaalanen
- Linux Graphics Stack overview — blog.mecheye.net
- Running Android on mainline graphics stack — LWN
- Android-x86 graphics stack evolution — XDC 2018
Part II: Core Objects — Activity, Window, and Task
5. Activity and Window Relationship
Understanding the relationship between Activity and Window is fundamental to the entire window system. An Activity is the application-level unit of UI, while Window/WindowState is the system-level unit of display surface management. Their connection spans two processes and multiple abstraction layers.
5.1 Client-Side: Activity → PhoneWindow → DecorView → ViewRootImpl
Each Activity owns exactly one Window object (concrete class PhoneWindow), created during Activity.attach():
graph LR
subgraph "Application Process"
A[Activity] -->|mWindow| PW[PhoneWindow]
PW -->|mDecor| DV[DecorView<br/>FrameLayout]
DV -->|child| CP[mContentParent<br/>App Layout]
DV -.->|addView| WMG[WindowManagerGlobal<br/>Singleton]
WMG -->|creates| VRI[ViewRootImpl<br/>Per window]
end
subgraph "System Server Process"
VRI -->|IWindowSession| SS[Session<br/>Per client process]
SS -->|addWindow| WMS[WindowManagerService]
WMS -->|creates| WS[WindowState]
end
Key classes and their roles:
| Class | File | Role |
|---|---|---|
Activity |
core/java/android/app/Activity.java |
Application UI unit; owns PhoneWindow, delegates setContentView() |
Window |
core/java/android/view/Window.java |
Abstract window; defines policies (features, flags, callbacks) |
PhoneWindow |
core/java/com/android/internal/policy/PhoneWindow.java |
Concrete Window; creates DecorView, inflates content layout |
DecorView |
core/java/com/android/internal/policy/DecorView.java |
Root FrameLayout; contains action bar area + content area |
ViewRootImpl |
core/java/android/view/ViewRootImpl.java |
IPC bridge to server; manages layout, draw, input, surface |
WindowManagerGlobal |
core/java/android/view/WindowManagerGlobal.java |
Singleton; manages all ViewRootImpl instances per process |
Session |
services/core/.../wm/Session.java |
Server-side IPC endpoint; one per client process |
Connection flow:
Activity.attach()createsPhoneWindow→ stores inmWindowActivity.onCreate()callssetContentView()→PhoneWindow.setContentView()inflates layout intoDecorView.mContentParentActivity.onResume()→WindowManagerGlobal.addView(decorView, params)→ createsViewRootImplViewRootImplopensIWindowSessionto server →Session.addToDisplay()→WindowManagerService.addWindow()- Server creates
WindowState, attaches toActivityRecord(which extendsWindowToken)
5.2 Server-Side: ActivityRecord → WindowToken → WindowState
On the server side, ActivityRecord extends WindowToken, which extends WindowContainer<WindowState>. This inheritance makes ActivityRecord itself the container for all windows belonging to that Activity.
classDiagram
class WindowContainer~E~ {
+mChildren : ArrayList~E~
+mSurfaceControl : SurfaceControl
+addChild(E child)
+removeChild(E child)
}
class WindowToken {
+token : IBinder
+windowType : int
}
class ActivityRecord {
+mStartingWindow : WindowState
+mInputApplicationHandle
+findMainWindow() WindowState
+addWindow(WindowState)
}
class WindowState {
+mActivityRecord : ActivityRecord
+mAttrs : LayoutParams
+mSurfaceControl : SurfaceControl
+getActivityRecord() ActivityRecord
}
WindowContainer <|-- WindowToken : extends
WindowToken <|-- ActivityRecord : extends
ActivityRecord *-- WindowState : contains (mChildren)
WindowState --> ActivityRecord : mActivityRecord
Bidirectional linkage:
ActivityRecordcontainsWindowStateobjects as children (inherited fromWindowContainer)WindowState.mActivityRecordholds a back-reference to its owningActivityRecordActivityRecord.findMainWindow()searches its children for the primary application window (typeTYPE_BASE_APPLICATIONorTYPE_APPLICATION_STARTING)
An Activity can own multiple WindowState objects:
- Main application window (
TYPE_BASE_APPLICATION) - Starting/splash window (
mStartingWindow, typeTYPE_APPLICATION_STARTING) - Sub-windows: dialogs, popups, menus
- Overlay windows:
TYPE_APPLICATION_OVERLAY
5.3 Complete Hierarchy: From Activity to SurfaceFlinger
The full server-side container hierarchy positions ActivityRecord within the task/display tree:
graph TB
RWC["RootWindowContainer"]
DC["DisplayContent<br/>(extends RootDisplayArea)"]
TDA["TaskDisplayArea"]
TASK["Task<br/>(extends TaskFragment)"]
TF["TaskFragment<br/>(for embedded activities)"]
AR["ActivityRecord<br/>(extends WindowToken)"]
WS1["WindowState (main app window)"]
WS2["WindowState (starting window)"]
WS3["WindowState (sub-windows)"]
RWC --> DC
DC --> TDA
TDA --> TASK
TASK --> TF
TF --> AR
AR --> WS1
AR --> WS2
AR --> WS3
Every node in this hierarchy has a corresponding SurfaceControl, creating a parallel surface tree that SurfaceFlinger composites. When an Activity’s window changes (resize, move, z-order), the change propagates through WindowContainerTransaction → SurfaceControl.Transaction → SurfaceFlinger.
5.4 Window Types and Activity
Windows are classified by type constants that determine their z-ordering layer:
| Constant | Value | Description |
|---|---|---|
TYPE_BASE_APPLICATION |
1 | Base app window (first window Activity creates) |
TYPE_APPLICATION |
2 | Normal app window |
TYPE_APPLICATION_STARTING |
3 | Splash/starting window (shown before Activity draws) |
TYPE_APPLICATION_MEDIA |
— | Media playback surface below app |
TYPE_APPLICATION_MEDIA_OVERLAY |
— | Overlay above media but below app |
TYPE_APPLICATION_OVERLAY |
2038 | Floating window drawn over other apps |
Reference files:
frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java— Server-side activity state and window managementframeworks/base/services/core/java/com/android/server/wm/WindowState.java— Individual window state trackingframeworks/base/core/java/com/android/internal/policy/PhoneWindow.java— Default Window implementation for activitiesframeworks/base/core/java/android/view/ViewRootImpl.java— Client-side window management and rendering rootframeworks/base/core/java/android/view/WindowManagerImpl.java— Client-side WindowManager APIframeworks/base/core/java/android/app/Activity.java— Activity lifecycle and window attachment
6. Task — The Central Connecting Concept
Task is the most pivotal class in Android’s window management architecture. It is the structural unit that ties together Activities (app logic), Windows (rendering surfaces), and Displays (physical hardware). Every Activity lives inside a Task; every Task owns a SurfaceControl in the compositor; every Task is anchored to a specific DisplayContent. Understanding Task is understanding how Android organizes everything.
6.1 Class Hierarchy
classDiagram
ConfigurationContainer~E~ <|-- WindowContainer~E~
WindowContainer <|-- TaskFragment
TaskFragment <|-- Task
Task (services/core/.../wm/Task.java) inherits:
- From
TaskFragment: Ability to hold Activities or other Tasks; organizer binding; embedded bounds - From
WindowContainer:mSurfaceControl,mChildren,mDisplayContent, animation leash, z-order, configuration propagation - From
ConfigurationContainer:Configurationinheritance and override mechanism
Critical design choice: Task extends TaskFragment — not the reverse. TaskFragment is the general embedded-activity container; Task specializes it into a persistent, identifiable, user-visible unit shown in Recents.
Task also has its own subtype: every Task can act as a root Task (windowing-mode container anchored to a TaskDisplayArea) or a leaf Task (directly containing ActivityRecord children). The same class handles both roles dynamically.
6.2 Root Task vs. Leaf Task
| Property | Root Task | Leaf Task |
|---|---|---|
isRootTask() |
true (getRootTask() == this) |
false |
isLeafTask() |
false (has Task children) |
true (no Task children) |
| Parent | TaskDisplayArea |
Root Task (or intermediate Task) |
| Children | Other Tasks | ActivityRecord objects |
| Role | Windowing mode boundary | Logical activity group |
graph TD
TDA[TaskDisplayArea] --> RT1[Root Task<br/>WINDOWING_MODE_FULLSCREEN]
TDA --> RT2[Root Task<br/>WINDOWING_MODE_FREEFORM]
TDA --> RT3[Root Task<br/>WINDOWING_MODE_PINNED]
RT1 --> LT1[Leaf Task #101<br/>com.example.app]
RT1 --> LT2[Leaf Task #102<br/>com.other.app]
LT1 --> AR1[ActivityRecord<br/>MainActivity]
LT1 --> AR2[ActivityRecord<br/>DetailActivity]
RT2 --> LT3[Leaf Task #103<br/>Freeform app]
LT3 --> AR3[ActivityRecord]
isLeafTask() implementation:
boolean isLeafTask() {
for (int i = mChildren.size() - 1; i >= 0; --i) {
if (mChildren.get(i).asTask() != null) return false; // has Task child
}
return true; // only ActivityRecords/TaskFragments
}
6.3 Key Identity Fields
| Field | Type | Purpose |
|---|---|---|
mTaskId |
int |
Globally unique identifier; persisted across reboots |
affinity |
String |
Groups activities (default: app package name) |
rootAffinity |
String |
Immutable affinity set at creation |
mWindowingMode |
int |
WINDOWING_MODE_FULLSCREEN/FREEFORM/PINNED/MULTI_WINDOW |
effectiveUid |
int |
UID owning this task |
mUserId |
int |
User account the task belongs to |
inRecents |
boolean |
Whether task appears in the Recents list |
mTaskDescription |
TaskDescription |
Icon, label, color shown in Recents |
isPersistable |
boolean |
Whether task is saved to disk |
6.4 Task as the Activity Container
Every ActivityRecord lives inside a Task. The binding is established when the Activity is launched and maintained through onParentChanged():
sequenceDiagram
participant AS as ActivityStarter
participant Task as Task
participant AR as ActivityRecord
participant WS as WindowState
AS->>AS: computeTargetTask() → which Task?
AS->>Task: addChild(activityRecord)
Task->>AR: onParentChanged(newParent=this)
AR->>AR: this.task = newParent.getTask()
AR->>AR: setInitialBoundsIfNeeded()
Note over AR: Activity now knows its Task
AR->>WS: (later) ViewRootImpl adds window
WS->>WS: mActivityRecord = token.asActivityRecord()
WS->>WS: getTask() = mActivityRecord.task
Launch mode determines Task assignment (ActivityInfo.java):
| Launch Mode | Value | Task Behavior |
|---|---|---|
LAUNCH_MULTIPLE |
0 | New instance in source task (default) |
LAUNCH_SINGLE_TOP |
1 | Reuse if Activity is at top of its task |
LAUNCH_SINGLE_TASK |
2 | One instance per task; reuse existing task |
LAUNCH_SINGLE_INSTANCE |
3 | Isolated dedicated task; no other Activities |
LAUNCH_SINGLE_INSTANCE_PER_TASK |
4 | One instance, must be task root Activity |
Affinity (taskAffinity in AndroidManifest.xml) controls which existing task an Activity joins by default. Activities with the same affinity group together. When FLAG_ACTIVITY_NEW_TASK is set, a new Task is created if no matching-affinity task exists.
ActivityStarter.computeTargetTask() logic:
- If
resultTo == null && mInTask == null && !mAddingToTask && FLAG_ACTIVITY_NEW_TASK→ return null (force new task) - If source Activity exists (
mSourceRecord != null) → returnsourceRecord.getTask() - If
mInTaskis set (explicit task target) → returnmInTask - Otherwise →
getOrCreateRootTask()then return top activity’s task
6.5 Task as the Window Organizer Unit
Every Task owns a SurfaceControl — its own layer in SurfaceFlinger’s compositor tree. This is the foundation of the leash pattern used by ShellTaskOrganizer:
graph TB
subgraph "SurfaceFlinger Layer Tree"
DC[DisplayContent Surface] --> TDA_S[TaskDisplayArea Surface]
TDA_S --> RT_S[Root Task Surface]
RT_S --> LT_S[Leaf Task Surface<br/>mSurfaceControl]
LT_S --> AR_S[ActivityRecord Surface<br/>WindowToken]
AR_S --> WS_S[WindowState Surface<br/>app window]
end
subgraph "Shell via TaskOrganizer"
STO[ShellTaskOrganizer] -->|receives leash| LEASH[Leash SurfaceControl<br/>child of Task surface]
LEASH -.->|animates transforms| LT_S
end
When a Task gets a TaskOrganizer (e.g., Shell’s ShellTaskOrganizer):
TaskOrganizerController.prepareLeash(task)creates a childSurfaceControlof the Task’s surfaceITaskOrganizer.onTaskAppeared(RunningTaskInfo, leash)delivers this leash to Shell- Shell applies position, scale, alpha to the leash — animating the entire Task without touching individual windows
Task surface methods:
makeSurface()— inherited fromWindowContainer, creates the Task’s own SurfaceControl. The innerDecorSurfaceContainerclass separately managesmContainerSurface(layer container) +mDecorSurface(TaskFragment decoration dividers)updateSurfaceSize(t)— appliessetWindowCrop()to constrain rendering to task boundsassignChildLayers(t)— manages z-ordering of child TaskFragments and Activities within the TaskreparentSurfaceControl(t, newParent)— moves Task’s surface to new display when reparented
6.6 Task as the Display Unit
Task connects to Display through the TaskDisplayArea → DisplayContent chain:
graph LR
RWC[RootWindowContainer] --> DC0[DisplayContent<br/>Display 0 Primary]
RWC --> DC1[DisplayContent<br/>Display 1 External]
DC0 --> TDA0[TaskDisplayArea]
DC1 --> TDA1[TaskDisplayArea]
TDA0 --> RT[Root Task]
RT --> LT[Leaf Task]
LT -.->|mDisplayContent| DC0
LT -.->|getDisplayId| 0
The mDisplayContent field (inherited from WindowContainer) is set automatically whenever a Task is added to or reparented within a display hierarchy. Key methods:
| Method | Returns | Purpose |
|---|---|---|
getDisplayContent() |
DisplayContent |
The display this task is on |
getDisplayId() |
int |
Numeric display ID |
getDisplayArea() |
TaskDisplayArea |
The task display area container |
canBeLaunchedOnDisplay(int) |
boolean |
Validate display compatibility |
onDisplayChanged(DisplayContent) |
void |
Called when display changes; broadcasts to listeners |
When a Task moves between displays, onDisplayChanged() fires, updates mDisplayContent, then notifies ITaskStackListener via notifyTaskDisplayChanged(taskId, newDisplayId) and Shell via dispatchTaskInfoChangedIfNeeded().
6.7 TaskInfo — The Public State Snapshot
TaskInfo (and its subclass RunningTaskInfo) is the read-only snapshot of Task state delivered to Shell and clients:
public class TaskInfo {
public int taskId;
public int displayId; // Which display
public int displayAreaFeatureId; // Which TaskDisplayArea feature
public Configuration configuration; // Bounds, windowingMode, activityType
public WindowContainerToken token; // Handle for WCT operations
public Intent baseIntent;
public ComponentName topActivity;
public TaskDescription taskDescription; // Recents icon/label/color
public boolean isFocused;
public boolean isVisible;
public boolean isVisibleRequested;
public int userId;
}
Task.fillTaskInfo() populates all these fields each time something changes. dispatchTaskInfoChangedIfNeeded() compares the new snapshot against the last-sent one and only dispatches if there’s a real difference.
6.8 Task Lifecycle and Recents
stateDiagram-v2
[*] --> Created: ActivityStarter creates Task
Created --> Running: First Activity added
Running --> Focused: Task brought to front
Focused --> Running: Another task gains focus
Running --> InRecents: Task goes to background
InRecents --> Running: User returns to task
InRecents --> Snapshot: TaskSnapshotController captures thumbnail
Snapshot --> InRecents: Thumbnail stored for Recents UI
InRecents --> Destroyed: Trim due to memory / user removes
Destroyed --> [*]
Persistence: If isPersistable == true, Task.saveToXml() serializes the task and all its ActivityRecord children to disk. On boot, ActivityTaskManagerService restores persisted tasks via restoreFromXml(), recreating the full hierarchy without relaunching activities.
Recents: RecentTasks (services/core/.../wm/RecentTasks.java) maintains an ArrayList<Task> ordered most-recent first. isVisibleRecentTask() filters by activity type (excludes HOME, RECENTS, ASSISTANT tasks). TaskSnapshotController captures task thumbnails used as previews in the Recents UI.
6.9 Complete Connection Map
graph TB
subgraph "Activity Layer"
AI[ActivityInfo<br/>launchMode, taskAffinity] --> AS[ActivityStarter<br/>computeTargetTask]
AS -->|addChild| AR["ActivityRecord<br/>task = this.getTask()"]
AR -->|WindowToken| WS["WindowState<br/>getTask = ar.task"]
end
subgraph "Task Layer"
TASK[Task<br/>mTaskId, affinity,<br/>mWindowingMode,<br/>mSurfaceControl,<br/>mDisplayContent]
AR -->|onParentChanged| TASK
WS -->|mActivityRecord.task| TASK
end
subgraph "Display Layer"
TDA[TaskDisplayArea<br/>windowing mode buckets]
DC[DisplayContent<br/>Physical display]
TDA --> TASK
TASK -.->|mDisplayContent| DC
end
subgraph "Shell Layer"
STO[ShellTaskOrganizer<br/>TaskListener per mode]
TASK -->|onTaskAppeared leash| STO
STO -->|WCT reparent/setBounds| TASK
end
subgraph "Compositor Layer"
SC[SurfaceControl<br/>mSurfaceControl]
TASK --> SC
SC -->|child surfaces| SF[SurfaceFlinger]
end
subgraph "Recents Layer"
RT[RecentTasks<br/>ArrayList of Tasks]
TSC[TaskSnapshotController<br/>thumbnails]
TASK -.->|inRecents=true| RT
TASK -.->|snapshot on background| TSC
end
6.10 Key Files
| File | Role |
|---|---|
services/core/.../wm/Task.java |
Core Task implementation |
services/core/.../wm/TaskFragment.java |
TaskFragment base (Task extends this) |
services/core/.../wm/TaskDisplayArea.java |
Display-level task container |
services/core/.../wm/ActivityRecord.java |
Activity; holds task field |
services/core/.../wm/ActivityStarter.java |
Task assignment at launch |
services/core/.../wm/RecentTasks.java |
Recents list management |
services/core/.../wm/TaskSnapshotController.java |
Thumbnail capture for Recents |
services/core/.../wm/TaskOrganizerController.java |
Leash creation; organizer callbacks |
core/java/android/app/TaskInfo.java |
Public Task state snapshot |
core/java/android/content/pm/ActivityInfo.java |
LAUNCH_* constants, taskAffinity |
Shell/.../ShellTaskOrganizer.java |
Shell-side Task reception and routing |
Reference files:
frameworks/base/services/core/java/com/android/server/wm/Task.java— Task container implementationframeworks/base/services/core/java/com/android/server/wm/TaskFragment.java— Task fragment for activity embeddingframeworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java— Root of the window hierarchyframeworks/base/services/core/java/com/android/server/wm/WindowContainer.java— Base class for all window containers
Part III: Window Management System
7. WM Core (Server-Side)
7.1 WindowContainer Hierarchy
The entire window system is built on a generic tree structure rooted in WindowContainer<E>, providing unified parent-child relationships, configuration propagation, and surface management.
graph TD
RWC[RootWindowContainer] --> DC1["DisplayContent (extends RootDisplayArea)<br/>Display 0"]
RWC --> DC2["DisplayContent (extends RootDisplayArea)<br/>Display 1"]
DC1 --> DA_BELOW[DisplayArea<br/>BELOW_TASKS]
DC1 --> TDA[TaskDisplayArea]
DC1 --> DA_ABOVE[DisplayArea<br/>ABOVE_TASKS]
DA_BELOW --> WT_WALL[WindowToken<br/>Wallpaper]
WT_WALL --> WS_WALL[WindowState]
TDA --> T1[Task<br/>Home]
TDA --> T2[Task<br/>App Stack]
TDA --> T3[Task<br/>PiP]
T2 --> TF1[TaskFragment]
T2 --> TF2[TaskFragment]
TF1 --> AR1[ActivityRecord<br/>Activity A]
TF2 --> AR2[ActivityRecord<br/>Activity B]
AR1 --> WS1[WindowState<br/>Main Window]
AR1 --> WS2[WindowState<br/>Dialog]
AR2 --> WS3[WindowState<br/>Main Window]
DA_ABOVE --> WT_STATUS[WindowToken<br/>Status Bar]
DA_ABOVE --> WT_NAV[WindowToken<br/>Navigation Bar]
WT_STATUS --> WS_STATUS[WindowState]
WT_NAV --> WS_NAV[WindowState]
style RWC fill:#e1f5fe
style DC1 fill:#b3e5fc
style TDA fill:#81d4fa
style T2 fill:#4fc3f7
style TF1 fill:#29b6f6
style TF2 fill:#29b6f6
style AR1 fill:#03a9f4
style AR2 fill:#03a9f4
7.2 Core Classes
WindowManagerService (WindowManagerService.java)
Central system service implementing IWindowManager.Stub. Manages all windows, displays, input coordination, and surface management via SurfaceControl.
Key responsibilities:
- Window addition, removal, and layout computation
- Display management and multi-display support
- Input event dispatch coordination
- Frame synchronization via BLAST (Binary-Layer-Synchronized Transactions)
- Keyguard and focus management
ActivityTaskManagerService (ActivityTaskManagerService.java)
Implements IActivityTaskManager.Stub. Manages activity/task lifecycle, task stacks, and activity state transitions (RESUMED, PAUSED, STOPPED, FINISHING).
RootWindowContainer (RootWindowContainer.java)
Root of the hierarchy. Holds all DisplayContent instances and coordinates multi-display focus, visibility, and keyguard control.
WindowContainer (WindowContainer.java)
Generic base class WindowContainer<E extends WindowContainer> providing:
- Parent-child relationships and tree traversal
- SurfaceControl lifecycle management
- Configuration propagation down the tree
- Animation and transition support via
Animatableinterface
DisplayContent (DisplayContent.java)
Extends RootDisplayArea. Represents a single physical or virtual display with complete display metrics, rotation, cutout handling, and the full DisplayArea hierarchy beneath it. Maintains 1:1 correspondence with a LogicalDisplay from DisplayManagerService.
Task (Task.java)
Extends TaskFragment. Container for activities in a logical group with windowing mode configuration (FULLSCREEN, PINNED, FREEFORM), activity type tracking, bounds management, and multi-window support.
TaskFragment (TaskFragment.java)
Architectural pattern for embedding and organizing activities within tasks. Enables activity embedding (side-by-side activities within a single task), companion fragment relationships, and isolated navigation.
ActivityRecord (ActivityRecord.java)
Represents a single activity instance with full lifecycle state management, window token ownership, configuration state, and app compatibility/letterboxing support.
WindowState (WindowState.java)
Represents an individual window surface. Manages LayoutParams, SurfaceControl, window framing/insets, input handling, z-ordering, and visibility state.
7.3 Organizer APIs (Server-Shell Bridge)
The Organizer pattern is the primary mechanism by which Shell controls window layout:
sequenceDiagram
participant Shell as WM Shell
participant WOC as WindowOrganizerController
participant TC as TaskOrganizerController
participant Task as Task
Shell->>TC: registerTaskOrganizer()
Note over TC: Shell now receives all<br/>task lifecycle events
Task->>TC: onTaskAppeared(taskInfo, leash)
TC->>Shell: onTaskAppeared(RunningTaskInfo, SurfaceControl)
Shell->>WOC: applyTransaction(WindowContainerTransaction)
Note over WOC: Atomic batch of operations:<br/>setBounds, setHidden,<br/>reorder, reparent
WOC->>Task: Apply changes atomically
Task->>TC: onTaskInfoChanged(taskInfo)
TC->>Shell: onTaskInfoChanged(RunningTaskInfo)
WindowContainerTransaction (WindowContainerTransaction.java) enables atomic batch operations:
- Change objects: Bounds, visibility, focusable, configuration overrides
- HierarchyOp objects: Reparent, reorder, create, delete containers
- TaskFragmentOperation objects: Fragment-specific operations (create, reparent activities)
Reference files:
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java— Central WM serviceframeworks/base/services/core/java/com/android/server/wm/WindowContainer.java— Container hierarchy base classframeworks/base/services/core/java/com/android/server/wm/WindowToken.java— Window grouping tokenframeworks/base/services/core/java/com/android/server/wm/DisplayContent.java— Per-display window managementframeworks/base/services/core/java/com/android/server/wm/WindowOrganizerController.java— Organizer registration and dispatch
8. WM Shell Library
8.1 Shell Architecture
The Shell library (frameworks/base/libs/WindowManager/Shell/) contains ~674 source files (354 Java + 320 Kotlin) across 33 top-level packages (96 total directories including nested), implementing all surface-level window management features.
graph TB
subgraph "WM Shell Library"
subgraph "Core Infrastructure"
STO[ShellTaskOrganizer]
RDAO[RootDisplayAreaOrganizer]
RTDAO[RootTaskDisplayAreaOrganizer]
SC_CORE[ShellController]
SI_CORE[ShellInterface]
end
subgraph "Feature Modules"
SS[SplitScreen<br/>18 files]
PIP[PiP / PiP2<br/>70 files]
BUB[Bubbles<br/>77 files]
DM[DesktopMode<br/>76 files]
FF[Freeform<br/>7 files]
OH[OneHanded]
BK[Back Animation]
KG[Keyguard Transitions]
SW[Starting Window]
DND[Drag & Drop]
PL[Pinned Layer]
WD[Window Decoration<br/>99 files]
AE[Activity Embedding]
AZO[App Zoom Out]
ATW[App to Web]
end
subgraph "Transition System"
TRANS[Transitions<br/>Master Orchestrator]
DTH[DefaultTransitionHandler]
DMH[DefaultMixedHandler]
RTH[RemoteTransitionHandler]
end
subgraph "Infrastructure"
DAG[Dagger Modules]
COM[Common Utilities<br/>83 files]
PERF[Performance<br/>PerfHintController]
REPO[Repository<br/>State Management]
PLOG[ProtoLog]
end
end
STO --> SS
STO --> PIP
STO --> BUB
STO --> DM
STO --> FF
TRANS --> DTH
TRANS --> DMH
TRANS --> RTH
SS --> TRANS
PIP --> TRANS
DM --> TRANS
FF --> TRANS
BK --> TRANS
KG --> TRANS
8.2 ShellTaskOrganizer (ShellTaskOrganizer.java)
The unified task organizer serves as the single point of contact between WM Core and all Shell features. It dispatches task lifecycle events to appropriate feature listeners based on task type.
Listener Types (lines 95-108):
| Constant | Value | Description |
|———-|——-|————-|
| TASK_LISTENER_TYPE_FULLSCREEN | -2 | Fullscreen tasks |
| TASK_LISTENER_TYPE_MULTI_WINDOW | -3 | Split-screen tasks |
| TASK_LISTENER_TYPE_PIP | -4 | Picture-in-Picture tasks |
| TASK_LISTENER_TYPE_FREEFORM | -5 | Freeform/desktop tasks |
8.3 Feature Controller Pattern
All major features follow a consistent pattern:
classDiagram
class FeatureInterface {
<<interface>>
+publicMethod1()
+publicMethod2()
+registerCallback()
}
class FeatureController {
-ShellTaskOrganizer organizer
-Transitions transitions
-ShellExecutor mainExecutor
+init()
+onTaskAppeared()
+onTaskVanished()
}
class FeatureTransitions {
+startAnimation(TransitionInfo)
+mergeAnimation(TransitionInfo)
}
class FeatureAIDL {
<<aidl>>
+remoteMethod1()
+remoteMethod2()
}
class ExternalInterfaceBinder {
+asInterface() FeatureAIDL
}
FeatureInterface <|.. FeatureController
FeatureController --> FeatureTransitions
FeatureController --> ExternalInterfaceBinder
ExternalInterfaceBinder ..|> FeatureAIDL
Each feature:
- Implements a public interface (e.g.,
SplitScreen,Pip,Bubbles) - Has a controller managing core logic
- Has transition handlers for Shell-driven animations
- Exposes an AIDL-based external interface for cross-process access (e.g., Launcher3)
- Registers as a
TaskListenerwithShellTaskOrganizer
Reference files:
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java— Unified task organizerframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java— DI component interfaceframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java— Shell initialization lifecycleframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java— Shell event hubframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java— Transition orchestrator
9. SystemUI and Launcher3 Integration
The WM Shell library exists as a Dagger subcomponent within SystemUI’s process. Shell features run on their own thread(s) and communicate with SystemUI and Launcher3 through carefully designed interface boundaries that enforce thread safety.
9.1 Communication Architecture
graph TB
subgraph "SystemUI Process"
subgraph "SysUI Scope (main thread)"
WMShell["WMShell<br/>(CoreStartable)"]
SysUiState["SysUiState"]
CommandQueue["CommandQueue"]
end
subgraph "WM Shell Scope (shell thread)"
ShellController["ShellController"]
ShellInit["ShellInit"]
PipCtrl["PipController"]
SplitCtrl["SplitScreenController"]
RecentCtrl["RecentTasksController"]
TransCtrl["Transitions"]
end
end
subgraph "Launcher3 Process"
LauncherRecents["LauncherRecentsView"]
LauncherTransitions["LauncherTransitionRunner"]
end
WMShell -->|"ShellInterface<br/>(posts to shell thread)"| ShellController
ShellController -->|"createExternalInterfaces()<br/>Bundle of IBinders"| LauncherRecents
RecentCtrl -->|"IRecentTasks.Stub"| LauncherRecents
TransCtrl -->|"IShellTransitions.Stub"| LauncherTransitions
WMShell -->|"SysUiState flags"| SysUiState
9.2 ShellInterface Contract
ShellInterface is the primary boundary between SystemUI and the Shell. ShellController implements it via an inner ShellInterfaceImpl class annotated @ExternalThread, meaning every method posts work to the Shell main thread executor:
| ShellInterface Method | Direction | Purpose |
|---|---|---|
onInit() |
SysUI to Shell | Triggers ShellInit.init() on the shell thread |
onConfigurationChanged(Configuration) |
SysUI to Shell | Forwards config changes to ConfigurationChangeListeners |
onKeyguardVisibilityChanged(visible, occluded, animatingDismiss) |
SysUI to Shell | Notifies KeyguardChangeListeners |
onKeyguardDismissAnimationFinished() |
SysUI to Shell | Keyguard dismiss completion |
onUserChanged(userId, Context) |
SysUI to Shell | User switch propagation |
onUserProfilesChanged(List) |
SysUI to Shell | Profile changes |
createExternalInterfaces(Bundle) |
SysUI to Shell | Creates IBinders for Launcher (blocking, 2s timeout) |
handleCommand(String[], PrintWriter) |
SysUI to Shell | Shell command passthrough (blocking) |
dump(PrintWriter) |
SysUI to Shell | Dump shell state (blocking) |
addDisplayImeChangeListener(listener, executor) |
SysUI to Shell | IME visibility/bounds tracking |
9.3 Shell-to-Launcher3 AIDL Interfaces
Each Shell feature independently registers its external interface with ShellController.addExternalInterface() during initialization:
| AIDL Interface | Descriptor Key | Registering Controller | Purpose |
|---|---|---|---|
IRecentTasks |
IRecentTasks.DESCRIPTOR |
RecentTasksController |
Recent task list, split pairs |
IShellTransitions |
IShellTransitions.DESCRIPTOR |
Transitions |
Shell transition coordination |
IPip |
IPip.DESCRIPTOR |
PipController |
PiP entry/exit control |
ISplitScreen |
ISplitScreen.DESCRIPTOR |
SplitScreenController |
Split-screen management |
IBubbles |
IBubbles.DESCRIPTOR |
BubbleController |
Bubble state queries |
IBackAnimation |
IBackAnimation.DESCRIPTOR |
BackAnimationController |
Predictive back animations |
IDragAndDrop |
IDragAndDrop.DESCRIPTOR |
DragAndDropController |
Cross-app drag and drop |
IOneHanded |
IOneHanded.DESCRIPTOR |
OneHandedController |
One-handed mode |
IStartingWindow |
IStartingWindow.DESCRIPTOR |
StartingWindowController |
Starting window management |
IDesktopMode |
IDesktopMode.DESCRIPTOR |
DesktopTasksController |
Desktop windowing mode |
9.4 Shell-to-Launcher Callback Registration Flow
Launcher obtains Shell binders through a Bundle-packing mechanism orchestrated by ShellController.createExternalInterfaces():
sequenceDiagram
participant Launcher as Launcher3
participant SysUI as SystemUI (main thread)
participant ShellCtrl as ShellController (shell thread)
participant Features as Shell Features
Note over Features: During ShellInit.init()
Features->>ShellCtrl: addExternalInterface(DESCRIPTOR, supplier, this)
Note over ShellCtrl: Stores Supplier in mExternalInterfaceSuppliers
Note over Launcher,SysUI: Launcher binds to SystemUI
SysUI->>ShellCtrl: ShellInterface.createExternalInterfaces(bundle)
Note over ShellCtrl: executeBlocking() - waits up to 2s
ShellCtrl->>ShellCtrl: Invalidate old mExternalInterfaces
loop For each registered supplier
ShellCtrl->>Features: supplier.get() - creates new IFoo.Stub
Features-->>ShellCtrl: ExternalInterfaceBinder
ShellCtrl->>ShellCtrl: bundle.putBinder(key, binder.asBinder())
end
ShellCtrl-->>SysUI: Bundle with all IBinders
SysUI-->>Launcher: Bundle delivered
Launcher->>Launcher: Extract IRecentTasks, IShellTransitions, etc.
9.5 ExternalInterfaceBinder Permission Enforcement
The ExternalInterfaceBinder pattern enforces permission on every remote call from Launcher via executeRemoteCallWithTaskPermission(), which checks MANAGE_ACTIVITY_TASKS before dispatching to the Shell thread.
Binder invalidation: When createExternalInterfaces() is called again (e.g., on user switch), all previous binders are invalidated. This ensures that a Launcher instance from a previous user cannot continue calling into Shell features.
9.6 WMShell CoreStartable: Binding SysUI Events to Shell
The WMShell class is a CoreStartable that registers listeners for SysUI events and forwards them to Shell interfaces:
| SysUI Source | Shell Target | Purpose |
|---|---|---|
ConfigurationController |
ShellInterface.onConfigurationChanged() |
Config changes |
KeyguardStateController |
ShellInterface.onKeyguardVisibilityChanged() |
Keyguard show/hide |
KeyguardUpdateMonitor |
ShellInterface.onKeyguardDismissAnimationFinished() |
Dismiss animation |
UserTracker |
ShellInterface.onUserChanged() / onUserProfilesChanged() |
User switch |
Pip.registerPipTransitionCallback() |
SysUiState flags |
PiP animating state |
SplitScreen.registerSplitAnimationListener() |
SysUiState flags |
Split invocation state |
DesktopMode.addVisibleTasksListener() |
SysUiState flags |
Freeform active in desktop |
See also: §10 for the DI architecture that constructs these bindings, and §11 for the threading model governing the @ExternalThread annotation.
Reference files:
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java— Bundle packing, binder invalidation, ShellInterfaceImplframeworks/base/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java— CoreStartable binding SysUI events to Shellframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExternalInterfaceBinder.java— Permission-enforcing binder wrapperframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java— Shell-SystemUI DI bridgepackages/apps/Launcher3/quickstep/src/com/android/quickstep/RecentsActivity.java— Recents UI
10. Dependency Injection Architecture
The WM Shell uses Dagger 2 as a subcomponent within SystemUI’s dependency graph. This architecture enforces a clean boundary: Shell components are scoped under @WMSingleton and only explicitly exported interfaces are visible to the SysUIComponent.
10.1 Dagger Module Hierarchy
graph TB
subgraph "WM Shell Scope (@WMSingleton)"
WMShellBaseModule["WMShellBaseModule<br/>(common to all form factors)"]
WMShellConcurrencyModule["WMShellConcurrencyModule<br/>(thread providers)"]
WMShellModule["WMShellModule<br/>(phone-specific)"]
TvWMShellModule["TvWMShellModule<br/>(TV-specific)"]
PipModule["PipModule"]
ShellBackModule["ShellBackAnimationModule"]
LetterboxModule["LetterboxModule"]
TvPipModule["TvPipModule"]
WMComponent["WMComponent<br/>Exports Interface"]
end
subgraph "SysUI Scope (@SysUISingleton)"
SysUIComponent["SysUIComponent"]
end
WMShellBaseModule --> WMShellConcurrencyModule
WMShellModule -->|includes| WMShellBaseModule
WMShellModule -->|includes| PipModule
WMShellModule -->|includes| ShellBackModule
WMShellModule -->|includes| LetterboxModule
TvWMShellModule -->|includes| TvPipModule
WMShellModule --> WMComponent
TvWMShellModule --> WMComponent
WMComponent -->|"@BindsInstance<br/>Shell exports"| SysUIComponent
10.2 WMComponent Exports
WMComponent is the @Subcomponent interface that explicitly defines what Shell exposes to SystemUI:
| Export Method | Type | Optional | Purpose |
|---|---|---|---|
getShell() |
ShellInterface |
No | Primary SysUI-to-Shell communication |
getShellTransitions() |
ShellTransitions |
No | Transition management |
getKeyguardTransitions() |
KeyguardTransitions |
No | Keyguard-specific transitions |
getPip() |
Optional<Pip> |
Yes | Picture-in-Picture |
getSplitScreen() |
Optional<SplitScreen> |
Yes | Split-screen mode |
getBubbles() |
Optional<Bubbles> |
Yes | Bubble notifications |
getOneHanded() |
Optional<OneHanded> |
Yes | One-handed mode |
getRecentTasks() |
Optional<RecentTasks> |
Yes | Recent tasks management |
getBackAnimation() |
Optional<BackAnimation> |
Yes | Predictive back animation |
getDesktopMode() |
Optional<DesktopMode> |
Yes | Desktop windowing |
getStartingSurface() |
Optional<StartingSurface> |
Yes | Starting window management |
getTaskViewFactory() |
Optional<TaskViewFactory> |
Yes | TaskView creation |
getDisplayAreaHelper() |
Optional<DisplayAreaHelper> |
Yes | Display area management |
getAppZoomOut() |
Optional<AppZoomOut> |
Yes | App zoom-out animation |
getAppHandles() |
Optional<AppHandles> |
Yes | App handle UI in desktop |
10.3 WMShellConcurrencyModule Thread Provider Bindings
WMShellConcurrencyModule defines all threading primitives used by the Shell:
| Qualifier Annotation | Thread Name | Priority | Notes |
|---|---|---|---|
@ExternalMainThread |
(SysUI main) | inherited | SysUI’s main looper |
@ShellMainThread |
wmshell.main |
THREAD_PRIORITY_DISPLAY (-4) |
Conditionally separate from SysUI main |
@ShellAnimationThread |
wmshell.anim |
THREAD_PRIORITY_DISPLAY (-4) |
Dedicated animation thread |
@ShellSplashscreenThread |
wmshell.splashscreen |
THREAD_PRIORITY_TOP_APP_BOOST (-10) |
High priority; splash speed is critical |
@ShellDesktopThread |
wmshell.desktop |
THREAD_PRIORITY_TOP_APP_BOOST (-10) |
Desktop mode operations |
@ShellBackgroundThread |
wmshell.background |
THREAD_PRIORITY_BACKGROUND (10) |
Low-priority; boostable to FOREGROUND (-2) |
The @ShellMainThread provider has conditional behavior: if config_enableShellMainThread is true, a separate thread is created; otherwise the SysUI main handler is reused. The module also provides a @ShellMainThread Choreographer obtained via executeBlocking() on the shell main thread.
10.4 Variant-Specific Module Selection
Different device form factors use different WMComponent subcomponents:
| Aspect | Phone (WMShellModule) |
TV (TvWMShellModule) |
|---|---|---|
| PiP implementation | PipModule (touch-based, gesture-driven) |
TvPipModule (remote-control oriented) |
| Split-screen | SplitScreenController |
TvSplitScreenController |
| Starting window | PhoneStartingWindowTypeAlgorithm |
TvStartingWindowTypeAlgorithm |
| Bubbles | Full BubbleController |
Not included |
| Back animation | ShellBackAnimationModule |
Not included |
| Desktop mode | Full desktop windowing | Not applicable |
10.5 DI Initialization Sequence
sequenceDiagram
participant App as SystemUI Application
participant Init as SystemUIInitializer
participant WMComp as WMComponent
participant SysComp as SysUIComponent
participant WMShellSvc as WMShell (CoreStartable)
App->>Init: init(context)
Init->>Init: setupWmComponent(context)
alt Shell main thread enabled
Init->>Init: createShellMainThread()
Init->>Init: shellThread.start()
Note over Init: Build WMComponent ON the shell thread
Init->>WMComp: wmBuilder.setShellMainThread(thread).build()
else Shell main thread disabled
Init->>WMComp: wmBuilder.build()
end
Note over WMComp: All @WMSingleton providers instantiated
Init->>SysComp: Build SysUIComponent with WMComponent exports
Note over SysComp: Injects ShellInterface, Optional Pip, etc.
App->>WMComp: wmComponent.init()
WMComp->>WMComp: getShell().onInit()
Note over WMComp: ShellInit.init() runs all init callbacks
App->>WMShellSvc: start()
WMShellSvc->>WMShellSvc: Register ConfigurationListener, KeyguardCallback, UserTracker
The key ordering guarantee: WMComponent.init() is called before any CoreStartable.start(), so all Shell features are initialized before WMShell begins binding SysUI events.
Reference files:
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java— Phone variant Shell DI moduleframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java— Component interface with 15 exportsframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java— Common bindings shared across variantsframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java— Thread and executor providersframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java— TV variant module
11. Threading Model
The WM Shell threading model enforces strict thread boundaries between SystemUI’s main thread and the Shell’s own thread pool. The design prevents deadlocks and race conditions across the Shell/SysUI boundary while allowing high-priority animation work to proceed without blocking the UI.
11.1 Thread Architecture Overview
graph LR
subgraph "SystemUI Main Thread"
SysUI["SysUI Components<br/>(WMShell, StatusBar, etc.)"]
end
subgraph "Shell Thread Pool"
Main["wmshell.main<br/>PRIORITY_DISPLAY (-4)"]
Anim["wmshell.anim<br/>PRIORITY_DISPLAY (-4)"]
Splash["wmshell.splashscreen<br/>PRIORITY_TOP_APP_BOOST (-10)"]
Desktop["wmshell.desktop<br/>PRIORITY_TOP_APP_BOOST (-10)"]
Background["wmshell.background<br/>PRIORITY_BACKGROUND (10)<br/>boostable to FOREGROUND (-2)"]
end
subgraph "system_server"
WMS["WindowManagerService<br/>(mH handler + mGlobalLock)"]
end
SysUI -->|"@ExternalMainThread executor<br/>posts to shell thread"| Main
Main -->|"@ShellAnimationThread executor"| Anim
Main -->|"Binder IPC"| WMS
Anim -->|"SurfaceControl transactions"| WMS
11.2 Thread Boundaries
sequenceDiagram
participant SysUI as SysUI Main Thread
participant ShellThread as Shell Main Thread
participant WMThread as WM/System Thread
participant SF as SurfaceFlinger
SysUI->>ShellThread: ShellInterface.onConfigurationChanged()<br/>(posted via ShellExecutor)
Note over ShellThread: Feature controllers<br/>execute on Shell thread
ShellThread->>WMThread: WindowContainerTransaction<br/>(via Binder)
WMThread->>ShellThread: TaskOrganizer callbacks<br/>(via Binder)
ShellThread->>SF: SurfaceControl.Transaction<br/>(direct)
ShellThread->>SysUI: Callback.onResult()<br/>(posted via SysUI Executor)
11.3 Shell Thread Pool Details
| Thread | Name | Priority | Numeric | Purpose |
|---|---|---|---|---|
| Shell Main | wmshell.main |
THREAD_PRIORITY_DISPLAY |
-4 | Core Shell logic, state management, IPC dispatch |
| Animation | wmshell.anim |
THREAD_PRIORITY_DISPLAY |
-4 | Shell-driven animations, SurfaceControl transactions |
| Splashscreen | wmshell.splashscreen |
THREAD_PRIORITY_TOP_APP_BOOST |
-10 | Starting window creation (latency-critical for app launch) |
| Desktop | wmshell.desktop |
THREAD_PRIORITY_TOP_APP_BOOST |
-10 | Desktop mode windowing operations |
| Background | wmshell.background |
THREAD_PRIORITY_BACKGROUND |
10 | Low-priority cleanup, persistence, non-urgent work |
The @ShellMainThread provider has conditional behavior: if config_enableShellMainThread is true, a separate thread is created; otherwise the SysUI main handler is reused. In debug builds, the main and animation thread loopers enable slow-dispatch logging at 30ms threshold.
11.4 Threading Rules
- Shell Main Thread: All Shell feature controllers execute on
@ShellMainThread. External calls are posted to this thread before execution. - SysUI Main Thread: UI updates and SysUI callbacks run on the main (UI) thread via
@ExternalMainThreadexecutor. - Cross-thread calls: Marshaled via executors;
ShellController.ShellInterfaceImplwraps calls withmMainExecutor.execute() - Blocking calls:
createExternalInterfaces()andhandleCommand()useShellExecutor.executeBlocking()with 2-secondCountDownLatchtimeout - Binder calls to WM: Cross-process via AIDL, handled on Binder thread pool
- Same-thread optimization: If
execute()is called on the handler’s own looper thread, the runnable runs synchronously (no post)
11.5 HandlerExecutor and Boost Semantics
HandlerExecutor is the Shell’s ShellExecutor implementation backed by a Handler. Key behaviors:
Priority boosting: Executors can be temporarily boosted to a higher thread priority using a reference-counted mechanism. The background thread normally runs at THREAD_PRIORITY_BACKGROUND (10) but can be boosted to THREAD_PRIORITY_FOREGROUND (-2):
| Method | Behavior |
|---|---|
setBoost() |
Increment boost count; set thread to boosted priority if transitioning from 0 |
resetBoost() |
Decrement boost count; restore default priority if transitioning to 0 |
isBoosted() |
Returns true if boost count > 0 |
The boost is reference-counted: multiple callers can request boosts simultaneously, and the thread priority only resets when all boost requests are released.
Thread assertion: assertCurrentThread() throws IllegalStateException if called from the wrong thread, enabling fail-fast debugging of threading violations.
11.6 WMS mGlobalLock and WindowManagerGlobalLock
On the system_server side, WindowManagerService uses a single global lock (mGlobalLock) shared with ActivityTaskManagerService:
WMS uses two handlers for asynchronous work:
| Handler | Thread | Purpose |
|---|---|---|
mH (class H) |
android.display |
Main WMS message processing: window lifecycle, focus, policy |
mAnimationHandler |
android.anim |
Animation ticks, starting window creation, layout-adjacent work |
Critical interaction: When Shell code calls into WMS via Binder (e.g., WindowOrganizer.applyTransaction()), the call enters system_server on a Binder thread, acquires mGlobalLock, and executes synchronously. The Shell must never hold its own lock during such a call to avoid deadlock if WMS simultaneously calls back via TaskOrganizer callbacks.
sequenceDiagram
participant ShellMain as wmshell.main
participant Binder as Binder Thread (system_server)
participant WMS as WMS (mGlobalLock)
participant Callback as TaskOrganizer Callback
ShellMain->>Binder: WindowOrganizer.applyTransaction()
Binder->>WMS: acquire mGlobalLock
WMS->>WMS: Process transaction
WMS->>Binder: release mGlobalLock, return
Note over WMS: Separately, on task change:
WMS->>Callback: onTaskInfoChanged() via Binder
Callback->>ShellMain: post to shell executor
ShellMain->>ShellMain: Handle callback
Reference files:
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java— Thread pool definitions and executor providersframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java— Shell executor interface withexecuteBlocking()frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java— Handler-backed executor with boost semanticsframeworks/base/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellMainThread.java— Main thread annotationframeworks/base/services/core/java/com/android/server/wm/WindowManagerService.java—mGlobalLock,mH, andmAnimationHandler
12. Shell Feature Modules
12.1 Desktop Mode (76 files)
The largest and most actively evolving feature module, signaling AOSP’s desktop-first direction.
graph TB
subgraph "Desktop Mode Architecture"
DTC[DesktopTasksController<br/>Main Controller]
DRepo[DesktopRepository<br/>State Management]
DDEH[DesktopDisplayEventHandler<br/>Display Events]
subgraph "Transitions"
DMTH[DesktopMixedTransitionHandler]
DDTH[DragToDesktopTransitionHandler]
end
subgraph "Window Chrome"
WD[WindowDecorViewModel]
DTBAR[Desktop Titlebar]
HMENU[Handle Menu]
RESIZE[Resize Handles]
end
subgraph "Multi-Desks"
MD[MultiDesksController]
DeskState[Desk State]
end
subgraph "Minimize"
MIN[MinimizeHandler]
end
subgraph "Education"
EDU[Desktop Education UI]
end
end
DTC --> DRepo
DTC --> DMTH
DTC --> DDTH
DTC --> DDEH
DTC --> WD
DTC --> MD
DTC --> MIN
WD --> DTBAR
WD --> HMENU
WD --> RESIZE
Key Feature Flags (DesktopExperienceFlags.java, 133 flags):
| Category | Example Flags | Purpose |
|---|---|---|
| Connected Displays | ENABLE_CONNECTED_DISPLAYS_DND, _PIP, _WINDOW_DRAG, _WALLPAPER, _CURSOR |
Multi-display desktop experience |
| Desktop Policy | ENABLE_DESKTOP_FIRST_LISTENER, _BASED_DRAG_TO_MAXIMIZE |
Desktop-first behavior |
| Display Topology | DISPLAY_TOPOLOGY, ENABLE_AUTO_RESTART_ON_DISPLAY_MOVE |
Display arrangement management |
| Activity Embedding | ENABLE_ACTIVITY_EMBEDDING_SUPPORT_FOR_CONNECTED_DISPLAYS |
Split layouts on external displays |
12.2 Split Screen (18 files)
graph LR
SSC[SplitScreenController] --> SC_COORD[StageCoordinator]
SC_COORD --> STL_MAIN[StageTaskListener<br/>MAIN Stage]
SC_COORD --> STL_SIDE[StageTaskListener<br/>SIDE Stage]
SC_COORD --> SST[SplitScreenTransitions]
SSC --> SS_AIDL[ISplitScreen<br/>AIDL]
12.3 Picture-in-Picture (PiP v1 + PiP v2, 70 files)
- PiP v1 (
pip/): Mature implementation withPipTaskOrganizer,PipTransition,PipAnimationController - PiP v2 (
pip2/): Newer simplified architecture under development
12.4 Bubbles (77 files)
Full notification bubble system with sub-packages: animation/, bar/, logging/, storage/, appinfo/, fold/, shortcut/, util/
12.5 Window Decoration (99 files)
Rich window chrome system for desktop mode: titlebars, resize handles, handle menus, tiling/snapping grid (tiling/), extensible decoration framework (extension/).
12.6 Activity Embedding and TaskFragment
graph TB
subgraph "Jetpack WindowManager Extensions"
WME[WindowExtensions<br/>Entry Point]
AEC[ActivityEmbeddingComponent]
SplitCtrl[SplitController]
WLC[WindowLayoutComponent]
WAC[WindowAreaComponent]
end
subgraph "WM Core"
TF[TaskFragment]
TFOC[TaskFragmentOrganizerController]
end
subgraph "Shell"
AE_Shell[ActivityEmbedding<br/>Shell Module]
end
WME --> AEC
WME --> WLC
WME --> WAC
AEC --> SplitCtrl
SplitCtrl --> TFOC
TFOC --> TF
AE_Shell --> TFOC
Reference files:
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java— PiP controllerframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java— Split screen controllerframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt— Desktop mode controllerframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java— Freeform task listenerframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java— Bubble notifications
13. Custom Window Manager and Shell Development
Android’s Shell architecture is designed for extensibility. Custom shell features integrate via a small set of stable binder interfaces without modifying WM Core.
13.1 Architecture: WM Core ↔ Shell Boundary
The interface boundary is strictly binder-based. WM Core never calls Shell code directly; all communication is via AIDL:
graph TB
subgraph "System Server"
WMC["WM Core<br/>(WindowManagerService, ATMS)"]
end
subgraph "Shell Process (same or separate)"
ITO["ITaskOrganizer implementation"]
ITH["ITransitionHandler"]
IDAO["IDisplayAreaOrganizer"]
end
WMC -->|"AIDL binder"| ITO
WMC -->|"AIDL binder"| ITH
WMC -->|"AIDL binder"| IDAO
Source: frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md
13.2 Required Interfaces
ITaskOrganizer — Task Lifecycle Callbacks
Source: frameworks/base/core/java/android/window/ITaskOrganizer.aidl
A custom shell must implement these callbacks (all oneway):
| Callback | Trigger | Key Parameter |
|---|---|---|
onTaskAppeared(RunningTaskInfo, SurfaceControl) |
Task created/visible | Leash SurfaceControl |
onTaskVanished(RunningTaskInfo) |
Task removed/invisible | TaskInfo snapshot |
onTaskInfoChanged(RunningTaskInfo) |
Task metadata changed | Updated TaskInfo |
onBackPressedOnTaskRoot(RunningTaskInfo) |
Back press on root | TaskInfo |
addStartingWindow(StartingWindowInfo, IBinder) |
Starting window needed | Window info |
removeStartingWindow(int taskId, ...) |
Starting window done | Task ID |
onTransitionReady(IBinder, TransitionInfo, SC.Transaction, SC.Transaction) |
Transition ready to animate | Info + transactions |
requestStartTransition(IBinder, TransitionRequestInfo) |
Shell may start transition | Request context |
TaskOrganizer — Base Registration
Source: frameworks/base/core/java/android/window/TaskOrganizer.java (line 112)
public List<TaskAppearedInfo> registerOrganizer() {
// Claims ownership of organized tasks; returns list of already-existing tasks
return mTaskOrganizerController
.registerTaskOrganizer(mInterface)
.getList();
// WM Core will now route onTaskAppeared/Vanished/Changed to mInterface
}
Once registered, WM Core delivers onTaskAppeared for every existing and future task in the requested windowing mode, providing a leash SurfaceControl that the shell can animate.
WindowContainerTransaction — Atomic Operations
Source: frameworks/base/core/java/android/window/WindowContainerTransaction.java
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setBounds(taskToken, new Rect(0, 0, 1080, 720)); // resize
wct.setWindowingMode(taskToken, WINDOWING_MODE_FREEFORM); // change mode
wct.reorder(taskToken, true /*onTop*/); // bring to front
wct.reparent(taskToken, newParent, true /*onTop*/); // reparent task
// Apply atomically to WM Core:
shellTaskOrganizer.applyTransaction(wct);
// or via Shell Transitions:
transitions.startTransition(TRANSIT_CHANGE, wct, handler);
13.3 Shell Initialization
Source: frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java (line 62)
// Each Shell module registers an init callback:
shellInit.addInitCallback(() -> myFeature.onInit(), this);
// When SystemUI calls ShellInit.init():
public void init() {
for (Runnable callback : mInitCallbacks) {
callback.run(); // modules initialize in registration order
}
}
Shell modules use Dagger 2 for dependency injection. The WMComponent (131-line file at libs/WindowManager/Shell/.../dagger/WMComponent.java) declares which shell modules are provided.
13.4 Transition Handler Registration
Source: frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
// Register a custom handler (highest priority wins)
transitions.addHandler(myTransitionHandler);
// Handler interface to implement:
public interface TransitionHandler {
boolean startAnimation(IBinder transition,
TransitionInfo info,
SurfaceControl.Transaction startTransaction,
SurfaceControl.Transaction finishTransaction,
Transitions.TransitionFinishCallback finishCallback);
void mergeAnimation(IBinder transition, TransitionInfo info, ...);
void onTransitionConsumed(IBinder transition, boolean aborted, ...);
}
The handler receives:
startTransaction: pre-applied to establish the initial visual statefinishTransaction: to be applied by the handler when animation ends- Each
TransitionInfo.Changecontains a leash (see §16) to animate
13.5 Minimal Custom Shell Feature
A minimal feature module consists of:
MyFeature.java // Core logic, implements init callback
MyFeatureTaskOrganizer.java // Extends TaskOrganizer, handles task lifecycle
MyFeatureTransitionHandler.java // Implements TransitionHandler
Template pattern (from PiP example in libs/WindowManager/Shell/src/com/android/wm/shell/pip/):
// 1. Feature interface
public interface MyFeature {
void doSomething();
}
// 2. Implementation with TaskOrganizer
public class MyFeatureController implements MyFeature {
private final ShellTaskOrganizer mOrganizer;
private final Transitions mTransitions;
@Inject
public MyFeatureController(ShellTaskOrganizer organizer, Transitions transitions,
ShellInit shellInit) {
mOrganizer = organizer;
mTransitions = transitions;
shellInit.addInitCallback(this::onInit, this);
}
private void onInit() {
mOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_MULTI_WINDOW);
mTransitions.addHandler(mTransitionHandler);
}
@Override
public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
// position task, set up animations
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setBounds(taskInfo.token, myDesiredBounds);
mOrganizer.applyTransaction(wct);
}
}
Reference files:
frameworks/base/core/java/android/window/TaskOrganizer.java— Client-side task organizer APIframeworks/base/core/java/android/window/ITaskOrganizer.aidl— Task organizer AIDL interfaceframeworks/base/core/java/android/window/WindowContainerTransaction.java— Atomic WM operationsframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java— Shell transition handlingframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md— Shell architecture overviewframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java— Shell initialization sequence
14. SurfaceControlViewHost and Embedded Windows
SurfaceControlViewHost (SCVH) enables rendering a full Android View hierarchy into an isolated SurfaceControl, which can then be embedded in another process’s UI. It is the foundation of process-isolated UI embedding in Android.
14.1 Role in the Window Subsystem
In normal window management, a View hierarchy lives inside a Window → ViewRootImpl → WindowState chain, with WindowManagerService managing the WindowState. SCVH bypasses WMS by substituting a custom WindowlessWindowManager that manages surface allocations locally.
graph TB
subgraph "Normal Window"
N_ACT[Activity] --> N_PW[PhoneWindow]
N_PW --> N_VRI[ViewRootImpl]
N_VRI -->|"IWindowSession"| N_WMS["WMS"]
N_WMS --> N_WS[WindowState]
N_WS --> N_SC[SurfaceControl]
end
subgraph "SurfaceControlViewHost"
S_HOST["Host App (any context)"] --> S_SCVH[SurfaceControlViewHost]
S_SCVH --> S_VRI[ViewRootImpl]
S_VRI --> S_WWM["WindowlessWindowManager<br/>(no WMS)"]
S_SCVH --> S_SC["SurfaceControl (root)"]
S_SC --> S_CHILD["Child windows"]
end
14.2 Core Implementation
Source: frameworks/base/core/java/android/view/SurfaceControlViewHost.java
Key fields:
mViewRoot(line 59): TheViewRootImplthat drives renderingmWm(line 61):WindowlessWindowManager— does NOT call into WMSmSurfaceControl(line 63): RootSurfaceControlfor the hosted hierarchy
Lifecycle:
// 1. Create host
SurfaceControlViewHost scvh = new SurfaceControlViewHost(context, display, hostToken);
// 2. Attach view
scvh.setView(myView, width, height);
// 3. Export surface for embedding
SurfacePackage pkg = scvh.getSurfacePackage();
// Send pkg to another process via Binder (it's Parcelable)
// 4. Embedded view becomes visible in the other process's UI
anotherProcessView.setChildSurfacePackage(pkg); // SurfaceView API
// 5. Release when done
scvh.release();
14.3 WindowlessWindowManager
Source: frameworks/base/core/java/android/view/WindowlessWindowManager.java
WindowlessWindowManager implements IWindowSession (the interface normally backed by WMS’s Session), but manages surfaces locally:
mRootSurface(line 89): rootSurfaceControlfor all windows in this hostmStateForWindow(line 80):HashMap<IBinder, State>— tracks per-windowSurfaceControlmRealWm(line 91): the real WMS session, used only for input routing delegation
Key difference from normal WMS: Calls to addToDisplay(), relayout(), remove() do NOT cross into WindowManagerService. They create/modify/destroy child SurfaceControl objects under mRootSurface. This means:
- No
WindowStateexists in WM Core for SCVH windows - No
ActivityRecordassociation - The host’s
SurfaceControl(fromgetSurfacePackage()) is a leaf in the host process’s SurfaceControl tree
14.4 Input Routing via InputTransferToken
Source: SurfaceControlViewHost.java (line 35, line 131, line 164)
// Host sets its input token so the embedded view can receive input
scvh.setHostInputTransferToken(hostInputToken);
// WindowlessWindowManager relays to real WM for input channel setup:
mRealWm.updateInputChannel(window, displayId, surface, attrs, hostToken, outConfig);
InputTransferToken is a binder token that Android’s input system uses to transfer input focus between SurfaceControls across process boundaries. Without it, the embedded view would not receive touch/key events.
14.5 SurfacePackage — Cross-Process Transfer
Source: SurfaceControlViewHost.java — inner class SurfacePackage (line 173)
public static final class SurfacePackage implements Parcelable {
private final SurfaceControl mSurfaceControl;
private final IBinder mAccessibilityEmbeddedConnection; // A11y bridge
private final IBinder mRemoteInterface; // ISurfaceControlViewHost
private final InputTransferToken mInputTransferToken;
// Parcelable — can be sent via AIDL to any process
}
The SurfacePackage is lifetime-independent from the SCVH itself. The receiving process can hold the package, embed it in a SurfaceView via SurfaceView.setChildSurfacePackage(pkg), and the SCVH continues driving its view hierarchy.
14.6 Use Cases in Android
| Use Case | Description |
|---|---|
| SDK Sandbox Runtime | Isolated SDK content rendered in sandboxed process; host app receives SurfacePackage |
| Wear Tiles | Tile rendering process sends SurfacePackage to watch face host |
| Remote Views (enhanced) | Rich interactable content from remote process |
| Picture-in-Picture | PiP content from another app’s surface |
| Presentation API | android.app.Presentation uses similar pattern for secondary display |
Reference files:
frameworks/base/core/java/android/view/SurfaceControlViewHost.java— Embedded view host APIframeworks/base/core/java/android/view/WindowlessWindowManager.java— Windowless WM for embedded surfacesframeworks/base/core/java/android/view/SurfaceControlViewHost.java(inner classSurfacePackage) — Cross-process surface packageframeworks/base/core/java/android/window/InputTransferToken.java— Input routing for embedded views
Part IV: Transitions, Animation, and Surface Leash
15. Transition System
15.1 New vs Legacy Architecture
| Aspect | Legacy AppTransition | New Shell Transitions |
|---|---|---|
| Animation control | WM Core only | Shell-driven via TransitionPlayer |
| Parallel transitions | No | Yes (multi-track) |
| Gesture support | Limited | Full (transient-launch with cancellation) |
| Merging | No | Yes (intelligent combining) |
| Feature integration | Hard-coded | Handler-based dispatch |
15.2 Transition Lifecycle
The transition system operates across two layers (WM Core and Shell), each with its own state machine:
WM Core States (Transition.java):
stateDiagram-v2
[*] --> STATE_PENDING: requestTransition()
STATE_PENDING --> STATE_COLLECTING: collect participants
STATE_COLLECTING --> STATE_STARTED: all participants collected
STATE_STARTED --> STATE_PLAYING: onTransitionReady() sent to Shell
STATE_PLAYING --> STATE_FINISHED: Shell calls finishTransition()
STATE_COLLECTING --> STATE_ABORT: abort requested
STATE_ABORT --> [*]
STATE_FINISHED --> [*]
note right of STATE_COLLECTING
WM Core collects participants
(activities, tasks, windows)
end note
note right of STATE_PLAYING
Shell has control of
animation leashes
end note
Shell States (Transitions.java):
stateDiagram-v2
[*] --> PENDING: onTransitionReady()
PENDING --> READY: handler accepts
READY --> ACTIVE: animation starts
ACTIVE --> MERGED: new transition merges in
ACTIVE --> FINISHED: animation completes
MERGED --> FINISHED: merged animation completes
FINISHED --> [*]
note right of ACTIVE
Shell orchestrates animations
via SurfaceControl transactions
end note
15.3 Transition Flow
sequenceDiagram
participant App as Application
participant WMS as WindowManagerService
participant TC as TransitionController
participant Trans as Transition
participant Shell as Shell Transitions
participant SF as SurfaceFlinger
App->>WMS: startActivity()
WMS->>TC: requestTransition(OPEN)
TC->>Trans: new Transition(OPEN)
Note over Trans: Collect participants<br/>(activities, tasks)
Trans->>Trans: collect(ActivityRecord)
Trans->>Trans: collect(Task)
Note over Trans: Wait for sync ready<br/>(BLAST sync engine)
Trans->>TC: onTransactionReady()
TC->>Shell: onTransitionReady(TransitionInfo, SurfaceTransaction)
Note over Shell: Dispatch to appropriate handler
Shell->>Shell: DefaultTransitionHandler or<br/>Feature-specific handler
loop Animation Frames
Shell->>SF: SurfaceControl.Transaction<br/>(position, alpha, crop, matrix)
end
Shell->>TC: finishTransition(wct)
TC->>Trans: Apply final state
Trans->>WMS: Cleanup
15.4 Shell Transition Handlers
- DefaultTransitionHandler: Standard open/close/change animations
- DefaultMixedHandler: Handles transitions involving multiple features simultaneously
- RemoteTransitionHandler: Delegates to external transition players (e.g., Launcher3 for recents)
- Feature-specific handlers:
SplitScreenTransitions,PipTransition,DesktopMixedTransitionHandler, etc.
Reference files:
frameworks/base/services/core/java/com/android/server/wm/TransitionController.java— Transition lifecycle management and Shell communicationframeworks/base/services/core/java/com/android/server/wm/Transition.java— Transition state machine (PENDING through FINISHED)frameworks/base/services/core/java/com/android/server/wm/BLASTSyncEngine.java— Synchronizes participant readiness before transition starts
16. Surface Leash — Deep Dive
The surface leash is the central mechanism that enables WM Core to hand window animations to the Shell without exposing or blocking on the window’s real SurfaceControl. Understanding it requires understanding why it exists and the precise SurfaceControl operations involved.
16.1 What Is a Leash?
Source: frameworks/base/services/core/java/com/android/server/wm/SurfaceAnimator.java (lines 45–50)
The SurfaceAnimator class comment defines the leash precisely:
“A class that can run animations on objects that have a set of child surfaces. We do this by reparenting all child surfaces of an object onto a new surface, called the ‘Leash’. The Leash gets attached in the surface hierarchy where the children were attached to. We then hand off the Leash to the component handling the animation, which is specified by the
AnimationAdapter. When the animation is done animating, our callback to finish the animation will be invoked, at which we reparent the children back to the original parent.”
A leash is an intermediate effect-layer SurfaceControl inserted between a window’s original parent and the window’s own SurfaceControl:
graph LR
subgraph "Before Animation"
B_PAR[OriginalParent] --> B_WS[WindowSurface]
end
subgraph "During Animation"
D_PAR[OriginalParent] --> D_LEASH["Leash (animation target)"]
D_LEASH --> D_WS[WindowSurface]
end
16.2 Leash Creation
Source: SurfaceAnimator.java — createAnimationLeash() (lines 399–424)
private static SurfaceControl createAnimationLeash(
@NonNull Animatable animatable,
@NonNull SurfaceControl surface,
@NonNull Transaction t,
@AnimationType int type,
int width, int height, int x, int y,
boolean hidden,
Supplier<Transaction> transactionFactory) {
// 1. Create the leash as an effect layer with the same parent as the window
final SurfaceControl.Builder builder = animatable.makeAnimationLeash()
.setParent(animatable.getAnimationLeashParent()) // same parent slot
.setName(surface + " - animation-leash of " + animationTypeToString(type))
.setHidden(hidden)
.setEffectLayer() // effect layer, not buffer
.setCallsite("SurfaceAnimator.createAnimationLeash");
final SurfaceControl leash = builder.build();
// 2. Position and size the leash to match the window
t.setWindowCrop(leash, width, height);
t.setPosition(leash, x, y);
t.show(leash);
t.setAlpha(leash, hidden ? 0 : 1);
// 3. Reparent the window's real SurfaceControl under the leash
t.reparent(surface, leash);
return leash;
}
Key properties:
- Effect layer (
setEffectLayer()): A SurfaceLayer that doesn’t hold its own buffers; it exists purely to aggregate child layers and apply transforms. This is how alpha, scale, and crop applied to the leash cascade down to the real window surface. - Same parent: The leash occupies exactly the z-order slot the original window had. Nothing in the hierarchy above it sees any change — only the leash is there, with the window now a child of it.
reparent(surface, leash): The single operation that inserts the leash. After this, animating the leash animates the entire window subtree.
16.3 Why a Leash Is Needed
Problem 1 — WM Lock Contention: WM Core holds mGlobalLock during almost all operations. If a SurfaceAnimationRunner (which runs off-thread without holding the lock) were to animate the window’s real SurfaceControl directly, there would be concurrent unsafe access between the animation thread and WM Core operations on the same surface.
Problem 2 — Separation of Concerns: The leash is handed to the animation adapter. The animator knows only the leash; it has no access to the actual window hierarchy. WM Core can continue modifying the window’s real SurfaceControl (e.g., changing crop or position) independently.
Problem 3 — Atomic Finish: When the animation ends, SurfaceAnimator’s finish callback (via getFinishedCallback at line ~115) reparents the window back to its original parent and destroys the leash in a single Transaction. From SurfaceFlinger’s perspective, this is a single atomic commit — no intermediate state is visible.
sequenceDiagram
participant WM as WM Core (mGlobalLock)
participant SA as SurfaceAnimator
participant Leash as Leash SurfaceControl
participant SF as SurfaceFlinger
participant Anim as SurfaceAnimationRunner (no lock)
WM->>SA: startAnimation(animatable, adapter)
SA->>Leash: createAnimationLeash()
SA->>SF: t.reparent(windowSurface, leash) [atomic]
SA->>WM: animatable.onAnimationLeashCreated(t, leash)
WM-->>SA: (returns, releases lock)
SA->>Anim: adapter.startAnimation(leash, ...)
loop per-frame (Choreographer)
Anim->>Leash: t.setAlpha / setMatrix / setPosition (no WM lock)
Leash->>SF: commit transaction
end
Anim->>SA: onAnimationFinished()
SA->>SF: t.reparent(windowSurface, originalParent) [atomic]
SA->>Leash: leash.release()
16.4 Shell Transitions and the Leash
In the Shell Transition system, each TransitionInfo.Change carries a leash set up by WM Core before the transition info is sent to Shell:
Source: frameworks/base/core/java/android/window/TransitionInfo.java (lines 632–666, 739, 926)
public static final class Change implements Parcelable {
private SurfaceControl mLeash; // pre-created by WM Core
public SurfaceControl getLeash() { return mLeash; }
public void setLeash(@NonNull SurfaceControl leash) { mLeash = leash; }
}
Source: frameworks/base/services/core/java/com/android/server/wm/Transition.java — getLeashSurface() (lines 3039–3058)
The leash is created in WM Core during Transition.buildTransitionInfo(). Shell handlers receive the TransitionInfo, iterate over Change objects, call change.getLeash() to get the animatable surface, and apply SurfaceControl.Transaction operations to it. The actual window surfaces are invisible to Shell — Shell only ever sees and animates the leashes.
Reference files:
frameworks/base/services/core/java/com/android/server/wm/SurfaceAnimator.java— leash lifecycleframeworks/base/services/core/java/com/android/server/wm/Transition.java— leash creation for transitionsframeworks/base/core/java/android/window/TransitionInfo.java— Change.getLeash() API- SurfaceAnimator source (AOSP mirror)
17. Animation Framework Architecture
Android’s animation system is a layered architecture from app-level property animators down to compositor-level surface transforms. Understanding these layers is essential to understanding how window animations work.
17.1 Animation System Layers
graph TB
subgraph "App Layer"
OA[ObjectAnimator<br/>Property-based animation]
VA[ValueAnimator<br/>Core timing engine]
AS[AnimatorSet<br/>Composite orchestration]
LA[Legacy Animation<br/>Transformation matrices]
end
subgraph "Timing Layer"
AH[AnimationHandler<br/>Per-thread singleton]
CH[Choreographer<br/>VSYNC-driven frame sequencing]
end
subgraph "WM Server Layer"
SA[SurfaceAnimator<br/>Leash-based animation]
WA[WindowAnimator<br/>Global scheduler]
SAR[SurfaceAnimationRunner<br/>Off-thread executor]
AA[AnimationAdapter<br/>Animation abstraction]
end
subgraph "Compositor Layer"
SCT[SurfaceControl.Transaction<br/>Batched surface operations]
SF[SurfaceFlinger<br/>Native compositor]
end
OA --> VA
AS --> VA
VA --> AH
LA --> AH
AH --> CH
SA --> AA
AA --> SAR
SAR --> VA
WA --> CH
SAR --> SCT
WA --> SCT
SCT --> SF
17.2 ValueAnimator — Core Timing Engine
ValueAnimator (core/java/android/animation/ValueAnimator.java) is the foundation of all modern Android animations. It produces interpolated values between keyframes synchronized to display VSYNC.
Key fields:
mStartTime— timestamp when animation began (-1when stopped)mDuration— total animation time (default 300ms)mInterpolator— easing function (defaultAccelerateDecelerateInterpolator)mValues—PropertyValuesHolder[]defining animated propertiesmDurationScale— system-wide animation speed multiplier
Per-frame execution:
AnimationHandlercallsdoAnimationFrame(long frameTime)each VSYNC- Calculates elapsed fraction:
(currentTime - mStartTime) / (mDuration * mDurationScale) - Applies interpolator:
mInterpolator.getInterpolation(fraction)→ eased progress (0→1) - Each
PropertyValuesHolderevaluates its value at the interpolated fraction - Listeners receive
onAnimationUpdate()callback with new values
Class hierarchy:
classDiagram
class Animator {
<<abstract>>
}
class ValueAnimator {
+AnimationFrameCallback
}
class ObjectAnimator {
auto-updates target object properties
}
Animator <|-- ValueAnimator
ValueAnimator <|-- ObjectAnimator
ObjectAnimator extends ValueAnimator to automatically apply values to a target object’s properties via reflection or Property<T, V> references. Example: ObjectAnimator.ofFloat(view, "translationX", 0f, 100f) animates the view’s setTranslationX().
AnimatorSet coordinates multiple animators with timing relationships:
playTogether(Animator...)— parallel executionplaySequentially(Animator...)— sequential executionplay(A).with(B).before(C)— dependency graph
17.3 Choreographer — VSYNC Synchronization
Choreographer (core/java/android/view/Choreographer.java) synchronizes all UI operations to the display refresh rate. It processes five callback types in strict order per frame:
| Callback Type | Priority | Purpose |
|---|---|---|
CALLBACK_INPUT |
0 | Input event processing |
CALLBACK_ANIMATION |
1 | ValueAnimator/ObjectAnimator pulses |
CALLBACK_INSETS_ANIMATION |
2 | Inset animation updates |
CALLBACK_TRAVERSAL |
3 | View measure/layout/draw |
CALLBACK_COMMIT |
4 | Transaction commits, post-draw |
This ordering guarantees that animations compute new values (ANIMATION) before views lay out with those values (TRAVERSAL), and transactions commit after drawing (COMMIT).
17.4 AnimationHandler — Per-Thread Animation Scheduling
AnimationHandler (core/java/android/animation/AnimationHandler.java) is a thread-local singleton that bridges all ValueAnimator instances to Choreographer:
sequenceDiagram
participant VA as ValueAnimator
participant AH as AnimationHandler
participant CH as Choreographer
participant VSYNC as Display VSYNC
VA->>AH: addAnimationFrameCallback(this)
AH->>CH: postFrameCallback(mFrameCallback)
loop Every VSYNC
VSYNC->>CH: VSYNC signal
CH->>AH: mFrameCallback.doFrame(frameTimeNanos)
AH->>AH: doAnimationFrame(frameTime)
AH->>VA: doAnimationFrame(frameTime) [for each registered animator]
VA->>VA: Calculate fraction, interpolate, update values
VA->>VA: Notify AnimatorUpdateListeners
AH->>CH: postFrameCallback(mFrameCallback) [if animations remain]
end
When no animations are running, AnimationHandler stops posting callbacks (zero overhead). It also pauses infinite animators when all windows are backgrounded.
17.5 Legacy View Animation System
The older Animation class (core/java/android/view/animation/Animation.java) operates through Transformation matrices rather than property setters:
getTransformation(long currentTime, Transformation outTransformation)— computes matrix + alpha at given time- Subclasses:
AlphaAnimation,TranslateAnimation,ScaleAnimation,RotateAnimation,AnimationSet - Still used extensively in window animations, wrapped by
WindowAnimationSpecfor the surface animation pipeline
Key difference from ValueAnimator: Legacy animations produce a Transformation (matrix + alpha) that is applied to a surface, while ValueAnimator produces arbitrary property values. The window animation system bridges both by wrapping legacy Animation objects in AnimationSpec adapters.
17.6 SurfaceControl.Transaction — Compositor Operations
All window animations ultimately apply transforms via SurfaceControl.Transaction:
| Method | Purpose | Animation Use |
|---|---|---|
setPosition(sc, x, y) |
Move surface | Window slide/translate |
setScale(sc, scaleX, scaleY) |
Resize surface | Window scale up/down |
setAlpha(sc, alpha) |
Set opacity | Fade in/out |
setMatrix(sc, dsdx, dtdx, dtdy, dsdy) |
Affine transform | Rotation, skew |
setWindowCrop(sc, rect) |
Clip to rectangle | Reveal/conceal |
setCornerRadius(sc, radius) |
Round corners | Material rounded windows |
show(sc) / hide(sc) |
Visibility | Window appear/disappear |
reparent(sc, newParent) |
Move in tree | Animation leash management |
setFrameTimelineVsync(vsyncId) |
Timing sync | Frame-accurate composition |
Transactions are batched and applied atomically to SurfaceFlinger via apply().
Reference files:
frameworks/base/services/core/java/com/android/server/wm/AnimationAdapter.java— Interface decoupling animation implementations from SurfaceAnimatorframeworks/base/services/core/java/com/android/server/wm/LocalAnimationAdapter.java— Bridges AnimationSpec to SurfaceAnimationRunnerframeworks/base/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java— Off-thread animation executor using SurfaceFlinger VSYNCframeworks/base/services/core/java/com/android/server/wm/WindowAnimationSpec.java— Wraps legacy Animation objects for the surface animation pipeline
18. Window Animation System
The server-side window animation system uses a leash-based architecture that decouples animation transforms from the window’s actual surface hierarchy.
18.1 SurfaceAnimator — Leash Pattern
SurfaceAnimator (services/core/.../wm/SurfaceAnimator.java) is the core engine for all WM-level animations. It introduces the leash pattern:
graph LR
subgraph "Before Animation"
P1[Parent Surface] --> W1[Window Surface<br/>with content]
end
subgraph "During Animation"
P2[Parent Surface] --> L[Animation Leash<br/>receives transforms]
L --> W2[Window Surface<br/>unchanged]
end
subgraph "After Animation"
P3[Parent Surface] --> W3[Window Surface<br/>restored to parent]
end
Why leashes?
- Animation transforms (scale, translate, alpha) apply to the leash, not the window itself
- The window’s content surface remains unmodified — no layout thrashing
- Animations can be cancelled cleanly by simply removing the leash
- Animation transfer between windows is possible without interruption
Animation lifecycle:
startAnimation()— creates leash, reparents window surface under leash, hands leash toAnimationAdapter- Per-frame — adapter applies transforms to leash via
SurfaceControl.Transaction cancelAnimation()or completion — reparents surface back to original parent, destroys leash
18.2 Animation Type Constants
| Type | Value | Usage |
|---|---|---|
ANIMATION_TYPE_APP_TRANSITION |
— | Activity open/close animations |
ANIMATION_TYPE_SCREEN_ROTATION |
— | Display rotation animation |
ANIMATION_TYPE_DIMMER |
— | Dim layer behind dialogs |
ANIMATION_TYPE_WINDOW_ANIMATION |
— | Per-window enter/exit |
ANIMATION_TYPE_INSETS_CONTROL |
— | System bar show/hide |
ANIMATION_TYPE_TOKEN_TRANSFORM |
— | Token-level transform |
ANIMATION_TYPE_STARTING_REVEAL |
— | Splash screen reveal |
ANIMATION_TYPE_PREDICT_BACK |
— | Predictive back gesture |
18.3 Animatable Interface
Every WindowContainer implements the SurfaceAnimator.Animatable interface:
interface Animatable {
SurfaceControl getSurfaceControl();
SurfaceControl getAnimationLeashParent();
SurfaceControl.Builder makeAnimationLeash();
void onAnimationLeashCreated(Transaction t, SurfaceControl leash);
void onAnimationLeashLost(Transaction t);
Transaction getSyncTransaction();
int getSurfaceWidth();
int getSurfaceHeight();
}
When onAnimationLeashCreated() fires, the WindowContainer stores mAnimationLeash and calls reassignLayer() to update z-ordering. When onAnimationLeashLost() fires after animation ends, it restores the surface position via updateSurfacePosition().
18.4 AnimationAdapter and AnimationSpec
The adapter pattern decouples animation implementations from SurfaceAnimator:
classDiagram
class AnimationAdapter {
<<interface>>
+startAnimation(leash, t, type, finishCallback)
+onAnimationCancelled(leash)
+getDurationHint() long
+getShowWallpaper() boolean
}
class LocalAnimationAdapter {
-mSpec : AnimationSpec
-mAnimator : SurfaceAnimationRunner
}
class AnimationSpec {
<<interface>>
+getDuration() long
+apply(Transaction, SurfaceControl, long currentPlayTime)
}
class WindowAnimationSpec {
-mAnimation : Animation
+apply(t, leash, currentPlayTime)
}
AnimationAdapter <|.. LocalAnimationAdapter
LocalAnimationAdapter --> AnimationSpec
LocalAnimationAdapter --> SurfaceAnimationRunner
AnimationSpec <|.. WindowAnimationSpec
WindowAnimationSpec --> Animation : wraps legacy
WindowAnimationSpec wraps a legacy Animation object for the surface pipeline:
- Per-frame: calls
mAnimation.getTransformation(currentPlayTime, transformation) - Applies the resulting matrix, alpha, and clip to the leash via
SurfaceControl.Transaction
18.5 SurfaceAnimationRunner — Off-Thread Execution
SurfaceAnimationRunner (services/core/.../wm/SurfaceAnimationRunner.java) executes animations without holding the WindowManager lock, preventing animation jank from WM operations:
Threading model:
- Animations run on a dedicated
SurfaceAnimationThread - Uses
SfValueAnimator(extendsValueAnimator) with SurfaceFlinger VSYNC timing - Transaction application deferred to
CALLBACK_TRAVERSALcallback
Lifecycle:
sequenceDiagram
participant Caller
participant SAR as SurfaceAnimationRunner
participant SFVA as SfValueAnimator
participant SF as SurfaceFlinger
Caller->>SAR: startAnimation(spec, leash, t, callback)
SAR->>SAR: Add to mPendingAnimations
SAR->>SAR: Post vsync callback startAnimations()
SAR->>SFVA: Create with spec.getDuration()
SAR->>SFVA: Add update listener
loop Per VSYNC frame
SFVA->>SAR: onAnimationUpdate()
SAR->>SF: spec.apply(mFrameTransaction, leash, currentPlayTime)
SAR->>SF: scheduleApplyTransaction(vsyncId)
end
SFVA->>SAR: onAnimationEnd()
SAR->>SAR: Remove from mRunningAnimations
SAR->>Caller: Post finishCallback to AnimationThread
Note over Caller: SurfaceAnimator.reset() / removeLeash()
18.6 WindowAnimator — Global Scheduler
WindowAnimator (services/core/.../wm/WindowAnimator.java) is the singleton that drives all per-frame WM animation work:
scheduleAnimation()→ postsmAnimationFrameCallbackto Choreographeranimate(long frameTimeNs)fires each VSYNC:- For each
DisplayContent:updateWindowsForAnimator()+prepareSurfaces() - Checks
isAnimating(CHILDREN, ANIMATION_TYPE_ALL)to continue scheduling - Merges all
DisplayContent.mPendingTransactioninto a single transaction mTransaction.apply()→ sends batched surface operations to SurfaceFlinger
- For each
18.7 Complete Server-Side Animation Flow
sequenceDiagram
participant WC as WindowContainer
participant SA as SurfaceAnimator
participant LAA as LocalAnimationAdapter
participant SAR as SurfaceAnimationRunner
participant SVA as SfValueAnimator
participant Spec as WindowAnimationSpec
participant SCT as SurfaceControl.Transaction
participant SF as SurfaceFlinger
WC->>SA: startAnimation(t, adapter, type)
SA->>SA: createAnimationLeash()
SA->>SA: reparent window → leash
SA->>WC: onAnimationLeashCreated(t, leash)
SA->>LAA: startAnimation(leash, t, type, callback)
LAA->>SAR: startAnimation(spec, leash, t, callback)
SAR->>SAR: Add to mPendingAnimations
Note over SAR: Next VSYNC (SurfaceAnimation Thread)
SAR->>SVA: Create ValueAnimator, start()
loop Every VSYNC
SVA->>SVA: doAnimationFrame(frameTime)
SVA->>Spec: apply(mFrameTransaction, leash, playTime)
Spec->>Spec: mAnimation.getTransformation(playTime)
Spec->>SCT: setMatrix(leash, matrix)
Spec->>SCT: setAlpha(leash, alpha)
Spec->>SCT: setWindowCrop(leash, clipRect)
SCT->>SCT: setFrameTimelineVsync(vsyncId)
SCT->>SF: apply()
end
SVA->>SAR: onAnimationEnd()
SAR->>SA: finishCallback
SA->>SA: removeLeash()
SA->>WC: onAnimationLeashLost(t)
Reference files:
frameworks/base/services/core/java/com/android/server/wm/SurfaceAnimator.java— Leash-based animation engine with Animatable interfaceframeworks/base/services/core/java/com/android/server/wm/TransitionController.java— Transition lifecycle management replacing legacy AppTransitionframeworks/base/services/core/java/com/android/server/wm/WindowStateAnimator.java— Per-window animation state tracking
19. Shell Transition Animations
The Shell transition system (covered architecturally in §15) drives all modern window animations. This section details the animation mechanics.
19.1 Animation Resource Definitions
Window animations are defined as XML resources in frameworks/base/core/res/res/anim/:
Activity transitions (translate + alpha):
activity_open_enter.xml— Alpha (0→1, 83ms) + Translate (96dp right→0, 450ms)activity_open_exit.xml— Alpha (1→0) + Translate (0→96dp right)activity_close_enter.xml— Reverse of open_exitactivity_close_exit.xml— Reverse of open_enter
Task transitions (full-width slide):
task_open_enter.xml— Translate (105% right→0)task_open_exit.xml— Translate (0→105% right)task_close_enter/exit.xml— Reverse
Dialog animations (scale + alpha):
dialog_enter.xml— Scale (0.9→1.0) + Alpha (0→1)dialog_exit.xml— Scale (1.0→0.9) + Alpha (1→0)
Additional categories: wallpaper (8 variants), translucent activity, dream activity, voice activity, task fragment (12+ variants including directional overlays).
Animation XML structure:
<set android:shareInterpolator="false" android:hasRoundedCorners="true">
<alpha android:fromAlpha="0" android:toAlpha="1.0"
android:duration="83" android:startOffset="50"
android:interpolator="@interpolator/linear" />
<translate android:fromXDelta="96dp" android:toXDelta="0"
android:duration="450"
android:interpolator="@interpolator/fast_out_extra_slow_in" />
</set>
19.2 Animation Attribute System
Window animations are mapped through the WindowAnimation styleable (core/res/res/values/attrs.xml):
| Attribute | Transition | Direction |
|---|---|---|
activityOpenEnterAnimation |
Activity opening | Entering window |
activityOpenExitAnimation |
Activity opening | Exiting window |
activityCloseEnterAnimation |
Activity closing | Entering window |
activityCloseExitAnimation |
Activity closing | Exiting window |
taskOpenEnterAnimation |
Task opening | Entering task |
taskOpenExitAnimation |
Task opening | Exiting task |
taskCloseEnterAnimation |
Task closing | Entering task |
taskCloseExitAnimation |
Task closing | Exiting task |
taskToFrontEnterAnimation |
Task brought forward | Entering |
taskToBackExitAnimation |
Task sent back | Exiting |
wallpaperOpenEnterAnimation |
Over wallpaper open | Entering |
wallpaperCloseExitAnimation |
Over wallpaper close | Exiting |
Default bindings in Animation.Activity style (core/res/res/values/styles.xml) map these attributes to the XML animation files.
19.3 Animation Loading Pipeline
flowchart TD
A[TransitionInfo arrives in Shell] --> B[DefaultTransitionHandler.startAnimation]
B --> C[loadAnimation per Change]
C --> D{Animation type?}
D -->|Keyguard| E[loadKeyguardExitAnimation]
D -->|Custom ActivityOptions| F["ANIM_CUSTOM / ANIM_SCALE_UP /<br/>ANIM_CLIP_REVEAL / ANIM_THUMBNAIL"]
D -->|Default| G[TransitionAnimationHelper.loadAttributeAnimation]
G --> H{Transition type + isTask + isEntering?}
H --> I[Select WindowAnimation attribute]
I --> J[TransitionAnimation.loadDefaultAnimationAttr]
J --> K[AnimationUtils.loadAnimation context, resId]
K --> L[Parse XML → Animation/AnimationSet object]
L --> M[Initialize with window bounds]
M --> N[DefaultSurfaceAnimator.buildSurfaceAnimation]
N --> O[ValueAnimator 0→1, duration from Animation]
TransitionAnimationHelper.loadAttributeAnimation() maps transition type to attribute:
TRANSIT_OPEN+ isTask + entering →taskOpenEnterAnimationTRANSIT_OPEN+ !isTask + entering →activityOpenEnterAnimationTRANSIT_CLOSE+ isTask + exiting →taskCloseExitAnimationTRANSIT_TO_FRONT+ entering →taskToFrontEnterAnimation- Wallpaper and translucent variants have dedicated attribute paths
19.4 Shell Animation Execution
DefaultTransitionHandler processes each TransitionInfo.Change:
- Determine animation:
loadAnimation()selects theAnimationobject based on transition type, flags, and activity options - Initialize:
Animation.initialize(animWidth, animHeight, parentWidth, parentHeight) - Constrain:
restrictDuration(MAX_ANIMATION_DURATION)caps at 1,500ms;scaleCurrentDuration(animationScale)applies system scale - Build surface animator:
DefaultSurfaceAnimator.buildSurfaceAnimation()creates aValueAnimator(0→1) with the same duration
Per-frame surface update (DefaultSurfaceAnimator.DefaultAnimationAdapter):
sequenceDiagram
participant VA as ValueAnimator
participant Handler as onAnimationUpdate
participant T as SurfaceControl.Transaction
participant SF as SurfaceFlinger
VA->>Handler: onAnimationUpdate(animator)
Handler->>Handler: currentPlayTime = animator.getCurrentPlayTime()
Handler->>Handler: mAnimation.getTransformation(currentPlayTime, transformation)
Handler->>Handler: transformation.getMatrix().postTranslate(x, y)
Handler->>T: setMatrix(leash, matrix, scratchMatrix)
Handler->>T: setAlpha(leash, alpha)
opt Has clip rect
Handler->>T: setWindowCrop(leash, clipRect)
end
opt Has corner radius
Handler->>T: setCornerRadius(leash, radius)
end
Handler->>T: setFrameTimelineVsync(choreographer.getVsyncId())
T->>SF: apply()
19.5 TransitionInfo.Change — Animation Data
Each Change in TransitionInfo carries the data needed for animation:
| Field | Purpose |
|---|---|
getMode() |
TRANSIT_OPEN, TRANSIT_CLOSE, TRANSIT_TO_FRONT, TRANSIT_TO_BACK, TRANSIT_CHANGE |
getLeash() |
SurfaceControl that the handler animates |
getStartAbsBounds() |
Window bounds before transition |
getEndAbsBounds() |
Window bounds after transition |
getStartRelOffset() |
Position relative to parent at start |
getEndRelOffset() |
Position relative to parent at end |
getFlags() |
FLAG_TRANSLUCENT, FLAG_SHOW_WALLPAPER, FLAG_NO_ANIMATION, etc. |
getAnimationOptions() |
Custom ActivityOptions (enter/exit animation resource IDs, thumbnails) |
getTaskInfo() |
RunningTaskInfo if this is a task-level change |
getStartDisplayId() / getEndDisplayId() |
For cross-display moves |
19.6 startTransaction / finishTransaction Pattern
The two-transaction pattern ensures correct state at animation boundaries:
startTransaction — applied immediately before animation begins:
setupStartState()configures initial visibility:- Opening windows:
show()+setAlpha(0)(invisible but surface exists) - Closing windows:
show()+setAlpha(1)(visible, will fade out) - Sets identity matrix and proper crop to prevent old bounds from flashing
- Opening windows:
finishTransaction — applied after animation completes:
- Sets permanent end state (final bounds, visibility, z-order)
- Ensures surface hierarchy matches post-transition state
- Applied via
TransitionFinishCallback.onTransitionFinished(wct)
19.7 Handler Dispatch Chain
Transition handlers are checked in reverse registration order (last-added = highest priority):
flowchart TD
T[Transition Ready] --> H1{Feature-Specific Handlers?}
H1 -->|Yes: consumed| A1[Feature handler animates<br/>PiP / Split / Desktop]
H1 -->|No: pass| H2{RemoteTransitionHandler?}
H2 -->|Match found| A2[Remote process animates<br/>via IRemoteTransition]
H2 -->|No match| H3{MixedTransitionHandler?<br/>Complex multi-feature}
H3 -->|Consumed| A3[Mixed handler coordinates]
H3 -->|Pass| H4{KeyguardTransitionHandler?}
H4 -->|Keyguard transition| A4[Keyguard animates]
H4 -->|Pass| H5[DefaultTransitionHandler]
H5 --> A5[Default attribute-based animation]
Remote transitions allow cross-process animation delegation:
- Launcher registers
IRemoteTransitionfor app open/close transitions - Shell delegates animation to the remote process via Binder
- Remote receives leash
SurfaceControland applies its own transforms - When remote calls finish callback, Shell applies
finishTransaction
19.8 Multi-Track Animation
Shell supports concurrent animations via tracks:
- Each transition is assigned a track (default track 0)
- Transitions on the same track play serially (queued in
mReadyTransitions) - Transitions on different tracks play in parallel
SYNCtransitions force all prior animations to finish before starting- Merge: a new transition can merge into an active animation for seamless combination
19.9 Custom Transition Types
Beyond core TRANSIT_OPEN/CLOSE/CHANGE, Shell defines custom types for feature-specific animations:
| Custom Type | Handler | Animation |
|---|---|---|
TRANSIT_EXIT_PIP |
PipTransition | Expand from PiP bounds to fullscreen |
TRANSIT_EXIT_PIP_TO_SPLIT |
PipTransition | Expand PiP into split-screen half |
TRANSIT_SPLIT_SCREEN_PAIR_OPEN |
SplitScreenTransition | Two windows slide into halves |
TRANSIT_SPLIT_DISMISS |
SplitScreenTransition | Split collapses to single window |
TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE |
ToggleResizeHandler | 300ms bounds interpolation |
TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP |
DragToDesktopHandler | Scale-down following finger |
TRANSIT_MINIMIZE |
MinimizationHandler | Scale + offset to taskbar |
TRANSIT_PIP_BOUNDS_CHANGE |
PipTransition | Smooth PiP resize |
19.10 Key Animation Files
| File | Role |
|---|---|
core/res/res/anim/*.xml (211 files) |
Animation definitions (translate, scale, alpha) |
core/res/res/interpolator/*.xml (55+ files) |
Timing curves (Material motion) |
core/res/res/values/attrs.xml |
WindowAnimation styleable declaration |
core/res/res/values/styles.xml |
Animation.Activity default bindings |
core/java/android/animation/ValueAnimator.java |
Core timing engine |
core/java/android/animation/AnimationHandler.java |
Per-thread Choreographer bridge |
core/java/android/view/Choreographer.java |
VSYNC-driven frame sequencing |
core/java/android/view/animation/AnimationUtils.java |
XML → Animation parsing |
internal/policy/TransitionAnimation.java |
Attribute → resource loading |
services/core/.../wm/SurfaceAnimator.java |
Leash-based animation engine |
services/core/.../wm/WindowAnimator.java |
Global animation scheduler |
services/core/.../wm/SurfaceAnimationRunner.java |
Off-thread animation executor |
services/core/.../wm/LocalAnimationAdapter.java |
AnimationSpec adapter |
services/core/.../wm/WindowAnimationSpec.java |
Legacy Animation wrapper |
Shell/.../transition/Transitions.java |
Shell transition coordinator |
Shell/.../transition/DefaultTransitionHandler.java |
Default animation handler |
Shell/.../transition/DefaultSurfaceAnimator.java |
Shell per-frame surface transforms |
Shell/.../transition/TransitionAnimationHelper.java |
Type → attribute mapping |
Shell/.../transition/RemoteTransitionHandler.java |
Cross-process animation |
Reference files:
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java— Shell-side transition coordinator, handler dispatch, andTransitionHandlerinterfaceframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java— Default animation handler for standard open/close/change transitionsframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHandler.java— Handles transitions involving multiple features simultaneously
Part V: Display System
20. Display System Architecture
20.1 Display Stack Overview
The display system spans from hardware abstraction to framework APIs:
graph TB
subgraph "Public APIs"
DM_API[DisplayManager]
D_API[Display]
VD_API[VirtualDisplay]
end
subgraph "System Services"
DMS[DisplayManagerService]
LDM[LogicalDisplayMapper]
LD[LogicalDisplay]
DGA[DisplayGroupAllocator]
DG[DisplayGroup]
DTC_DISP[DisplayTopologyCoordinator]
end
subgraph "Display Adapters"
LDA[LocalDisplayAdapter]
VDA[VirtualDisplayAdapter]
DD[DisplayDevice]
end
subgraph "Window Manager"
DC[DisplayContent]
DR[DisplayRotation]
DP[DisplayPolicy]
DWS[DisplayWindowSettings]
DAP[DisplayAreaPolicy]
end
subgraph "Native"
SF[SurfaceFlinger]
end
DM_API --> DMS
DMS --> LDM
LDM --> LD
LDM --> DGA
DGA --> DG
DMS --> DTC_DISP
LDA --> DD
VDA --> DD
DD --> LDM
DMS -->|DisplayManagerInternal| DC
DC --> DR
DC --> DP
DC --> DWS
DC --> DAP
LDA --> SF
DC -->|SurfaceControl| SF
20.2 Display Device to LogicalDisplay Mapping
sequenceDiagram
participant HW as Hardware Event
participant LDA as LocalDisplayAdapter
participant DDR as DisplayDeviceRepository
participant LDM as LogicalDisplayMapper
participant LD as LogicalDisplay
participant DGA as DisplayGroupAllocator
participant DMS as DisplayManagerService
participant RWC as RootWindowContainer
participant DC as DisplayContent
HW->>LDA: Display connected
LDA->>DDR: onDisplayDeviceEventLocked(device)
DDR->>LDM: onDisplayDeviceEventLocked()
LDM->>LD: createNewLogicalDisplayLocked()
LDM->>DGA: assignDisplayGroupLocked(display)
DGA->>DGA: Determine group type
LDM->>DMS: notifyLogicalDisplayEventLocked(ADDED)
DMS->>RWC: onDisplayAdded(displayId)
RWC->>DC: new DisplayContent(displayId)
DC->>DC: DisplayAreaPolicy.instantiate()
Note over DC: DisplayArea hierarchy built
20.3 Key Display Concepts
| Concept | Class | Purpose |
|---|---|---|
| DisplayDevice | DisplayDevice.java |
Abstract representation of physical/virtual display hardware |
| LogicalDisplay | LogicalDisplay.java |
OS-level display abstraction; what apps and WM see |
| DisplayContent | DisplayContent.java |
WM-side per-display state; holds the window/DisplayArea hierarchy |
| DisplayGroup | DisplayGroup.java |
Collection of LogicalDisplays sharing power/wake state |
| Display | Display.java |
Public API handle for apps to query display properties |
| DisplayInfo | DisplayInfo.java |
System-internal detailed display information (layerStack, flags, type, metrics) |
Reference files:
frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java— WM-side per-display state, window hierarchy, and layoutframeworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java— System UI window policy (status bar, nav bar placement)frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java— Rotation state machine and sensor-driven orientation
21. DisplayManagerService
DisplayManagerService (DMS) is the central system service responsible for managing all display devices in Android. It runs on the android.display thread and coordinates between physical hardware (via SurfaceFlinger), logical display abstractions, and client-facing APIs.
21.1 Core Architecture
DMS maintains several critical data structures, all guarded by SyncRoot:
| Data Structure | Type | Purpose |
|---|---|---|
mDisplayDeviceRepo |
DisplayDeviceRepository |
Registry of all physical/virtual DisplayDevice instances |
mLogicalDisplayMapper |
LogicalDisplayMapper |
Maps DisplayDevices to LogicalDisplays, handles layout |
mDisplayPowerControllers |
SparseArray<DisplayPowerController> |
Per-display brightness and power state control |
mDisplayModeDirector |
DisplayModeDirector |
Determines desired refresh rate and display mode |
mPersistentDataStore |
PersistentDataStore |
Persists display configuration across reboots |
mVirtualDisplayAdapter |
VirtualDisplayAdapter |
Manages virtual display creation and lifecycle |
mExternalDisplayPolicy |
ExternalDisplayPolicy |
Policies for external display mirroring vs. extension |
21.2 DisplayDevice Class Hierarchy
graph TD
DD["DisplayDevice<br/>(abstract base class)"]
LDD["LocalDisplayDevice<br/>Physical panels via SurfaceFlinger"]
VDD["VirtualDisplayDevice<br/>App-created virtual displays"]
WDD["WifiDisplayDevice<br/>Miracast/WiFi Direct displays"]
ODD["OverlayDisplayDevice<br/>Developer overlay simulations"]
DD --> LDD
DD --> VDD
DD --> WDD
DD --> ODD
LDA["LocalDisplayAdapter"] -.->|"inner class"| LDD
VDA["VirtualDisplayAdapter"] -.->|"inner class"| VDD
WDA["WifiDisplayAdapter"] -.->|"inner class"| WDD
ODA["OverlayDisplayAdapter"] -.->|"inner class"| ODD
Key fields in DisplayDevice:
| Field | Type | Description |
|---|---|---|
mDisplayAdapter |
DisplayAdapter |
Owning adapter that created this device |
mDisplayToken |
IBinder |
SurfaceFlinger token identifying this display |
mUniqueId |
String |
Globally unique identifier (e.g., local:0) |
mDisplayDeviceConfig |
DisplayDeviceConfig |
Brightness curves, thermal config, HDR metadata |
mCurrentLayerStack |
int |
Assigned layer stack (-1 if unassigned) |
21.3 LogicalDisplay Internals
A LogicalDisplay represents a user-visible display. It maps to a primary DisplayDevice and derives base metrics (resolution, density, refresh rate).
Key fields:
| Field | Type | Description |
|---|---|---|
mDisplayId |
int |
Logical display ID (0 = default display) |
mLayerStack |
int |
Layer stack for SurfaceFlinger compositing |
mPrimaryDisplayDevice |
DisplayDevice |
Device driving this logical display |
mBaseDisplayInfo |
DisplayInfo |
Unmodified display info from device |
mOverrideDisplayInfo |
DisplayInfo |
WM overrides (rotation, size, density) |
mIsEnabled |
boolean |
Whether display is allowed to be ON |
mIsInTransition |
boolean |
Device-state fold/unfold transition in progress |
mDisplayGroupId |
int |
Display group for power management |
mDesiredDisplayModeSpecs |
DesiredDisplayModeSpecs |
Requested refresh rate range |
stateDiagram-v2
[*] --> Disabled: Display added
Disabled --> Enabled: Layout assigns enabled=true
Enabled --> InTransition: Device state change begins
InTransition --> Enabled: Transition complete
InTransition --> DeviceSwap: Primary device changed
DeviceSwap --> Enabled: New device assigned
Enabled --> Disabled: Layout removes display
Disabled --> [*]: Display removed
Enabled --> Blanked: Layer stack set to BLANK_LAYER_STACK
Blanked --> Enabled: Content restored
21.4 LogicalDisplayMapper Events
| Event | Description |
|---|---|
LOGICAL_DISPLAY_EVENT_ADDED |
New display appeared |
LOGICAL_DISPLAY_EVENT_REMOVED |
Display removed |
LOGICAL_DISPLAY_EVENT_SWAPPED |
Display device swapped (foldable) |
LOGICAL_DISPLAY_EVENT_CONNECTED |
External display connected |
LOGICAL_DISPLAY_EVENT_DISCONNECTED |
External display disconnected |
LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION |
Foldable state change |
21.5 Display Adapters
| Adapter | Inner Device Class | Registered During | Purpose |
|---|---|---|---|
LocalDisplayAdapter |
LocalDisplayDevice |
registerDefaultDisplayAdapters |
Physical displays from SurfaceFlinger hotplug |
VirtualDisplayAdapter |
VirtualDisplayDevice |
registerDefaultDisplayAdapters |
App-created virtual displays |
OverlayDisplayAdapter |
OverlayDisplayDevice |
registerAdditionalDisplayAdapters |
Developer simulation overlays |
WifiDisplayAdapter |
WifiDisplayDevice |
registerAdditionalDisplayAdapters |
Miracast wireless displays |
21.6 DMS Boot Sequence
sequenceDiagram
participant SS as SystemServer
participant DMS as DisplayManagerService
participant LDA as LocalDisplayAdapter
participant SF as SurfaceFlinger
SS->>DMS: constructor()
Note over DMS: Creates DisplayDeviceRepo,<br/>LogicalDisplayMapper,<br/>DisplayModeDirector
SS->>DMS: setupSchedulerPolicies()
Note over DMS: Elevates android.display and<br/>android.anim to THREAD_GROUP_TOP_APP
SS->>DMS: onStart()
DMS->>DMS: loadPersistentDataStore()
DMS->>DMS: registerDefaultDisplayAdapters()
DMS->>LDA: register LocalDisplayAdapter
LDA->>SF: Listen for hotplug events
SF-->>LDA: Physical display connected
LDA->>DMS: onDisplayDeviceEvent(ADDED)
DMS->>DMS: publishBinderService(DISPLAY_SERVICE)
SS->>DMS: onBootPhase(WAIT_FOR_DEFAULT_DISPLAY)
Note over DMS: Blocks up to 10s until<br/>DEFAULT_DISPLAY is ready
SS->>DMS: windowManagerAndInputReady()
DMS->>DMS: mLogicalDisplayMapper.onWindowManagerReady()
SS->>DMS: systemReady(safeMode)
DMS->>DMS: registerAdditionalDisplayAdapters()
Note over DMS: OverlayDisplayAdapter,<br/>WifiDisplayAdapter
DMS->>DMS: mDisplayModeDirector.start()
Key boot sequence details:
- Thread priority:
setupSchedulerPolicies()elevatesandroid.displayandandroid.animtoTHREAD_GROUP_TOP_APP - Default display gate: System blocks at
PHASE_WAIT_FOR_DEFAULT_DISPLAYup to 10s (×HW_TIMEOUT_MULTIPLIER) until LocalDisplayAdapter reports a physical display - Two-phase registration: Default adapters (Local, Virtual) during
onStart(); additional adapters (Overlay, WiFi) duringsystemReady()
21.7 Display Power Management
Each LogicalDisplay has an associated DisplayPowerController (DPC) managing power state, brightness, and color fade animations.
stateDiagram-v2
[*] --> STATE_OFF
STATE_OFF --> STATE_ON: requestPowerState(POLICY_BRIGHT)
STATE_ON --> STATE_OFF: requestPowerState(POLICY_OFF)
STATE_ON --> STATE_DOZE: requestPowerState(POLICY_DOZE)
STATE_DOZE --> STATE_ON: requestPowerState(POLICY_BRIGHT)
STATE_DOZE --> STATE_DOZE_SUSPEND: Low-power doze timeout
STATE_DOZE_SUSPEND --> STATE_DOZE: Wake from doze suspend
STATE_DOZE --> STATE_OFF: requestPowerState(POLICY_OFF)
STATE_ON --> STATE_ON_SUSPEND: Display offload
STATE_ON_SUSPEND --> STATE_ON: Exit offload
| Component | Role |
|---|---|
PowerManagerService |
Issues DisplayPowerRequest with policy (BRIGHT, DIM, DOZE, OFF) |
DisplayPowerController |
Receives request via requestPowerState(), drives state machine |
DisplayStateController |
Manages actual hardware state transitions, doze overrides |
BrightnessController |
Computes target brightness from auto-brightness, user setting, throttling |
DisplayPowerState |
Applies screen state and brightness via ColorFade animation |
21.8 WM-DMS Communication
sequenceDiagram
participant DC as DisplayContent
participant DMI as DisplayManagerInternal
participant DMS as DisplayManagerService
participant LD as LogicalDisplay
Note over DC: Layout computation complete
DC->>DMI: setDisplayInfoOverrideFromWindowManager(displayId, info)
Note over DMI: WM tells DMS about rotation,<br/>insets, cutout handling
DMI->>DMS: Forward override
DMS->>LD: setDisplayInfoOverrideFromWindowManagerLocked(info)
Note over LD: Merges override with base info
DC->>DMI: setDisplayProperties(displayId, hasContent)
Note over DMI: WM informs DMS whether<br/>display has content
DMI->>DC: onDisplayChanged()
Note over DC: DMS notifies WM of<br/>hardware changes
DisplayManagerInternal key methods:
setDisplayInfoOverrideFromWindowManager()— WM overrides display metrics (rotation, insets)getDisplayPosition()— WM queries physical display positionsetDisplayProperties()— WM reports whether display has contentperformTraversal()— WM requests SurfaceFlinger transaction execution
Reference files:
frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java— Display lifecycle management, boot sequenceframeworks/base/services/core/java/com/android/server/display/LogicalDisplay.java— OS-level display abstractionframeworks/base/services/core/java/com/android/server/display/DisplayDevice.java— Abstract hardware display representationframeworks/base/services/core/java/com/android/server/display/DisplayDeviceRepository.java— Central device registryframeworks/base/services/core/java/com/android/server/display/DisplayPowerController.java— Display power state and brightness controlframeworks/base/services/core/java/com/android/server/display/LocalDisplayAdapter.java— Physical display adapter
22. DisplayArea Hierarchy and Policy
22.1 DisplayArea Class Hierarchy
classDiagram
class WindowContainer~E~ {
+mSurfaceControl: SurfaceControl
+mChildren: List~E~
+addChild(E child)
+removeChild(E child)
}
class DisplayArea~T~ {
<<abstract>>
+Type type
+int mFeatureId
+String mName
}
class Tokens {
<<nested in DisplayArea>>
+addChild(WindowToken)
}
class Dimmable {
<<nested in DisplayArea>>
+Dimmer mDimmer
}
class RootDisplayArea {
<<root>>
+List~Feature~ mFeatures
+DisplayArea.Tokens[] mAreaForLayer
}
class DisplayContent {
<<per-display>>
+int mDisplayId
+DisplayRotation mDisplayRotation
+DisplayPolicy mDisplayPolicy
}
class DisplayAreaGroup {
<<group>>
+Orientation handling
}
class TaskDisplayArea {
<<task container>>
+SparseArray mRootTasks
}
WindowContainer <|-- DisplayArea
DisplayArea <|-- Tokens
DisplayArea <|-- Dimmable
Dimmable <|-- RootDisplayArea
RootDisplayArea <|-- DisplayContent
RootDisplayArea <|-- DisplayAreaGroup
DisplayArea <|-- TaskDisplayArea
22.2 DisplayArea Type System
DisplayAreas enforce Z-order constraints via three types:
| Type | Z-Position | Contains |
|---|---|---|
BELOW_TASKS |
Below APPLICATION_LAYER | System windows below apps (wallpaper) |
ANY |
Mixed | Tasks, TaskDisplayArea, any content |
ABOVE_TASKS |
Above APPLICATION_LAYER | System windows above apps (status bar, nav bar) |
22.3 Feature-Driven Hierarchy
DisplayAreas are organized by features - each feature targets specific window types and gets its own DisplayArea layer:
System Feature IDs (from DisplayAreaOrganizer.java):
| Feature ID | Constant | Purpose |
|---|---|---|
| 0 | FEATURE_ROOT |
Root display area |
| 1 | FEATURE_DEFAULT_TASK_CONTAINER |
Default task container (required) |
| 2 | FEATURE_WINDOW_TOKENS |
Non-activity window tokens |
| 3 | FEATURE_ONE_HANDED |
One-handed mode |
| 4 | FEATURE_WINDOWED_MAGNIFICATION |
Accessibility windowed magnification |
| 5 | FEATURE_FULLSCREEN_MAGNIFICATION |
Accessibility full-screen magnification |
| 6 | FEATURE_HIDE_DISPLAY_CUTOUT |
Display cutout hiding |
| 7 | FEATURE_IME_PLACEHOLDER |
IME container placement |
| 8 | FEATURE_IME |
IME window tokens |
| 9 | FEATURE_WINDOWING_LAYER |
All window content layer |
| 10 | FEATURE_APP_ZOOM_OUT |
App zoom-out layer |
| 10,001-20,001 | FEATURE_VENDOR_* |
Vendor-defined features |
| 20,002+ | FEATURE_RUNTIME_TASK_CONTAINER_* |
Dynamic TaskDisplayAreas |
22.4 Default DisplayArea Hierarchy
Built by DisplayAreaPolicyBuilder (DisplayAreaPolicyBuilder.java):
graph TD
ROOT["DisplayContent (FEATURE_ROOT)"] --> WM["WindowedMagnification<br/>(FEATURE_WINDOWED_MAGNIFICATION)"]
WM --> HDC["HideDisplayCutout<br/>(FEATURE_HIDE_DISPLAY_CUTOUT)"]
WM --> OH["OneHanded<br/>(FEATURE_ONE_HANDED)"]
WM --> AZO_DA["AppZoomOut<br/>(FEATURE_APP_ZOOM_OUT)"]
WM --> TOKENS_BELOW["DisplayArea.Tokens<br/>Wallpaper, below-task windows"]
WM --> TDA_DEF["TaskDisplayArea<br/>(FEATURE_DEFAULT_TASK_CONTAINER)"]
WM --> TOKENS_ABOVE_TASK["DisplayArea.Tokens<br/>Above-task windows"]
WM --> IME_PH["ImePlaceholder<br/>(FEATURE_IME_PLACEHOLDER)"]
IME_PH --> IME_CT["ImeContainer<br/>DisplayArea.Tokens"]
WM --> TOKENS_ABOVE_IME["DisplayArea.Tokens<br/>Above-IME windows"]
style ROOT fill:#e8eaf6
style TDA_DEF fill:#c8e6c9
style IME_PH fill:#fff9c4
22.5 DisplayArea Organizer API
Shell can customize the hierarchy at runtime via DisplayAreaOrganizer:
sequenceDiagram
participant Shell as Shell (Organizer)
participant DAO as DisplayAreaOrganizerController
participant DA as DisplayArea
Shell->>DAO: registerOrganizer(FEATURE_ID)
DAO->>Shell: List of DisplayAreaAppearedInfo<br/>(info + SurfaceControl leash)
Note over Shell: Shell can now position,<br/>scale, transform the<br/>DisplayArea via leash
Shell->>DAO: createTaskDisplayArea(displayId, parentFeature, name)
DAO->>DA: Create runtime TaskDisplayArea
DAO->>Shell: DisplayAreaAppearedInfo
DA->>DAO: onDisplayAreaInfoChanged()
DAO->>Shell: onDisplayAreaInfoChanged(info)
Shell-Side Organizers:
RootDisplayAreaOrganizer- Registers forFEATURE_ROOT, manages per-display root leashesRootTaskDisplayAreaOrganizer- Registers forFEATURE_DEFAULT_TASK_CONTAINER, manages task areas
Reference files:
frameworks/base/services/core/java/com/android/server/wm/DisplayAreaPolicy.java— Policy interface for DisplayArea hierarchy constructionframeworks/base/services/core/java/com/android/server/wm/DisplayArea.java— Base class for display area containers (type-based Z-ordering)frameworks/base/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java— Feature-driven hierarchy builderframeworks/base/services/core/java/com/android/server/wm/RootDisplayArea.java— Root of per-display DisplayArea tree
23. Insets System
The Insets system manages how system UI elements (status bar, navigation bar, IME, display cutout) consume screen space and how that information is communicated to application windows. Server-side state is managed by InsetsStateController and dispatched to each window’s client-side InsetsController.
23.1 Insets Architecture
graph TD
subgraph "Server Side (system_server)"
ISC["InsetsStateController<br/>Per-DisplayContent"]
ISP["InsetsSourceProvider<br/>Per insets source"]
IP["InsetsPolicy<br/>Control target decisions"]
IS["InsetsState<br/>Collection of InsetsSource"]
end
subgraph "Client Side (app process)"
IC["InsetsController<br/>Per ViewRootImpl"]
IAC["InsetsAnimationController<br/>Animation playback"]
WI["WindowInsets<br/>Immutable snapshot"]
end
ISC -->|"maintains"| IS
ISC -->|"manages N"| ISP
IP -->|"decides control targets"| ISC
ISP -->|"provides InsetsSourceControl"| IC
IS -->|"dispatched to client as"| WI
IC -->|"animates via"| IAC
IC -->|"receives"| WI
23.2 Insets in Multi-Window
When tasks are in split-screen (MULTI_WINDOW):
- Status Bar: Applies to both tasks (full width inset)
- Navigation Bar: Full width at bottom/side; both tasks must account for it
- Per-Task Insets: Each task computes its own insets relative to its bounds
- IME: When IME appears, affects only the focused task’s layout; other task maintains position
- Inset Source Override:
InsetsSourceProvidercan have per-window override frames viamOverrideFrameProviders
InsetsStateController.updateAboveInsetsState() traverses the window hierarchy top-to-bottom, updating insets for each window based on windows above it.
23.3 IME Insets Integration
The IME is a special insets source managed by ImeInsetsSourceProvider, which extends InsetsSourceProvider with IME-specific lifecycle:
sequenceDiagram
participant App as App (InsetsController)
participant WMS as WindowManagerService
participant IMMS as InputMethodManagerService
participant IME as IME Process
participant ISP as ImeInsetsSourceProvider
App->>WMS: showSoftInput()
WMS->>IMMS: showSoftInput(token, flags)
IMMS->>IME: showSoftInput()
IME->>WMS: relayout(TYPE_INPUT_METHOD)
WMS->>ISP: setServerVisible(true)
ISP->>ISP: updateSourceFrame(imeWindowFrame)
ISP-->>App: InsetsState updated (IME visible)
App->>App: InsetsAnimationController animates
Note over App: WindowInsetsAnimation callback<br/>lets app control IME animation
Key mechanisms:
-
ImeInsetsSourceProvider (
services/core/.../wm/ImeInsetsSourceProvider.java:47): ExtendsInsetsSourceProviderwith freeze/thaw semantics. ThemFrozenflag decouples server visibility from dispatch to prevent flickering during IME transitions -
IME Target Selection:
DisplayContent.getImeTarget()determines which window receives IME insets — the focused window withFLAG_NOT_FOCUSABLEnot set -
Client-side control:
InsetsControllermanages IME animations. Apps can useWindowInsetsAnimation.Callbackto synchronize layout with IME show/hide animation -
InsetsAnimationControlImpl: Drives interpolation between shown/hidden states using
SurfaceControl.Transactionto move the IME surface in sync with app content -
IME in Multi-Display: Each
DisplayContenthas its ownImeInsetsSourceProvider, enabling per-display IME support
23.4 WindowInsets.Type Constants
All insets types are defined as bit flags in WindowInsets.Type:
| Constant | Bit | Value | Description |
|---|---|---|---|
STATUS_BARS |
0 | 1 | Top status bar (time, notifications, icons) |
NAVIGATION_BARS |
1 | 2 | Bottom/side navigation bar (back, home, recents) |
CAPTION_BAR |
2 | 4 | Freeform window caption/title bar |
IME |
3 | 8 | Input method editor (soft keyboard) |
SYSTEM_GESTURES |
4 | 16 | Regions reserved for system gestures (edge swipe) |
MANDATORY_SYSTEM_GESTURES |
5 | 32 | Non-excludable system gesture areas |
TAPPABLE_ELEMENT |
6 | 64 | Areas with tappable system UI elements |
DISPLAY_CUTOUT |
7 | 128 | Hardware display cutout (notch, punch-hole) |
SYSTEM_OVERLAYS |
8 | 256 | System overlays (always-on-display elements) |
Convenience methods:
systemBars() = STATUS_BARS | NAVIGATION_BARS | CAPTION_BAR | SYSTEM_OVERLAYS
Per-type data arrays in WindowInsets:
| Array | Purpose |
|---|---|
mTypeInsetsMap[9] |
Current insets for each type |
mTypeMaxInsetsMap[9] |
Maximum possible insets for each type |
mTypeVisibilityMap[9] |
Whether each type source is visible |
mTypeBoundingRectsMap[9] |
Bounding rectangles for non-rectangular insets |
mTypeMaxBoundingRectsMap[9] |
Maximum bounding rectangles |
23.5 InsetsPolicy: Transient Bars and Behavior
InsetsPolicy determines which window gets control over insets sources. It implements transient bar behavior and coordinates with StatusBarManagerInternal.
Controllable types:
CONTROLLABLE_TYPES = WindowInsets.Type.statusBars()
| WindowInsets.Type.navigationBars()
| WindowInsets.Type.ime();
Control targets maintained by InsetsPolicy:
| Target | Purpose |
|---|---|
mShowingTransientControlTarget |
Shows system bars transiently (no layout impact) |
mShowingPermanentControlTarget |
Shows system bars permanently (affects layout) |
mHidingPermanentControlTarget |
Hides system bars permanently (affects layout) |
mFakeStatusControlTarget |
Overrides status bar visibility during transient state |
mFakeNavControlTarget |
Overrides nav bar visibility during transient state |
Transient bar lifecycle:
graph TD
A["App requests immersive mode<br/>BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE"] --> B["System bars hidden<br/>Layout unaffected"]
B --> C{"User swipes from edge?"}
C -->|"Yes"| D["showTransient(types)"]
D --> E["Bars appear as overlay<br/>mShowingTransientTypes updated"]
E --> F["StatusBarManagerInternal<br/>notified"]
F --> G{"Timeout or interaction?"}
G -->|"Timeout"| H["hideTransient()"]
G -->|"Focus change"| I["abortTransient()"]
H --> J["mHidingTransientTypes set<br/>Animation plays"]
J --> B
I --> B
23.6 Insets Dispatch Flow
graph TD
A["System bar window<br/>position/visibility changes"] --> B["InsetsSourceProvider<br/>updateSourceFrame()"]
B --> C["InsetsStateController<br/>notifyInsetsChanged()"]
C --> D["For each WindowState:<br/>w.notifyInsetsChanged()"]
D --> E["WindowState computes<br/>visible InsetsState for client"]
E --> F["IPC: dispatchInsetsChanged()<br/>to client ViewRootImpl"]
F --> G["Client InsetsController<br/>receives new InsetsState"]
G --> H{"Animation in progress?"}
H -->|"No"| I["Apply insets immediately<br/>View.onApplyWindowInsets()"]
H -->|"Yes"| J["InsetsAnimationController<br/>interpolates between states"]
J --> K["View.onProgress() callback<br/>with interpolated insets"]
K --> L["Animation ends"]
L --> I
InsetsSourceProvider key responsibilities:
| Method | Purpose |
|---|---|
updateSourceFrame() |
Recalculates source frame from window’s layout |
updateControlForTarget() |
Assigns or revokes control to an InsetsControlTarget |
onPostLayout() |
Updates source position after layout pass |
setServerVisible() |
Sets visibility from server-side policy |
updateFakeControlTarget() |
Manages fake control for transient bar hiding |
Reference files:
frameworks/base/core/java/android/view/WindowInsets.java— Type constants, per-type data arraysframeworks/base/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java— IME insets source managementframeworks/base/core/java/android/view/InsetsController.java— Client-side insets animation controlframeworks/base/core/java/android/view/InsetsAnimationControlImpl.java— Animation interpolation engineframeworks/base/services/core/java/com/android/server/wm/InsetsSourceProvider.java— Base insets source providerframeworks/base/services/core/java/com/android/server/wm/InsetsStateController.java— Per-display insets state managementframeworks/base/services/core/java/com/android/server/wm/InsetsPolicy.java— Transient bars, control target resolution
24. Surface System and Composition
24.1 Surface Hierarchy
Every WindowContainer in the WM hierarchy maps 1:1 to a SurfaceControl in SurfaceFlinger, creating a parallel tree:
graph LR
subgraph "WindowContainer Tree"
RWC_W[RootWindowContainer]
DC_W[DisplayContent]
TDA_W[TaskDisplayArea]
T_W[Task]
AR_W[ActivityRecord]
WS_W[WindowState]
end
subgraph "SurfaceControl Tree"
RWC_S[Root SurfaceControl]
DC_S[Display SurfaceControl]
TDA_S[TDA SurfaceControl]
T_S[Task SurfaceControl]
AR_S[Activity SurfaceControl]
WS_S[Window SurfaceControl]
end
subgraph "SurfaceFlinger"
L1["Layer: Root"]
L2["Layer: Display"]
L3["Layer: TDA"]
L4["Layer: Task"]
L5["Layer: Activity"]
L6["Layer: Window"]
end
RWC_W -.-> RWC_S
DC_W -.-> DC_S
TDA_W -.-> TDA_S
T_W -.-> T_S
AR_W -.-> AR_S
WS_W -.-> WS_S
RWC_S -.-> L1
DC_S -.-> L2
TDA_S -.-> L3
T_S -.-> L4
AR_S -.-> L5
WS_S -.-> L6
24.2 SurfaceControl API (SurfaceControl.java)
Key operations via SurfaceControl.Transaction (atomic batching):
| Operation | Purpose |
|---|---|
show(sc) / hide(sc) |
Visibility control |
setPosition(sc, x, y) |
Position on display |
setAlpha(sc, alpha) |
Opacity (0.0-1.0) |
setLayer(sc, z) / setRelativeLayer(sc, relative, z) |
Z-ordering |
reparent(sc, newParent) |
Move in surface tree |
setMatrix(sc, dsdx, dtdx, dtdy, dsdy) |
Transformation (rotation, scale) |
apply() |
Synchronous commit to SurfaceFlinger |
24.3 BLAST Sync Mechanism
BLAST (Binary-Layer-Synchronized Transactions) synchronizes WM surface operations with app buffer production:
sequenceDiagram
participant WM as WindowManager
participant BSE as BLASTSyncEngine
participant VRI as ViewRootImpl (App)
participant SF as SurfaceFlinger
WM->>BSE: startSyncSet(listener)
WM->>BSE: addToSyncSet(syncId, windowContainer)
WM->>BSE: setReady(syncId)
Note over BSE: Wait for all windows to draw
BSE->>VRI: Relayout notification
VRI->>VRI: Draw new frame
VRI->>BSE: finishDrawing(transaction)
Note over BSE: All windows have drawn
BSE->>WM: onTransactionReady(syncId, mergedTransaction)
WM->>SF: Apply merged transaction atomically
Note over SF: All surfaces update<br/>in same frame
Key property: BLAST ensures that when multiple windows change simultaneously (e.g., during a transition), all their buffer updates appear in the same SurfaceFlinger frame, preventing visual tearing.
24.4 SurfaceFlinger Composition Pipeline
graph LR
subgraph "SurfaceFlinger (Native)"
CT[commitTransactions<br/>Apply pending changes]
PLC[prepareLayersForComposition<br/>Compute visible regions]
DCJ[doCompositionFrameJob<br/>Render or HWC]
PD[presentDisplays<br/>Output to hardware]
end
subgraph "Composition Backends"
HWC[HWComposer<br/>Hardware overlay]
RE[RenderEngine<br/>GPU composition]
end
CT --> PLC --> DCJ --> PD
DCJ --> HWC
DCJ --> RE
SurfaceFlinger key files:
frameworks/native/services/surfaceflinger/SurfaceFlinger.h- Main compositorframeworks/native/services/surfaceflinger/Layer.h- Individual compositable layer (maps to SurfaceControl)frameworks/native/services/surfaceflinger/DisplayDevice.h- Physical/virtual display output
Reference files:
frameworks/base/core/java/android/view/SurfaceControl.java— Java API for SurfaceFlinger surface operationsframeworks/native/services/surfaceflinger/SurfaceFlinger.h— Native compositor main headerframeworks/native/services/surfaceflinger/Layer.h— Compositable layer (maps to SurfaceControl)frameworks/native/services/surfaceflinger/DisplayDevice.h— Physical/virtual display outputframeworks/base/services/core/java/com/android/server/wm/BLASTSyncEngine.java— BLAST transaction synchronization for frame-level consistency
25. Window-Display Interaction Model
This section describes how windows and displays interact through the WM hierarchy, covering the data flow from app requests through layout computation to surface placement, as well as cross-display task management.
25.1 Complete Data Flow: Window Change to Display Output
sequenceDiagram
participant App as Application
participant VRI as ViewRootImpl
participant WMS as WindowManagerService
participant DC as DisplayContent
participant DMI as DisplayManagerInternal
participant DMS as DisplayManagerService
participant LD as LogicalDisplay
participant SF as SurfaceFlinger
participant HW as Display Hardware
App->>VRI: requestLayout()
VRI->>WMS: relayout(IWindow, params)
WMS->>DC: performLayout()
Note over DC: Compute window frames,<br/>bounds, insets
DC->>DMI: setDisplayInfoOverrideFromWindowManager()
DMI->>DMS: Forward rotation/insets override
DMS->>LD: setDisplayInfoOverrideFromWindowManagerLocked()
WMS->>VRI: Relayout result (new bounds)
VRI->>VRI: Draw to Surface buffer
VRI->>WMS: finishDrawing(transaction)
WMS->>SF: SurfaceControl.Transaction.apply()
SF->>SF: commitTransactions()
SF->>SF: Compose layers
SF->>HW: Present frame
25.2 Relationship Summary
graph TB
subgraph "Display System"
DD[DisplayDevice<br/>Hardware abstraction]
LD[LogicalDisplay<br/>OS-level display]
DG[DisplayGroup<br/>Power grouping]
DT[DisplayTopology<br/>Spatial arrangement]
end
subgraph "Window System"
DC[DisplayContent<br/>WM per-display state]
DAH[DisplayArea Hierarchy<br/>Window organization]
TDA[TaskDisplayArea<br/>Task container]
Tasks[Tasks and Windows<br/>Application content]
end
subgraph "Surface System"
SCTree[SurfaceControl Tree<br/>Parallel hierarchy]
BLAST[BLAST Sync<br/>Frame synchronization]
SF[SurfaceFlinger<br/>Composition]
end
DD -->|"1:1 mapping"| LD
LD -->|"1:1 via displayId"| DC
LD --> DG
LD --> DT
DC --> DAH
DAH --> TDA
TDA --> Tasks
DC -.->|"SurfaceControl mirrors<br/>WindowContainer tree"| SCTree
Tasks -.-> SCTree
SCTree --> BLAST
BLAST --> SF
SF -->|"Output to"| DD
style DD fill:#ffcdd2
style DC fill:#bbdefb
style SCTree fill:#c8e6c9
Key relationships:
- DisplayDevice - LogicalDisplay: 1:1 mapping (n:1 possible for mirroring); managed by
LogicalDisplayMapper - LogicalDisplay - DisplayContent: 1:1 via
displayId; DMS creates LogicalDisplay, WM creates corresponding DisplayContent - DisplayContent - SurfaceControl: Each WindowContainer creates a corresponding SurfaceControl, forming a parallel tree
- WM - DMS communication: Via
DisplayManagerInternal— WM sends display info overrides; DMS sends hardware change notifications - WM - SurfaceFlinger: Via
SurfaceControl.Transaction— BLAST sync ensures frame-level consistency - SurfaceFlinger - DisplayDevice: Native compositor outputs composed frames to physical hardware
25.3 DisplayPolicy: System Window Layout Rules
DisplayPolicy governs how system windows (status bar, navigation bar, IME) are positioned and how they affect other window layouts. Instantiated per DisplayContent.
System window tracking:
| Field | Type | Description |
|---|---|---|
mStatusBar |
WindowState |
TYPE_STATUS_BAR window reference |
mNavigationBar |
WindowState |
TYPE_NAVIGATION_BAR window reference |
mNotificationShade |
WindowState |
TYPE_NOTIFICATION_SHADE window reference |
mInsetsSourceWindowsExceptIme |
ArraySet<WindowState> |
All insets-producing windows except IME |
Key methods:
| Method | Purpose |
|---|---|
addWindowLw(win, attrs) |
Registers system windows and adds to insets source set |
simulateLayoutDisplay(displayFrames) |
Computes layout for insets sources without applying changes; used for configuration computation |
layoutWindowLw(win, attached, displayFrames) |
Computes frame for a single window based on type, layout params, display frames |
beginPostLayoutPolicyLw() |
Resets post-layout policy state before window iteration |
finishPostLayoutPolicyLw() |
Finalizes policy decisions (system bar appearance, screen-on behavior) |
25.4 Task Reparenting Across Displays
Tasks can be moved between displays using RootWindowContainer.moveRootTaskToDisplay(), used for multi-display scenarios such as moving an app from phone screen to external monitor.
graph TD
A["moveRootTaskToDisplay(rootTaskId, displayId, onTop)"] --> B["getDisplayContentOrCreate(displayId)"]
B --> C["moveRootTaskToTaskDisplayArea(rootTaskId, defaultTDA, onTop)"]
C --> D{"rootTask exists?"}
D -->|"No"| E["throw IllegalArgumentException"]
D -->|"Yes"| F{"Same display area?"}
F -->|"Yes"| G["throw IllegalArgumentException"]
F -->|"No"| H["rootTask.reparent(targetTDA, onTop)"]
H --> I["Task hierarchy reparented<br/>in WindowContainer tree"]
I --> J["rootTask.resumeNextFocusAfterReparent()"]
J --> K["Resume top activity<br/>on new display"]
Constraints:
- Caller must hold
INTERNAL_SYSTEM_WINDOWpermission - Pinned (PiP) tasks cannot be reparented across displays
- Target display must exist and have a valid
TaskDisplayArea - Split-screen tasks may need resizing for new display dimensions
25.5 DisplayContent.applySurfaceChangesTransaction()
This method is the core of the window layout and surface update pipeline. It orchestrates the full layout-to-surface cycle for a single display during each traversal:
graph TD
A["applySurfaceChangesTransaction()"] --> B["beginHoldScreenUpdate()"]
B --> C{"FINISH_LAYOUT_REDO_WALLPAPER?"}
C -->|"Yes"| D["WallpaperController<br/>adjustWallpaperWindows()"]
C -->|"No"| E{"FINISH_LAYOUT_REDO_LAYOUT?"}
D --> E
E -->|"Yes"| F["setLayoutNeeded()"]
E -->|"No"| G["InsetsStateController.onPreLayout()"]
F --> G
G --> H["Phase 1: Layout root windows<br/>forAllWindows(mPerformLayout)"]
H --> I["Phase 2: Layout attached windows<br/>forAllWindows(mPerformLayoutAttached)"]
I --> J["Phase 3: Post-layout policy"]
J --> K["DisplayPolicy.beginPostLayoutPolicyLw()"]
K --> L["forAllWindows(mApplyPostLayoutPolicy)"]
L --> M["DisplayPolicy.finishPostLayoutPolicyLw()"]
M --> N["InsetsStateController.onPostLayout()"]
N --> O["Phase 4: Apply surface changes<br/>forAllWindows(mApplySurfaceChangesTransaction)"]
O --> P["setDisplayProperties()<br/>hasContent, preferredRefreshRate"]
| Phase | Method | Purpose |
|---|---|---|
| 1. Layout root | forAllWindows(mPerformLayout) |
Compute frames for non-attached windows |
| 2. Layout attached | forAllWindows(mPerformLayoutAttached) |
Compute frames for child windows based on parent |
| 3. Post-layout policy | begin/finishPostLayoutPolicyLw() |
System UI visibility, screen-on, focus |
| 4. Surface changes | forAllWindows(mApplySurfaceChangesTransaction) |
Position surfaces, update visibility |
Reference files:
frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java—assignChildLayers(),applySurfaceChangesTransaction(),performLayout()frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java— System window layout rules and post-layout policyframeworks/base/services/core/java/com/android/server/wm/WindowSurfacePlacer.java— Coordinates surface placement passes across all displaysframeworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java—moveRootTaskToDisplay()for cross-display task reparenting
Part VI: Multi-Window System
26. Multi-Window Architecture
Android’s multi-window architecture allows multiple activities to be visible simultaneously through split-screen, freeform, and picture-in-picture modes. The system manages this through windowing mode constants, root task organization within TaskDisplayArea, and a modifier chain that resolves launch parameters.
26.1 Multi-Window Overview
graph TD
subgraph "WM Core - Multi-Window Policy"
WC[WindowConfiguration<br/>Windowing Modes]
LPC[LaunchParamsController<br/>Bounds Calculation]
TLPM[TaskLaunchParamsModifier<br/>Default Bounds]
DMLPM[DesktopModeLaunchParamsModifier<br/>Freeform Bounds]
PTC[PinnedTaskController<br/>PiP Policy]
end
subgraph "WM Shell - Multi-Window Presentation"
SSC[SplitScreenController<br/>Split Screen]
PipC[PipTaskOrganizer<br/>Picture-in-Picture]
FTL[FreeformTaskListener<br/>Freeform Windows]
DTC[DesktopTasksController<br/>Desktop Mode]
end
subgraph "Task Hierarchy"
TDA[TaskDisplayArea]
T_FS[Task<br/>FULLSCREEN]
T_MW[Task<br/>MULTI_WINDOW]
T_PIN[Task<br/>PINNED]
T_FREE[Task<br/>FREEFORM]
end
WC --> LPC
LPC --> TLPM
LPC --> DMLPM
WC --> PTC
TDA --> T_FS
TDA --> T_MW
TDA --> T_PIN
TDA --> T_FREE
T_MW --> SSC
T_PIN --> PipC
T_FREE --> FTL
T_FREE --> DTC
26.2 Internal Flow
- Activity Launch:
ActivityStarterprocesses launch request, consultsLaunchParamsController - Bounds Calculation: Chain of
LaunchParamsModifierinstances calculates target windowing mode and bounds - Task Creation/Reuse: Task created in appropriate windowing mode within
TaskDisplayArea - Shell Notification:
ShellTaskOrganizerdispatches to appropriate listener based on windowing mode - Surface Management: Shell feature controller manages the task’s
SurfaceControlleash for positioning and animation
26.3 Windowing Mode Resolution During Launch
When ActivityStarter launches a new activity, it resolves the windowing mode through TaskLaunchParamsModifier.onCalculate():
flowchart TD
A[Activity Launch Request] --> B{ActivityOptions has<br/>launchWindowingMode?}
B -->|Yes| C[Use options windowing mode]
B -->|No| D{Can inherit from<br/>source activity?}
D -->|Yes| E[Use source task windowing mode]
D -->|No| F{Task already on<br/>target DisplayArea?}
F -->|Yes| G[Inherit task windowing mode]
F -->|No| H{DisplayArea is<br/>freeform?}
H -->|Yes| I{Activity is<br/>resizable?}
I -->|Yes| J[WINDOWING_MODE_FREEFORM]
I -->|No| K[WINDOWING_MODE_FULLSCREEN]
H -->|No| L[Inherit DisplayArea<br/>windowing mode]
C --> M[Resolve bounds for mode]
E --> M
G --> M
J --> M
K --> M
L --> M
26.4 TaskDisplayArea Root Task Organization
TaskDisplayArea maintains cached references to special root tasks:
private Task mRootHomeTask; // ACTIVITY_TYPE_HOME
private Task mRootPinnedTask; // WINDOWING_MODE_PINNED (at most one)
Root task creation is governed by DisplayContent.alwaysCreateRootTask():
| Windowing Mode / Activity Type | Always Create? | Behavior |
|---|---|---|
| FULLSCREEN + STANDARD | Yes | Each task gets its own root task |
| FREEFORM + STANDARD | Yes | Each task gets its own root task |
| MULTI_WINDOW + STANDARD | Yes | Each task gets its own root task |
| PINNED + STANDARD | Yes | Single root task, replaces previous PiP |
| Any + HOME | No | Reuses singleton root home task |
| Any + ASSISTANT | No | Reuses singleton root assistant task |
| Any + DREAM | No | Reuses singleton root dream task |
Launch root tasks allow Shell organizers to register specific root tasks as launch targets:
static private class LaunchRootTaskDef {
Task task;
int[] windowingModes;
int[] activityTypes;
}
private final ArrayList<LaunchRootTaskDef> mLaunchRootTasks = new ArrayList<>();
26.5 LaunchParamsController Modifier Chain
LaunchParamsController coordinates resolution through a chain of LaunchParamsModifier instances iterated in reverse registration order (last registered = first evaluated):
| Order | Modifier | Responsibility |
|---|---|---|
| 1 | TaskLaunchParamsModifier |
Default: display selection, windowing mode, freeform bounds (cascading, layout hints) |
| 2 | DesktopModeLaunchParamsModifier |
Desktop overrides: snap-to-edge, desktop bounds, compat policy |
Resolution phases:
| Phase | Constant | Guarantees |
|---|---|---|
PHASE_DISPLAY |
0 | Preferred display determined |
PHASE_WINDOWING_MODE |
1 | Windowing mode resolved |
PHASE_DISPLAY_AREA |
2 | Target TaskDisplayArea selected |
PHASE_BOUNDS |
3 | Final bounds calculated |
Modifier return values:
| Result | Value | Behavior |
|---|---|---|
RESULT_SKIP |
0 | No opinion; skip output |
RESULT_DONE |
1 | Merge result and stop chain |
RESULT_CONTINUE |
2 | Merge result and continue |
Reference files:
frameworks/base/services/core/java/com/android/server/wm/WindowOrganizerController.java— Server-side controller for WindowContainerTransaction requestsframeworks/base/services/core/java/com/android/server/wm/LaunchParamsController.java— Chains LaunchParamsModifier instancesframeworks/base/services/core/java/com/android/server/wm/TaskDisplayArea.java— Root task management, getOrCreateRootTask()frameworks/base/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java— Default bounds calculation with 75dp cascade offset
27. Windowing Modes and Bounds
The windowing mode of a container determines how its bounds are computed, how it interacts with other windows, and what resize behaviors are permitted. Bounds flow through a configuration hierarchy that resolves requested overrides against parent constraints.
27.1 Windowing Modes
| Mode | Value | Bounds Source | Resizable | Z-Order |
|---|---|---|---|---|
FULLSCREEN |
1 | Display bounds | N/A | Normal |
MULTI_WINDOW |
6 | Shell-provided split bounds | Required | Normal |
FREEFORM |
5 | User drag / layout hints | Required | Normal |
PINNED |
2 | System-calculated corner rect | N/A | Always-on-top |
UNDEFINED |
0 | Inherited from parent | Inherited | Inherited |
Helper methods in WindowConfiguration:
inMultiWindowMode(): True for any non-fullscreen, non-undefined modetasksAreFloating(): True for FREEFORM or PINNEDcanResizeTask(): True for FREEFORM or MULTI_WINDOW
27.2 Bounds Computation
Bounds hierarchy per WindowContainer:
mBounds: Complete bounds within parent (includes system bars)mAppBounds: Application drawable area (respecting insets)mMaxBounds: Maximum bounds available (for max window metrics API)
27.3 Launch Params Calculation
sequenceDiagram
participant AS as ActivityStarter
participant LPC as LaunchParamsController
participant DMLPM as DesktopModeLaunchParamsModifier
participant TLPM as TaskLaunchParamsModifier
participant LP as LaunchParams
AS->>LPC: calculate(task, layout, activity, source, options)
LPC->>DMLPM: onCalculate(PHASE_DISPLAY)
Note over DMLPM: Desktop mode display selection
LPC->>TLPM: onCalculate(PHASE_DISPLAY)
Note over TLPM: Default display selection
LPC->>DMLPM: onCalculate(PHASE_WINDOWING_MODE)
LPC->>TLPM: onCalculate(PHASE_WINDOWING_MODE)
LPC->>DMLPM: onCalculate(PHASE_BOUNDS)
Note over DMLPM: Freeform: cascade offset,<br/>orientation adjustment
LPC->>TLPM: onCalculate(PHASE_BOUNDS)
Note over TLPM: Default: from options,<br/>WindowLayout hints,<br/>75dp cascade offset
LPC->>LP: Final result
LP->>AS: mBounds, mWindowingMode,<br/>mPreferredTaskDisplayArea
27.4 Bounds Resolution Chain
The bounds resolution chain in ConfigurationContainer maintains three distinct configuration layers:
graph LR
A["mRequestedOverrideConfiguration<br/>(what was asked for)"] --> B["resolveOverrideConfiguration()<br/>(apply parent constraints)"]
B --> C["mResolvedOverrideConfiguration<br/>(validated result)"]
C --> D["mFullConfiguration<br/>(parent config + resolved override)"]
D --> E["mMergedOverrideConfiguration<br/>(accumulated overrides top-down)"]
Requested bounds are set by the task organizer, activity options, or layout hints. Resolved bounds are produced by resolveOverrideConfiguration(), which each subclass overrides to apply constraints:
TaskFragment.resolveOverrideConfiguration()enforces:- Clearing bounds for tasks not allowed to override bounds from ancestors
- Translating relative embedded bounds to absolute bounds for embedded
TaskFragments - Forcing home activities to
WINDOWING_MODE_FULLSCREENeven on freeform displays - Preventing non-multi-window-capable tasks from entering multi-window modes (except pinned)
| Bounds Field | In WindowConfiguration |
Description |
|---|---|---|
mBounds |
getBounds() |
Outer bounds including insets |
mAppBounds |
getAppBounds() |
Usable area excluding system bar insets |
mMaxBounds |
getMaxBounds() |
Maximum bounds reported to WindowManager.getMaximumWindowMetrics() |
27.5 Size Compatibility Mode
When an app cannot handle available display bounds (e.g., a fixed-orientation phone app on a landscape tablet), the system enters size compatibility mode, managed by AppCompatSizeCompatModePolicy.
Triggers:
- Activity has a fixed orientation that does not match the display orientation
- Activity is not resizable and display bounds differ from last compatible bounds
- Resolved app bounds differ from container app bounds
Behaviors:
| Behavior | Description |
|---|---|
| Letterboxing | Bars added around the activity to maintain aspect ratio |
| Scaling | Activity renders at compatible size; system scales output to fill available space |
| Restart button | UI affordance allowing the user to restart at the new size |
The compatibility scale factor: compatScale = activityAppBoundsWidth / containerAppBoundsWidth
Letterbox position is controlled by LetterboxUiController, supporting configurable alignment (center, start, end) and background options (solid color, wallpaper blur).
27.6 Minimum Dimensions and Resize Limits
Activities declare minimum dimensions through <layout> attributes in the manifest:
<activity android:name=".MyActivity">
<layout android:minWidth="400dp"
android:minHeight="300dp"
android:defaultWidth="600dp"
android:defaultHeight="450dp"
android:gravity="center" />
</activity>
Resize mode constants from ActivityInfo:
| Constant | Value | Description |
|---|---|---|
RESIZE_MODE_UNRESIZEABLE |
0 | Cannot be resized; forced fullscreen on large screens |
RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION |
1 | Not explicitly resizable, but SDK implies support |
RESIZE_MODE_RESIZEABLE |
2 | Explicitly supports resizing (default) |
RESIZE_MODE_RESIZEABLE_AND_PIPABLE_DEPRECATED |
3 | Deprecated: was resizable + PiP |
RESIZE_MODE_FORCE_RESIZEABLE |
4 | System forces resizability |
RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY |
5 | Force-resized only in landscape |
RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY |
6 | Force-resized only in portrait |
RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION |
7 | Force-resized preserving declared orientation |
TaskLaunchParamsModifier respects these constraints during freeform bounds calculation, using a cascading offset of 75dp (CASCADING_OFFSET_DP) and a collision threshold of 4px (BOUNDS_CONFLICT_THRESHOLD).
27.7 Split Screen Internals
Split Positions:
SPLIT_POSITION_TOP_OR_LEFT(0)SPLIT_POSITION_BOTTOM_OR_RIGHT(1)
Snap Targets (from SplitScreenConstants.java):
SNAP_TO_2_50_50— Equal 50/50 splitSNAP_TO_2_33_66/SNAP_TO_2_66_33— One-third / two-thirdsSNAP_TO_2_90_10/SNAP_TO_2_10_90— Minimized splitsSNAP_TO_3_*— Three-app split configurations
The StageCoordinator manages two stages, each backed by a root task. Tasks are reparented into stages via WindowContainerTransaction, using WINDOWING_MODE_MULTI_WINDOW.
Reference files:
frameworks/base/core/java/android/app/WindowConfiguration.java— Defines WINDOWING_MODE constants, bounds hierarchy fieldsframeworks/base/services/core/java/com/android/server/wm/ConfigurationContainer.java— Configuration resolution chainframeworks/base/services/core/java/com/android/server/wm/TaskFragment.java— resolveOverrideConfiguration() with windowing mode enforcementframeworks/base/core/java/android/content/pm/ActivityInfo.java— RESIZE_MODE constants and WindowLayoutframeworks/base/services/core/java/com/android/server/wm/TaskDisplayArea.java— Manages root tasks per windowing mode
28. Multi-Window Operations
This section covers the concrete operations that manipulate windows in multi-window modes: resize, maximize, minimize, snap/tiling, close, drag-to-desktop, PiP operations, and split-screen operations. For cross-app data drag-and-drop (including split-screen drop targets), see §50. For caption bar drag-to-move/resize mechanics, see §49.7.
28.1 Window Resize
Freeform window resizing uses a three-component pipeline: input capture, bounds calculation, and transaction application.
Input Capture — DragResizeInputListener (windowdecor/DragResizeInputListener.java):
- Registers spy input channels on task edges via
INPUT_FEATURE_SPY - Detects resize direction using
CTRL_TYPEbitmask constants fromDragPositioningCallback:
| Constant | Value | Description |
|---|---|---|
CTRL_TYPE_UNDEFINED |
0 | Pure drag/move (no resize) |
CTRL_TYPE_LEFT |
1 | Resize from left edge |
CTRL_TYPE_RIGHT |
2 | Resize from right edge |
CTRL_TYPE_TOP |
4 | Resize from top edge |
CTRL_TYPE_BOTTOM |
8 | Resize from bottom edge |
Corner resizes combine flags (e.g., CTRL_TYPE_LEFT | CTRL_TYPE_TOP = 5 for top-left corner). Cursor icons update to match direction (diagonal arrows, horizontal/vertical double arrows).
Bounds Calculation & Application — FluidResizeTaskPositioner (windowdecor/FluidResizeTaskPositioner.java):
- Implements both
TaskPositionerandTransitions.TransitionHandler - Applies resize bounds in real-time via
WindowContainerTransactionduring drag, then animates final state via shell transitions
sequenceDiagram
participant User as User Drag
participant DRIL as DragResizeInputListener
participant FRTP as FluidResizeTaskPositioner
participant WCT as WindowContainerTransaction
participant STO as ShellTaskOrganizer
participant Trans as Transitions
User->>DRIL: ACTION_DOWN on edge
DRIL->>DRIL: calculateCtrlType()
DRIL->>FRTP: onDragPositioningStart(ctrlType, x, y)
FRTP->>FRTP: Save taskBoundsAtDragStart, stableBounds
User->>DRIL: ACTION_MOVE (repeated)
DRIL->>FRTP: onDragPositioningMove(x, y)
FRTP->>WCT: setDragResizing(token, true) [first move only]
FRTP->>WCT: setBounds(token, newBounds)
FRTP->>STO: applyTransaction(wct)
User->>DRIL: ACTION_UP
DRIL->>FRTP: onDragPositioningEnd(x, y)
FRTP->>WCT: setDragResizing(token, false)
FRTP->>WCT: setBounds(token, finalBounds)
FRTP->>Trans: startTransition(TRANSIT_CHANGE, wct)
Key WCT methods used:
setBounds(token, Rect)— updatesWindowConfigurationboundssetDragResizing(token, boolean)— signals drag state to WM Core (flagCHANGE_DRAG_RESIZING=1 << 7), which optimizes rendering by suppressing certain animations during resize
28.2 Window Maximize and Restore
Managed by DesktopTasksController.toggleDesktopTaskSize() in desktopmode/DesktopTasksController.kt.
Maximize Flow:
- User clicks maximize button in window header
- Current bounds saved via
repository.saveBoundsBeforeMaximize() - Destination calculated via
calculateMaximizeBounds()(stable bounds of display) - If task is tiled,
snapEventHandler.removeTaskIfTiled()releases it ToggleResizeDesktopTaskTransitionHandlerstarts transition withTRANSIT_DESKTOP_MODE_TOGGLE_RESIZE
Restore Flow:
- User clicks restore button (task already maximized)
- Saved pre-maximize bounds retrieved, or default bounds calculated via
calculateDefaultDesktopTaskBounds() - Same transition handler animates back to original size
Animation: 300ms bounds interpolation using RectEvaluator, with per-frame SurfaceControl position/crop/scale updates via Choreographer vsync timing.
Drag-to-Maximize: Dragging task header to top edge triggers dragToMaximizeDesktopTask(), which calls the same toggleDesktopTaskSize() with Direction.MAXIMIZE.
28.3 Window Minimize
Minimize reorders the task to the back rather than hiding it, making it recoverable from the taskbar.
Flow (DesktopTasksController.minimizeTask()):
- Task removed from tiling if applicable
- Check for PiP conversion (if
ENABLE_DESKTOP_WINDOWING_PIPflag is set, minimize can convert to PiP instead) - Regular minimize path:
wct.reorder(taskInfo.token, false /* onTop */)pushes task behind others - Desktop exit: If last visible task is minimized,
performDesktopExitCleanUp()brings home/wallpaper forward
Animation: DesktopMinimizationTransitionHandler handles the visual transition.
Transition Types:
TRANSIT_MINIMIZE— standard minimizeTRANSIT_DESKTOP_MODE_TASK_LIMIT_MINIMIZE— minimize due to task count limitTRANSIT_TO_BACK— minimize via back navigation
28.4 Window Snap and Tiling
Snap resizes a freeform task to occupy half the screen (left or right), enabling side-by-side tiling.
Entry Points:
- Drag to edge:
handleSnapResizingTaskOnDrag()detects window dragged to left/right edge - Menu button:
handleInstantSnapResizingTask()for instant snap from window header menu
Snap Bounds Calculation (DesktopTasksController.snapToHalfScreen()):
- Left snap:
Rect(left, top, left + width/2, bottom) - Right snap:
Rect(right - width/2, top, right, bottom)
Tiling Integration (when ENABLE_TILE_RESIZING flag is set):
SnapEventHandlerinterface manages tiling sessions- Supports a divider between tiled tasks
- Methods:
snapToHalfScreen(),removeTaskIfTiled(),getDividerBounds()
Non-resizable Apps: Shows toast (R.string.desktop_mode_non_resizable_snap_text) and returns task via returnToDragStartAnimator.
Animation: Same ToggleResizeDesktopTaskTransitionHandler as maximize, 300ms bounds interpolation with TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE.
28.5 Window Close
Flow:
- User clicks close (X) button in window header
DesktopTasksControllercreates WCT withremoveTask(token)- Transition started with
TRANSIT_CLOSE
Animation (CloseDesktopTaskTransitionHandler):
- Filters changes with mode
TRANSIT_CLOSEandWINDOWING_MODE_FREEFORM - Two parallel animations:
- Bounds (200ms): Scale down to 95% with center anchor, offset Y by 36dp downward,
STANDARD_ACCELERATEinterpolator - Alpha (100ms): Fade from 1.0 to 0.0,
LINEARinterpolator
- Bounds (200ms): Scale down to 95% with center anchor, offset Y by 36dp downward,
- Jank monitoring:
CUJ_DESKTOP_MODE_CLOSE_TASK
28.6 Drag-to-Desktop Transition
Converts a fullscreen or split-screen task to freeform desktop mode by dragging.
Handler: DragToDesktopTransitionHandler (sealed class with DefaultDragToDesktopTransitionHandler and SpringDragToDesktopTransitionHandler subclasses)
Three-Phase Transition:
stateDiagram-v2
[*] --> StartDrag: User drags task header
StartDrag --> Dragging: TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
Dragging --> EndDrag: Drop in desktop area
Dragging --> CancelDrag: Release outside / drag to status bar
EndDrag --> [*]: TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP
CancelDrag --> [*]: TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP
state CancelDrag {
[*] --> StandardCancel: Return to original mode
[*] --> CancelToSplit: Enter split at left/right
[*] --> CancelToBubble: Convert to bubble
}
Phase 1 — Start: Launches Home transiently behind the scaled task. Task follows finger with scale-down animation. State stored as TransitionState.FromFullscreen or TransitionState.FromSplit.
Phase 2A — End (success): WCT applies setWindowingMode(FREEFORM) and setBounds(). Merges into the start transition.
Phase 2B — Cancel: Three cancel variants:
STANDARD_CANCEL— scale back to original windowing modeCANCEL_SPLIT_LEFT/RIGHT— enter split-select at specified positionCANCEL_BUBBLE_LEFT/RIGHT— convert to bubble
28.7 PiP Operations
Picture-in-Picture operations are managed by PipTaskOrganizer and PipMotionHelper.
Enter PiP
- Activity calls
enterPictureInPictureMode() PipTransitionStateprogresses:UNDEFINED → TASK_APPEARED → ENTRY_SCHEDULED → ENTERING_PIP → ENTERED_PIP- WCT:
setWindowingMode(token, WINDOWING_MODE_PINNED),setBounds(token, pipBounds)
Exit PiP
Two exit paths based on destination:
flowchart TD
A[Exit PiP Triggered] --> B{requestEnterSplit?}
B -->|No| C[syncWithSplitScreenBounds]
B -->|Yes| D[Exit to Split]
C --> E{Was in split before?}
E -->|No| F[Exit to Fullscreen]
E -->|Yes| G[Restore to Split Position]
F -->|WCT| F1["setWindowingMode(UNDEFINED)<br/>setBounds(null)"]
F1 --> F2[TRANSIT_EXIT_PIP]
D -->|WCT| D1["setWindowingMode(UNDEFINED)<br/>onPipExpandToSplit(wct)"]
D1 --> D2[TRANSIT_EXIT_PIP_TO_SPLIT]
G -->|WCT| G1["Restore previous split position"]
G1 --> G2[TRANSIT_EXIT_PIP]
Exit to Fullscreen: wct.setWindowingMode(token, WINDOWING_MODE_UNDEFINED) + wct.setBounds(token, null) → inherits parent bounds.
Exit to Split: wct.setWindowingMode(token, WINDOWING_MODE_UNDEFINED) + StageCoordinator.onPipExpandToSplit(wct, taskInfo) → task reparented into split stage.
PiP Stash
PipMotionHelper handles stashing PiP to screen edges:
- Stash:
stashToEdge()→ physics-based fling animation to edge, setsPipBoundsState.setStashed(STASH_TYPE_LEFT | STASH_TYPE_RIGHT) - Unstash:
animateToUnStashedBounds()→ 250ms spring animation back to visible position
PiP Dismiss
PipMotionHelper.dismissPip() → hides menu → PipTaskOrganizer.removePip() → TRANSIT_REMOVE_PIP
28.8 Split-Screen Operations
Split-screen is managed by StageCoordinator with two stages backed by root tasks.
Enter Split Screen
StageCoordinator.prepareEnterSplitScreen():
- If split inactive:
prepareActiveSplit()→ activates split root, adds task to side stage - If split active:
prepareBringSplit()→ reparents top task to main stage, brings split forward - WCT operations:
setReparentLeafTaskIfRelaunch(false),evictAllChildren(),reparentTopTask(),addTask(),activateSplit()
Exit Split Screen
StageCoordinator.prepareExitSplitScreen(stageToTop, wct, exitReason):
- Removes all tasks from non-top stage via
removeAllTasks() - Sets windowing mode to fullscreen for top stage tasks
- Reparents root task to display area if on non-default display
- Calls
deactivateSplit()andmSplitState.exit()
Divider Drag
The divider between split stages is draggable to resize the split ratio. SplitLayout calculates new bounds for both stages and applies them via updateWindowBounds(wct).
Swap Stages
Swaps the tasks between main and side stages, effectively flipping left/right (or top/bottom) positions.
PiP-to-Split Transition
Complete flow when user presses “enter split” button on PiP:
sequenceDiagram
participant PM as PipMotionHelper
participant PTO as PipTaskOrganizer
participant SC as StageCoordinator
participant Trans as Transitions
PM->>PTO: exitPip(duration, enterSplit=true)
PTO->>PTO: syncWithSplitScreenBounds() → true
PTO->>PTO: direction = LEAVE_PIP_TO_SPLIT_SCREEN
PTO->>SC: onPipExpandToSplit(wct, taskInfo)
SC->>SC: prepareEnterSplitScreen(wct, task, position)
SC->>SC: evictOtherChildren from target stage
SC->>SC: addTask to stage, activateSplit
PTO->>Trans: startExitTransition(TRANSIT_EXIT_PIP_TO_SPLIT, wct)
Trans->>Trans: Animate bounds: PiP → split half
Trans->>SC: finishEnterSplitScreen(finishT)
SC->>SC: Show divider, inflate decorations
28.9 WCT Operations Summary
All multi-window operations use WindowContainerTransaction as the atomic batch mechanism. Key operations:
| WCT Method | Usage | Operations Using It |
|---|---|---|
setBounds(token, Rect) |
Set task bounds | Resize, Maximize, Snap, PiP move |
setWindowingMode(token, mode) |
Change windowing mode | Maximize (FULLSCREEN↔FREEFORM), PiP enter/exit, Split enter/exit |
setDragResizing(token, bool) |
Signal drag state | Resize (start/end) |
reorder(token, onTop) |
Reorder in z-stack | Minimize (to back) |
removeTask(token) |
Remove task | Close |
reparent(token, parent, onTop) |
Move to new parent | Split enter/exit, PiP-to-split |
setHidden(token, bool) |
Show/hide task | Minimize in some paths |
setSmallestScreenWidthDp(token, dp) |
Override screen width | Split layout |
28.10 Mode Transition Map
stateDiagram-v2
FS: Fullscreen
FW: Freeform
PiP: Picture-in-Picture
SP: Split Screen
FS --> FW: Drag-to-Desktop
FW --> FS: Maximize (toggleDesktopTaskSize)
FS --> SP: Split launch / drag-to-split
SP --> FS: Exit split (prepareExitSplitScreen)
FS --> PiP: enterPictureInPictureMode
PiP --> FS: exitPip (to fullscreen)
FW --> PiP: Minimize with PiP flag
PiP --> FW: exitPip (to freeform)
PiP --> SP: PiP-to-Split (onPipExpandToSplit)
SP --> PiP: Split task enters PiP
FW --> SP: Cancel drag-to-desktop (CANCEL_SPLIT)
SP --> FW: Drag from split to desktop
28.11 Key Files for Multi-Window Operations
| File | Role |
|---|---|
desktopmode/DesktopTasksController.kt |
Central desktop mode controller: maximize, minimize, snap, drag |
windowdecor/DragResizeInputListener.java |
Resize input capture on task edges |
windowdecor/FluidResizeTaskPositioner.java |
Real-time resize via WCT with transition finalization |
desktopmode/CloseDesktopTaskTransitionHandler.kt |
Close animation (scale + fade) |
desktopmode/DragToDesktopTransitionHandler.kt |
Three-phase fullscreen/split → freeform transition |
desktopmode/DesktopMinimizationTransitionHandler.kt |
Minimize animation |
desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt |
Maximize/snap 300ms bounds animation |
windowdecor/tiling/SnapEventHandler.kt |
Snap/tiling interface |
pip/PipTaskOrganizer.java |
PiP enter/exit/dismiss orchestration |
pip/phone/PipMotionHelper.java |
PiP move/stash/fling physics |
splitscreen/StageCoordinator.java |
Split enter/exit/divider/swap coordination |
android/window/WindowContainerTransaction.java |
Atomic batch operation API |
Reference files:
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java— Shell-side split-screen entry point coordinating stage creation and task placementframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java— Phone-specific PiP controller managing enter/exit/dismiss and gesture interactionsframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java— Central split-screen orchestrator managing two stages, divider, enter/exit flowsframeworks/base/core/java/android/window/WindowContainerTransaction.java— Atomic batch API for setBounds, setWindowingMode, reparent, reorder, and removeTask operations
29. Window Manager Paradigm Analysis: Stacking vs Tiling
This section analyzes Android’s multi-window modes through the classical lens of desktop window manager theory — specifically the dichotomy between stacking (floating) window managers and tiling window managers. Understanding where each Android windowing mode falls on this spectrum reveals the fundamental architectural choices made in the Android platform.
29.1 Classical Desktop Window Manager Taxonomy
Stacking Window Manager
A stacking window manager (also called a floating window manager) allows windows to overlap one another. Windows are drawn using the painter’s algorithm: the desktop is painted first, then each window is painted from back to front based on its z-order. The window at the top of the z-stack receives input focus.
Key characteristics:
- Windows are independent, freely movable, and resizable objects
- Z-order (depth) determines visibility when windows overlap
- User explicitly controls window position and size
- No constraint that windows must fill or tile the screen
- Examples: Windows Explorer shell, macOS Quartz Compositor, Openbox, Fluxbox, XFWM
Tiling Window Manager
A tiling window manager automatically arranges windows into non-overlapping regions that collectively fill the available screen space. There is no z-order concept for normal windows — every window is always fully visible.
Two sub-categories:
- Manual tiling: User explicitly decides where to place and how to split windows (herbstluftwm, i3 in manual mode). The user issues commands to split the current container horizontally or vertically.
- Dynamic/automatic tiling: Windows are automatically arranged according to a predefined layout algorithm (dwm, xmonad, Qtile). Examples of layout algorithms:
- Master-stack: One large “master” pane + a stack of secondary panes
- BSP (Binary Space Partitioning): Recursive bisection of the display rectangle into a binary tree of panes
- Monocle: A single full-screen window (the active one), others hidden behind
- Fibonacci/Spiral: Golden-ratio recursive subdivisions
The critical distinction: in a tiling WM, the WM itself owns and enforces the layout. In a stacking WM, the WM only enforces z-order rules; layout is user-driven.
29.2 Android’s Fundamental Architecture: Stacking Infrastructure
Android’s WM Core is always a stacking window manager at the infrastructure level. Every WindowContainer in the server-side hierarchy maps 1:1 to a SurfaceControl layer in SurfaceFlinger’s layer tree, and every SurfaceControl has an explicit z-order (controlled by SurfaceControl.Transaction.setLayer()).
The hierarchy is:
graph TB
DC_Z["DisplayContent"] --> TDA_Z["TaskDisplayArea"]
TDA_Z --> TASK_R["Task (root)"]
TASK_R --> TASK_L["Task (leaf)"]
TASK_L --> AR_Z["ActivityRecord"]
AR_Z --> WS_Z["WindowState"]
WS_Z --> SC_Z["SurfaceControl (z-order)"]
Z-order at each level is determined by the index of children in WindowContainer.mChildren (an ArrayList), with higher indices rendered on top. The WindowConfiguration.isAlwaysOnTop() method controls whether a container always floats above siblings.
The “tiling” behavior exposed to users is not a fundamental change to this infrastructure. Instead, it is a set of policy constraints enforced by WM Shell on top of the stacking infrastructure. This is the key architectural insight.
29.3 Per-Mode Analysis
29.3.1 Fullscreen — The “Monocle” Mode
| Property | Value |
|---|---|
| Windowing mode constant | WINDOWING_MODE_FULLSCREEN = 1 |
| WM paradigm analog | Tiling WM “monocle” layout |
isFloating() |
false |
| Z-order control by user | None (system overlays still stack) |
| User resize/move | None |
| Screen space usage | 100% |
Analysis: Fullscreen is analogous to the monocle layout in tiling WMs like dwm and i3 — a single application occupies the entire available display area. The user has no layout control. However, unlike a true tiling WM monocle, Android fullscreen still uses z-ordering for system overlays (status bar, navigation bar, IME, PiP window) that stack above the app surface. The “tile” is just the app; system UI is a stacking layer above it.
Fullscreen is the historical default for Android (pre-Android 7.0), reflecting mobile’s single-task focus.
29.3.2 Freeform — The Pure Stacking Mode
| Property | Value |
|---|---|
| Windowing mode constant | WINDOWING_MODE_FREEFORM = 5 |
| WM paradigm analog | Stacking WM (Windows Explorer, macOS, Openbox) |
isFloating() |
true |
| Z-order control by user | Yes — click-to-focus brings task to front |
| User resize/move | Yes — DragResizeInputListener, FluidResizeTaskPositioner |
| Screen space usage | Arbitrary (can overlap, leave gaps) |
| Bounds persistence | Yes — across power cycles |
Analysis: Freeform mode is a textbook stacking window manager. Windows can arbitrarily overlap. The user drags title bars to move windows (CTRL_TYPE_UNDEFINED in DragResizeInputListener) and drags edges/corners to resize them (CTRL_TYPE_LEFT/RIGHT/TOP/BOTTOM bitmask combinations). Focus follows click: DesktopTasksController calls wct.reorder(token, true /* onTop */) when a task receives focus, moving it to the top of mChildren in TaskDisplayArea.
There is no algorithmic auto-layout. Task.computeFreeformBounds() places a new window within parent bounds, but does not cascade, tile, or arrange existing windows. Each window’s Rect is stored independently in WindowConfiguration and persisted across restarts.
The isFloating() method in WindowConfiguration.java returns true for both WINDOWING_MODE_FREEFORM and WINDOWING_MODE_PINNED, exempting these windows from overscan insets — they can extend to the physical display edge, just like floating windows on a desktop OS.
// WindowConfiguration.java
public static boolean isFloating(@WindowingMode int windowingMode) {
return windowingMode == WINDOWING_MODE_FREEFORM
|| windowingMode == WINDOWING_MODE_PINNED;
}
29.3.3 Split-Screen — The Manual Tiling Mode
| Property | Value |
|---|---|
| Windowing mode constant | WINDOWING_MODE_MULTI_WINDOW = 6 |
| WM paradigm analog | Manual tiling WM (i3, herbstluftwm) |
isFloating() |
false |
| Z-order control by user | None between stages |
| User resize/move | Divider drag only |
| Screen space usage | 100% (two stages fill display) |
| Layout algorithm | Single-axis bisection with snap targets |
Analysis: Split-screen is the closest Android analog to a manual tiling window manager. The display is divided into exactly two non-overlapping stages (MainStage and SideStage in StageCoordinator). Both stages use WINDOWING_MODE_MULTI_WINDOW, and their bounds are constrained so they collectively fill the display (minus the divider bar).
The divider is the user’s layout control mechanism — analogous to adjusting split ratios in i3 or herbstluftwm. DividerSnapAlgorithm defines snap targets:
| Snap Target | Value | Ratio | Description |
|---|---|---|---|
SNAP_TO_2_50_50 |
0 | 50/50 | Equal split (default) |
SNAP_TO_2_33_66 |
1 | 33/67 | One-third / two-thirds |
SNAP_TO_2_66_33 |
2 | 66/34 | Two-thirds / one-third |
SNAP_TO_2_10_90 |
3 | 10/90 | Near-minimized left/top |
SNAP_TO_2_90_10 |
4 | 90/10 | Near-minimized right/bottom |
SNAP_TO_3_33_33_33 |
5 | 33/33/33 | Equal three-app split |
SNAP_TO_3_45_45_10 |
6 | 45/45/10 | Two large + one small |
SNAP_TO_3_10_45_45 |
7 | 10/45/45 | One small + two large |
These predefined fractions are analogous to tiling WM layout presets. Two-app splits use a single axis of bisection; three-app splits extend to a second division along the same axis. No recursive nesting (split-within-split) is possible.
A fundamental tiling WM property holds: stages cannot overlap. SplitLayout computes mStageBounds[0] and mStageBounds[1] such that together they equal the root bounds minus the divider window width. The WM Shell enforces this invariant through WindowContainerTransaction.setBounds() — not through z-order, but through geometric partitioning.
29.3.4 Picture-in-Picture — The Stacking Overlay
| Property | Value |
|---|---|
| Windowing mode constant | WINDOWING_MODE_PINNED = 2 |
| WM paradigm analog | Stacking WM “always-on-top” overlay |
isFloating() |
true |
isAlwaysOnTop() |
true (unconditionally) |
| Z-order control by user | None (always on top of everything) |
| User resize/move | Within screen bounds, gravity-based snapping |
| Screen space usage | Small fraction; overlaps other content |
Analysis: PiP is the purest application of stacking WM behavior in Android. The PiP window floats above all other content — including freeform windows, split-screen, and full-screen apps — by enforcing isAlwaysOnTop() == true at the WindowConfiguration level:
// WindowConfiguration.java
public boolean isAlwaysOnTop() {
if (mWindowingMode == WINDOWING_MODE_PINNED) return true;
...
}
The system enforces exactly one PiP window at a time via RootWindowContainer: before creating a new pinned root task, it calls removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED) to clear any existing one. This is analogous to the monocle constraint, but in the z-axis rather than xy plane.
The TaskDisplayArea caches a direct reference to the pinned task (mRootPinnedTask) for fast lookup when enforcing this constraint. The PiP window’s position is controlled through PipMotionHelper — the user can drag it anywhere, but it snaps to screen edges (gravity-based positioning), not to a grid or tiling layout.
29.3.5 Desktop Windowing (Android 15+) — Full Stacking with Optional Snap Tiling
| Property | Value |
|---|---|
| Windowing mode | WINDOWING_MODE_FREEFORM = 5 |
| WM paradigm analog | Modern stacking WM with snap assist (Windows 11, KDE Plasma) |
| Z-order control by user | Yes — click-to-focus |
| User resize/move | Full freeform |
| Optional tiling | Drag-to-edge snap zones (50/50, 33/67) |
| Window decorations | Full title bar, maximize/minimize/close buttons |
Analysis: Desktop windowing is freeform mode augmented with a thin layer of optional snap-tiling — analogous to Windows 11 Snap Layouts or KDE Plasma’s “Quarter Tiling.” The core remains a stacking WM: windows overlap, z-order is click-driven, layout is unconstrained.
The snap-tiling layer sits in DesktopTasksController:
- Dragging a window header to the top edge triggers maximize (
dragToMaximizeDesktopTask()) - Dragging to the left/right edge triggers half-screen snap (
snapToHalfScreen()) - The snap positions use the same
DividerSnapAlgorithmtargets as split-screen
Critically, snap-tiling in desktop mode is not enforced by the WM. After snapping, windows retain WINDOWING_MODE_FREEFORM and can be freely moved out of the snapped position. This contrasts with split-screen, where StageCoordinator actively maintains the non-overlapping invariant.
29.3.6 TaskFragment / Activity Embedding — Intra-Task Tiling
| Property | Value |
|---|---|
| Container type | TaskFragment extends WindowContainer |
| WM paradigm analog | Tiling within a single tile (tmux panes, terminal multiplexer) |
| Overlap | None (mRelativeEmbeddedBounds partition the parent Task) |
| Layout control | TaskFragmentOrganizer API (app-controlled) |
| Scope | Single Task boundary |
Analysis: Activity Embedding is a tiling system, but scoped within a single Task rather than across the whole display. Two TaskFragments in an AdjacentSet partition the parent Task’s bounds into non-overlapping left and right (or top and bottom) halves. No z-order manipulation occurs between adjacent fragments.
This is architecturally similar to how tmux creates panes within a terminal window, or how Emacs splits its frame into windows — a tiling layout within a floating container. The parent Task may itself be in any windowing mode (fullscreen, freeform, split-screen), making Activity Embedding a nested tiling layer inside Android’s broader window management.
29.4 The Stacking/Tiling Spectrum
graph LR
TILE["Pure Tiling<br/>(xmonad, dwm BSP, i3)"]
SPLIT["Split-Screen<br/>TaskFragment<br/>Embedded Activity"]
DESKTOP["Desktop (snapped)<br/>with snap zones<br/>Desktop Windowing"]
FREE["Freeform<br/>Fullscreen (monocle)"]
PIP["PiP<br/>(always-on-top)"]
STACK["Pure Stacking"]
TILE --- SPLIT
SPLIT --- DESKTOP
DESKTOP --- FREE
FREE --- PIP
PIP --- STACK
Each Android mode occupies a distinct position:
| Mode | Paradigm | Overlap Allowed | User Layout Control | Algorithmic Layout |
|---|---|---|---|---|
| Fullscreen | Monocle (tiling) | No (system only) | None | None (fixed to display) |
| Split-Screen | Manual tiling | No | Divider drag (1 axis) | Snap presets (5 fractions) |
| Activity Embedding | Intra-task tiling | No (within task) | App-controlled split | Fixed ratio per rule |
| Desktop (snapped) | Hybrid | Optional | Drag to snap zone | Snap zones (edge triggers) |
| Freeform | Stacking | Yes | Full move + resize | None |
| PiP | Stacking overlay | Yes (always on top) | Drag within screen | Edge gravity snapping |
29.5 Layout Algorithm Comparison
Classical tiling WMs implement recursive layout algorithms. Android’s modes implement a much simpler, single-level approach:
| Layout Type | Classical WM | Android Equivalent | Depth |
|---|---|---|---|
| Full screen | Monocle (dwm, i3) | WINDOWING_MODE_FULLSCREEN |
N/A |
| Equal halves | i3 split h/v |
Split-screen 50/50 | 1 level |
| Arbitrary ratio | i3 resize | Split-screen divider drag (10–90%) | 1 level |
| Master + stack | dwm master-stack | None (no native equivalent) | — |
| Binary space partition | bspwm | None (no recursive split) | — |
| Fibonacci/Spiral | Some WMs | None | — |
| Free positioning | Openbox, XFWM | Freeform / Desktop | N/A |
| Intra-container split | tmux panes | Activity Embedding | 1 level |
Android’s key limitation vs classical tiling WMs: No recursive subdivision. Split-screen supports up to 3-app splits (via SNAP_TO_3_* targets) but cannot be nested — you cannot put a split-screen group inside one side of another split. Classical tiling WMs allow arbitrary nesting: a BSP tree can subdivide any pane infinitely. Android’s WINDOWING_MODE_MULTI_WINDOW is a flat model with a fixed maximum of 3 concurrent split apps.
29.6 Why Android’s Infrastructure Is Stacking, Not Tiling
Even split-screen (the most tiling-like mode) relies on the stacking infrastructure:
- SurfaceControl layers still exist: Both split-screen stages have
SurfaceControlobjects with z-orders. The “non-overlapping” property is enforced by settingRectbounds, not by removing z-order. - PiP can appear above split-screen: A PiP window (
WINDOWING_MODE_PINNED) overlaps both split-screen stages — impossible in a pure tiling WM where every window is always fully visible. - System overlays always stack: Status bar, IME, notifications, and volume panels float above all app content in all modes. Pure tiling WMs treat these as part of the tiling layout or exclude them.
- Transition animations require z-order: The Shell transition system animates windows by manipulating
SurfaceControllayers during transitions (leash pattern,startTransaction/finishTransaction). This requires z-ordering infrastructure.
The WindowContainer hierarchy comment in Android’s source reflects this design: every container has a SurfaceControl, and the isAlwaysOnTop() flag is the primary mechanism for overriding default stacking order. Tiling constraints are geometric policy, not architectural necessity.
29.7 Historical Evolution Through the Stacking/Tiling Lens
| Android Version | New Mode | Paradigm Added |
|---|---|---|
| 1.0–6.x | Fullscreen only | Monocle |
| 7.0 (N) | Split-screen, Freeform | Manual tiling + Stacking |
| 8.0 (O) | PiP on phones | Stacking overlay |
| 12 (S) | Activity Embedding | Intra-task tiling |
| 13 (T) | Taskbar + improved split | Enhanced manual tiling |
| 15+ | Desktop windowing (large screen) | Full stacking WM |
The trajectory is clear: Android began as a pure monocle system (one app, full screen), added tiling (split-screen), added stacking (freeform, PiP), and is now building out a complete stacking WM for large-screen and desktop use cases. The evolution mirrors the history of the Linux desktop: early window managers were simple (twm), tiling emerged as a power-user paradigm (dwm, xmonad), and modern environments blend both (KDE, GNOME with tiling extensions).
29.8 Key Implementation Files
| File | Paradigm Relevance |
|---|---|
core/java/android/app/WindowConfiguration.java |
WINDOWING_MODE_* constants, isFloating(), isAlwaysOnTop() |
services/core/.../wm/TaskDisplayArea.java |
Z-order enforcement, mRootPinnedTask always-on-top guarantee |
services/core/.../wm/Task.java |
computeFreeformBounds(), stacking layer rank |
Shell/.../splitscreen/StageCoordinator.java |
Non-overlapping tiling invariant enforcement |
Shell/.../common/split/SplitLayout.java |
Divider position, stage bounds partitioning |
Shell/.../common/split/DividerSnapAlgorithm.java |
Tiling snap presets (5 predefined fractions) |
Shell/.../desktopmode/DesktopTasksController.kt |
Freeform stacking + optional snap-tiling |
Shell/.../pip/PipTaskOrganizer.java |
Always-on-top PiP stacking enforcement |
Shell/.../windowdecor/DragResizeInputListener.java |
Stacking WM free resize (CTRL_TYPE bitmask) |
services/core/.../wm/RootWindowContainer.java |
Exclusive PiP constraint (one pinned task) |
Reference files:
frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java— Per-display policy controlling system bar visibility, orientation, and window layout constraintsframeworks/base/services/core/java/com/android/server/wm/WindowState.java— Per-window server-side state including z-order layer assignment and surface management
30. Multi-Window Evolution
Android’s multi-window capability evolved incrementally from a single-Activity-per-screen model to a fully flexible desktop windowing system. This section traces the architectural milestones.
30.1 Era 1: Single Window (Android 1.0–6.0)
In early Android, one Activity occupied the entire screen at all times. The architecture enforced a strict 1:1 mapping:
graph LR
ACT_S["Activity"] --> PW_S["PhoneWindow"] --> WS_S["full-screen WindowState"]
Key constraints:
ActivityStack(predecessor ofTask) managed a linear stack of Activities — only the topmost was visible- No concept of windowing modes — all Activities were implicitly fullscreen
- No bounds configuration — Activities inherited the display size
- The window manager’s only layout concern was z-ordering and system bar insets
Legacy naming: The server-side container was called ActivityStack, and the per-Activity window token was AppWindowToken (now renamed to Task and ActivityRecord respectively).
30.2 Era 2: Multi-Window Introduction (Android 7.0, API 24)
Android 7.0 was the first major inflection point, introducing split-screen, freeform, and PiP windowing modes.
New concepts introduced:
| Concept | Implementation | Purpose |
|---|---|---|
| Windowing Modes | WindowConfiguration.WINDOWING_MODE_* constants |
Classify how a task is presented |
| Resizeable Activities | ActivityInfo.resizeMode field |
Declare whether Activity supports resizing |
| Multi-Window Callbacks | Activity.onMultiWindowModeChanged() |
Notify apps of mode transitions |
| Launch Bounds | ActivityOptions.setLaunchBounds(Rect) |
Control where new activities appear |
| Split-Screen | Two ActivityStack instances side by side |
Two-app concurrent display |
Resize mode constants (ActivityInfo.java):
| Constant | Value | Meaning |
|---|---|---|
RESIZE_MODE_UNRESIZEABLE |
0 | Cannot be resized (pre-7.0 behavior) |
RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION |
1 | Auto-resizable if targetSdkVersion ≥ 24 |
RESIZE_MODE_RESIZEABLE |
2 | Explicitly supports resizing |
RESIZE_MODE_FORCE_RESIZEABLE |
4 | System forces resizable even if not declared |
RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY |
5 | Force-resized landscape app |
RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY |
6 | Force-resized portrait app |
RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION |
7 | Force-resized, preserve orientation |
Architectural impact: The ActivityStack gained bounds and windowing mode state. A new hierarchy emerged where multiple stacks could coexist on a single display, each with independent bounds.
30.3 Era 3: PiP on Phones (Android 8.0, API 26)
Picture-in-Picture, originally TV-only, became available on phones:
- New public API:
Activity.enterPictureInPictureMode(PictureInPictureParams) - Callback:
Activity.onPictureInPictureModeChanged(boolean, Configuration) - Manifest:
android:supportsPictureInPictureattribute - Internally uses
WINDOWING_MODE_PINNED— the PiP window is an always-on-top floating Task
30.4 Era 4: TaskFragment and Activity Embedding (Android 12, API 31)
The second major inflection point introduced TaskFragment — a new container level between Task and ActivityRecord:
graph TD
subgraph "Pre-Android 12"
T1[Task] --> AR1[ActivityRecord]
T1 --> AR2[ActivityRecord]
end
subgraph "Android 12+"
T2[Task] --> TF1[TaskFragment<br/>Left split]
T2 --> TF2[TaskFragment<br/>Right split]
TF1 --> AR3[ActivityRecord]
TF2 --> AR4[ActivityRecord]
end
Why TaskFragment? Split-screen in Android 7.0 required two separate Tasks. TaskFragment enables splitting within a single Task — multiple Activities in the same Task can have independent bounds and lifecycle, enabling library-driven layout (Jetpack WindowManager) without framework changes.
30.5 Era 5: Desktop Windowing (Android 15+)
The latest evolution brings full desktop-class windowing:
- Freeform as default: 133 feature flags in
DesktopExperienceFlags.javasignal desktop-first design - Shell-driven architecture: WM Shell library handles all presentation (resize, maximize, minimize, snap, drag)
- Multi-desk support: Virtual desktops within a single display
- Window decorations: Title bars, resize handles, close/maximize/minimize buttons
- Drag-to-desktop transitions: Converting fullscreen/split tasks to freeform via gesture
30.6 Evolution Summary
timeline
title Android Multi-Window Evolution
section Single Window
Android 1.0-6.0 : One Activity = Full Screen
: ActivityStack (linear)
: AppWindowToken
section Multi-Window
Android 7.0 (N) : Split-screen, Freeform, PiP (TV)
: WINDOWING_MODE constants
: resizeMode attribute
Android 8.0 (O) : PiP on phones
: enterPictureInPictureMode API
section Activity Embedding
Android 12 (S) : TaskFragment introduced
: Activity Embedding APIs
: Jetpack WindowManager
Android 12L : Large screen optimizations
: Letterboxing improvements
section Desktop
Android 15+ : Desktop windowing mode
: 133 feature flags
: Multi-desk, window decorations
: Shell-driven transitions
| Era | Container Model | Window Modes | Animation System | Multi-Display |
|---|---|---|---|---|
| 1.0–6.0 | ActivityStack (linear) | Fullscreen only | Custom per-transition | Single |
| 7.0–11 | ActivityStack (bounded) | Fullscreen, Split, Freeform, PiP | AppTransition | Basic |
| 12–14 | TaskFragment (hierarchical) | + Embedded splits | Organizer + Shell transitions | Full support |
| 15+ | TaskFragment + Desktop | + Desktop freeform | Shell-driven multi-track | Desktop topology |
Reference files:
frameworks/base/core/java/android/window/DesktopExperienceFlags.java— 133 feature flags controlling desktop windowing behavior (freeform, snap, drag, multi-desk)frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt— Central desktop mode controller for maximize, minimize, snap, drag, and cross-display move
31. Embedded Activity
See also §14 for
SurfaceControlViewHost, a related cross-process UI embedding mechanism.
Embedded Activity (Activity Embedding) enables placing multiple Activities side-by-side within a single Task using TaskFragment as the container. This is the framework-level API that powers Jetpack WindowManager’s split layouts on large screens and foldables.
31.1 TaskFragment Architecture
TaskFragment (services/core/.../wm/TaskFragment.java) extends WindowContainer<WindowContainer> and sits between Task and ActivityRecord in the container hierarchy. Notably, Task itself extends TaskFragment, making every Task also a TaskFragment.
classDiagram
class WindowContainer~E~ {
+mChildren : ArrayList~E~
+mSurfaceControl : SurfaceControl
}
class TaskFragment {
+mTaskFragmentOrganizer : ITaskFragmentOrganizer
+mIsEmbedded : boolean
+mRelativeEmbeddedBounds : Rect
+mAdjacentTaskFragments : AdjacentSet
+isEmbedded() boolean
+isAllowedToEmbedActivity() int
+setRelativeEmbeddedBounds(Rect)
+setAdjacentTaskFragments(AdjacentSet)
}
class Task {
+mWindowingMode : int
+mChildren : TaskFragments + Tasks
}
class ActivityRecord {
+mTaskFragment : TaskFragment
}
WindowContainer <|-- TaskFragment : extends
TaskFragment <|-- Task : extends
TaskFragment *-- ActivityRecord : contains
Task *-- TaskFragment : contains (embedded)
Key fields:
mTaskFragmentOrganizer— the client-side organizer controlling this fragmentmIsEmbedded—truewhen this TaskFragment is embedded (not the root Task)mRelativeEmbeddedBounds— bounds relative to parent Task (not display)mAdjacentTaskFragments— tracks split-paired TaskFragments viaAdjacentSet
31.2 Embedding Permission Model
Not all Activities can be embedded. TaskFragment.isAllowedToEmbedActivity() enforces security:
| Result | Value | Condition |
|---|---|---|
EMBEDDING_ALLOWED |
0 | Same UID, system app, or explicit opt-in |
EMBEDDING_DISALLOWED_UNTRUSTED_HOST |
1 | Host app not trusted by embedded Activity |
EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION |
2 | TaskFragment too small for Activity’s minimum dimensions |
EMBEDDING_DISALLOWED_NEW_TASK |
3 | Activity requests FLAG_ACTIVITY_NEW_TASK (cannot embed) |
Trust levels:
- Trusted: Same UID (same app), system app, or matching signing certificate
- Untrusted: Requires
FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDINGin embedded Activity’sActivityInfoandEMBED_ANY_APP_IN_UNTRUSTED_MODEpermission for host
31.3 TaskFragmentOrganizer API
The client-side TaskFragmentOrganizer (extends WindowOrganizer) registers with the system to manage embedded TaskFragments:
sequenceDiagram
participant App as Host App
participant TFO as TaskFragmentOrganizer
participant TFOC as TaskFragmentOrganizerController (Server)
participant TF as TaskFragment (Server)
App->>TFO: registerOrganizer()
TFO->>TFOC: IPC register
App->>TFO: applyTransaction(wct)
Note over TFO: WCT contains:<br/>createTaskFragment(params)
TFO->>TFOC: Apply WCT
TFOC->>TF: Create TaskFragment
TFOC->>TFO: onTransactionReady(transaction)
Note over TFO: TYPE_TASK_FRAGMENT_APPEARED
TFO->>App: Callback with TaskFragmentInfo
App->>TFO: applyTransaction(wct)
Note over TFO: WCT contains:<br/>startActivityInTaskFragment(token, intent)
TFO->>TFOC: Apply WCT
TFOC->>TF: Launch Activity in fragment
TFOC->>TFO: onTransactionReady(transaction)
Note over TFO: TYPE_TASK_FRAGMENT_INFO_CHANGED
Transition types for organizer responses:
TASK_FRAGMENT_TRANSIT_NONE— no visual changeTASK_FRAGMENT_TRANSIT_OPEN— new Activity appearedTASK_FRAGMENT_TRANSIT_CLOSE— Activity finishedTASK_FRAGMENT_TRANSIT_CHANGE— resize/configuration changeTASK_FRAGMENT_TRANSIT_DRAG_RESIZE— user dragging divider
31.4 TaskFragment Operations
Operations are submitted via WindowContainerTransaction with TaskFragmentOperation objects:
| Operation | Type Value | Description |
|---|---|---|
OP_TYPE_CREATE_TASK_FRAGMENT |
0 | Create new embedded TaskFragment |
OP_TYPE_DELETE_TASK_FRAGMENT |
1 | Remove TaskFragment |
OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT |
2 | Launch Activity into fragment |
OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT |
3 | Move existing Activity to fragment |
OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS |
4 | Create split pair relationship |
OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS |
5 | Break split pair |
OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT |
6 | Move input focus |
OP_TYPE_SET_COMPANION_TASK_FRAGMENT |
7 | Link lifecycle of fragments |
OP_TYPE_SET_ANIMATION_PARAMS |
8 | Configure transition animations |
OP_TYPE_SET_RELATIVE_BOUNDS |
9 | Resize fragment within Task |
OP_TYPE_REORDER_TO_FRONT |
10 | Bring fragment to top |
OP_TYPE_SET_ISOLATED_NAVIGATION |
11 | Isolate back-stack navigation |
OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE |
12 | Manage divider surface |
OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE |
13 | Remove divider surface |
OP_TYPE_SET_DIM_ON_TASK |
14 | Dim background behind fragment |
OP_TYPE_SET_DECOR_SURFACE_BOOSTED |
15 | Boost divider z-order |
OP_TYPE_SET_PINNED |
16 | Pin fragment (prevent reorder) |
WCT convenience methods map directly to these operations:
wct.createTaskFragment(TaskFragmentCreationParams)→OP_TYPE_CREATE_TASK_FRAGMENTwct.reparentActivityToTaskFragment(fragmentToken, activityToken)→OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENTwct.setAdjacentTaskFragments(token1, token2, params)→OP_TYPE_SET_ADJACENT_TASK_FRAGMENTSwct.setRelativeBounds(fragmentToken, bounds)→OP_TYPE_SET_RELATIVE_BOUNDS
31.5 TaskFragment Transaction Events
The server dispatches TaskFragmentTransaction objects containing Change entries to the organizer:
| Change Type | Value | When Dispatched |
|---|---|---|
TYPE_TASK_FRAGMENT_APPEARED |
1 | Fragment attached to hierarchy |
TYPE_TASK_FRAGMENT_INFO_CHANGED |
2 | Bounds, visibility, or activity count changed |
TYPE_TASK_FRAGMENT_VANISHED |
3 | Fragment removed from hierarchy |
TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED |
4 | Parent Task configuration changed |
TYPE_TASK_FRAGMENT_ERROR |
5 | Operation failed (e.g., embedding disallowed) |
TYPE_ACTIVITY_REPARENTED_TO_TASK |
6 | Activity returned from PiP, needs re-embedding |
Each change carries TaskFragmentInfo (bounds, running activity count, visibility, position in parent, minimum dimensions) and optionally TaskFragmentParentInfo (parent Task configuration, display ID, task ID).
31.6 Jetpack WindowManager Integration
AOSP includes the platform-side implementation of Jetpack WindowManager’s Activity Embedding in frameworks/base/libs/WindowManager/Jetpack/:
graph TD
subgraph "App Layer"
JWM[Jetpack WindowManager<br/>androidx.window]
SR[SplitRule / SplitPairRule<br/>Declarative split config]
end
subgraph "Jetpack Extensions (in AOSP)"
SC[SplitController<br/>Rule evaluation engine]
SP[SplitPresenter<br/>Visual presentation]
JTFO[JetpackTaskFragmentOrganizer<br/>extends TaskFragmentOrganizer]
TFC[TaskFragmentContainer<br/>Client-side TF state]
end
subgraph "Framework"
TFOC[TaskFragmentOrganizerController]
TF[TaskFragment instances]
end
JWM --> SR
SR --> SC
SC --> SP
SP --> JTFO
JTFO --> TFC
JTFO -->|WCT operations| TFOC
TFOC --> TF
Key Jetpack extension classes (in AOSP):
| Class | Role |
|---|---|
SplitController |
Evaluates split rules, decides when/how to create TaskFragments |
SplitPresenter |
Calculates bounds, creates/resizes TaskFragments via WCT |
JetpackTaskFragmentOrganizer |
Extends TaskFragmentOrganizer; manages info cache, animations |
TaskFragmentContainer |
Client-side representation of a server TaskFragment |
How split rules work:
- App declares
SplitPairRule(e.g., “Activity A and Activity B should split 50/50”) - When Activity B launches,
SplitControllerintercepts and evaluates rules SplitPresentercreates two TaskFragments with calculated bounds via WCT- Activities are reparented into their respective TaskFragments
setAdjacentTaskFragments()links the pair for coordinated layout
31.7 Shell-Side Animation
The WM Shell’s ActivityEmbeddingController (Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java) implements Transitions.TransitionHandler to animate embedded activity transitions:
- Handles
TRANSIT_TASK_FRAGMENT_DRAG_RESIZEfor divider drag animations - Detects embedded windows via
FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY - Skips animation for
FLAG_FILLS_TASKwindows (non-split, full-task activities) - Coordinates with the Jetpack organizer’s
RemoteAnimationDefinitionfor custom app animations
31.8 Embedded Activity vs Split-Screen
| Aspect | Split-Screen (§28) | Embedded Activity |
|---|---|---|
| Container | Two root Tasks in StageCoordinator |
Multiple TaskFragments in one Task |
| Scope | System-level (all apps) | Per-app or cross-app (with trust) |
| Control | Shell-driven via WINDOWING_MODE_MULTI_WINDOW |
Library-driven via TaskFragmentOrganizer |
| Divider | System divider (SplitLayout) |
App-managed or decorSurface |
| Lifecycle | Independent Tasks | Shared Task lifecycle (back stack, recents) |
| API | Implicit (user gesture/intent) | Declarative (SplitPairRule) or programmatic |
| Back navigation | Independent per Task | Shared or isolated (SET_ISOLATED_NAVIGATION) |
Reference files:
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java— Shell-side transition handler for embedded activity animations and divider dragframeworks/base/core/java/android/window/TaskFragmentOrganizer.java— Client-side API for creating and managing embedded TaskFragments via WindowContainerTransactionframeworks/base/services/core/java/com/android/server/wm/TaskFragment.java— Server-side container between Task and ActivityRecord, manages embedding bounds and adjacency
Part VII: Multi-Display System
32. Multi-Display Architecture
32.1 End-to-End Multi-Display Flow
graph TB
subgraph "Hardware Layer"
INT[Internal LCD]
EXT[External HDMI/USB-C]
VIRT[Virtual Surface]
end
subgraph "Display Adapters"
LDA[LocalDisplayAdapter<br/>Physical displays]
VDA[VirtualDisplayAdapter<br/>Software displays]
end
subgraph "Display Manager"
DDR[DisplayDeviceRepository]
LDM[LogicalDisplayMapper<br/>Device to Logical mapping]
LD1[LogicalDisplay 0<br/>Internal]
LD2[LogicalDisplay 1<br/>External]
LD3[LogicalDisplay 2<br/>Virtual]
DGA[DisplayGroupAllocator]
DG1[DisplayGroup 0<br/>Default]
DG2[DisplayGroup 1<br/>External]
end
subgraph "Window Manager"
RWC[RootWindowContainer]
DC1[DisplayContent 0<br/>Internal]
DC2[DisplayContent 1<br/>External]
DC3[DisplayContent 2<br/>Virtual]
end
INT --> LDA
EXT --> LDA
VIRT --> VDA
LDA --> DDR
VDA --> DDR
DDR --> LDM
LDM --> LD1
LDM --> LD2
LDM --> LD3
DGA --> DG1
DGA --> DG2
LD1 --> DG1
LD2 --> DG2
LD3 --> DG2
LD1 -.->|"1:1 via displayId"| DC1
LD2 -.->|"1:1 via displayId"| DC2
LD3 -.->|"1:1 via displayId"| DC3
RWC --> DC1
RWC --> DC2
RWC --> DC3
32.2 Display Groups
DisplayGroup represents a collection of LogicalDisplays that share power and wake state:
graph LR
subgraph "DisplayGroup 0 - Default"
LD0[LogicalDisplay 0<br/>Internal LCD]
end
subgraph "DisplayGroup 1 - External"
LD1[LogicalDisplay 1<br/>HDMI Monitor]
LD2[LogicalDisplay 2<br/>USB-C Display]
end
subgraph "DisplayGroup 2 - Virtual Device"
LD3[LogicalDisplay 3<br/>MediaProjection]
end
Group Assignment via DisplayGroupAllocator:
GROUP_TYPE_PRIMARY- Default, non-desktop, extended, and fallback displaysGROUP_TYPE_SECONDARY- Projected displays (e.g., MediaProjection)- Reasons:
REASON_NON_DESKTOP/REASON_EXTENDED/REASON_FALLBACKmap to PRIMARY;REASON_PROJECTEDmaps to SECONDARY
Group Behavior: All displays in a group share power state (sleep/wake together). Lock screen activates when all groups with FLAG_DEFAULT_GROUP_ADJACENT plus the default group are non-interactive.
32.3 Per-Display State
Each DisplayContent maintains independent state:
| State | Description |
|---|---|
mCurrentFocus |
Currently focused window on this display |
mTopFocusedTask |
Top activity task on this display |
mInputMethodControlTarget |
IME control window for this display |
mDisplayPolicy |
Display-specific policies (rotation, orientation, cutout) |
mDisplayRotation |
Current rotation state |
mDisplayAreaPolicy |
DisplayArea hierarchy for this display |
32.4 Per-Display Focus and Input Routing
See also §44 for detailed input pipeline architecture and focus management.
sequenceDiagram
participant User as User Touch
participant IR as InputReader
participant DVP as DisplayViewport
participant DTG as DisplayTopologyGraph
participant RWC as RootWindowContainer
participant IMS as InputManagerService
User->>IR: Touch on external display
IR->>DVP: Map physical coords to logical display
DVP->>DTG: Identify target logical display
DTG->>RWC: Route to DisplayContent
RWC->>RWC: updateTopFocusedDisplayLocked()
RWC->>IMS: setFocusedDisplay(newDisplayId)
Note over IMS: Pointer and key events<br/>now route to new display
Key rule: Only one display has “top focus” at a time. RootWindowContainer.mTopFocusedDisplayId tracks which display receives keyboard events. Each display still maintains independent window focus for pointer events.
Reference files:
frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java— Top-level container managing all DisplayContent instances, mTopFocusedDisplayId, and cross-display operationsframeworks/base/services/core/java/com/android/server/wm/DisplayContent.java— Per-display window hierarchy including independent focus, rotation, IME, and DisplayArea policyframeworks/base/services/core/java/com/android/server/display/DisplayManagerService.java— System service managing display adapters, LogicalDisplayMapper, and display lifecycle events
33. Virtual Displays
Virtual displays enable off-screen rendering surfaces that behave like physical displays for the window manager. They are used for screen recording, Miracast, presentation APIs, Android Automotive, and virtual device managers.
33.1 Creation Flow
sequenceDiagram
participant App as Application
participant DM as DisplayManager
participant DMS as DisplayManagerService
participant VDA as VirtualDisplayAdapter
participant SC as SurfaceControl
App->>DM: createVirtualDisplay(config)
DM->>DMS: createVirtualDisplay(config, callback, projection)
DMS->>VDA: createVirtualDisplayLocked(callback, projection, ownerUid, config)
Note over VDA: Check global limit (mMaxDevices)<br/>Check per-package limit (mMaxDevicesPerPackage)
VDA->>SC: DisplayControl.createVirtualDisplay(name, secure, uniqueId, refreshRate)
SC-->>VDA: displayToken (IBinder)
VDA->>VDA: new VirtualDisplayDevice(displayToken, config)
VDA->>VDA: mVirtualDisplayDevices.put(appToken, device)
VDA-->>DMS: DisplayDevice
DMS->>DMS: Register DisplayDevice, assign display ID
DMS-->>App: VirtualDisplay
33.2 Use Cases
| Use Case | Flag Combination | Description |
|---|---|---|
| Screen mirroring | PUBLIC \| AUTO_MIRROR |
Mirrors default display to remote surface |
| MediaProjection capture | AUTO_MIRROR |
Screen recording via MediaProjection API |
| Presentation API | PRESENTATION |
Secondary content on external/virtual display |
| Automotive cluster | TRUSTED \| SHOULD_SHOW_SYSTEM_DECORATIONS |
Instrument cluster with full system UI |
| Virtual Device (VDM) | TRUSTED \| OWN_FOCUS \| OWN_DISPLAY_GROUP |
Companion device display |
| Developer overlay | N/A (OverlayDisplayAdapter) | Debug overlay via developer settings |
33.3 Virtual Display Flags
All flags are defined in DisplayManager (frameworks/base/core/java/android/hardware/display/DisplayManager.java):
| Flag | Bit | Value | Description |
|---|---|---|---|
VIRTUAL_DISPLAY_FLAG_PUBLIC |
0 | 1 | Display is publicly visible; other apps can project content |
VIRTUAL_DISPLAY_FLAG_PRESENTATION |
1 | 2 | Suitable as a Presentation display target |
VIRTUAL_DISPLAY_FLAG_SECURE |
2 | 4 | Secure display; DRM-protected content can be shown |
VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY |
3 | 8 | Never mirror other displays; only show own content |
VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR |
4 | 16 | Automatically mirror default display when no own content |
VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD |
5 | 32 | Show content even when keyguard is insecure |
VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH |
6 | 64 | Display can receive touch input events |
VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT |
7 | 128 | Orientation coupled to content rotation |
VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL |
8 | 256 | Destroy activities when display is removed |
VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS |
9 | 512 | Shows nav bar, status bar (requires TRUSTED) |
VIRTUAL_DISPLAY_FLAG_TRUSTED |
10 | 1024 | System-trusted; can show system UI and receive full input |
VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP |
11 | 2048 | Separate display group (independent wake/sleep state) |
VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED |
12 | 4096 | Always unlocked regardless of keyguard state |
VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED |
13 | 8192 | No haptic feedback or sound for touch events |
VIRTUAL_DISPLAY_FLAG_OWN_FOCUS |
14 | 16384 | Independent focus and touch mode |
VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP |
15 | 32768 | Device-specific display group |
VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED |
16 | 65536 | Cannot steal top focus from other displays |
Flag interaction rules:
OWN_CONTENT_ONLYandAUTO_MIRRORare mutually exclusive;OWN_CONTENT_ONLYtakes precedenceSHOULD_SHOW_SYSTEM_DECORATIONSrequiresTRUSTEDto functionPUBLICwithoutOWN_CONTENT_ONLYimpliesAUTO_MIRRORbehavior
33.4 Lifecycle
Virtual displays are bound to the creating process via IBinder.DeathRecipient:
- Creation:
VirtualDisplayAdapterlinks the app token to theVirtualDisplayDeviceviaappToken.linkToDeath(device, 0) - Surface changes:
setVirtualDisplaySurfaceLocked()allows hot-swapping the render target - Resize:
resizeVirtualDisplayLocked()updates width, height, and density - Release:
releaseVirtualDisplayLocked()callsdevice.destroyLocked(true)andunlinkToDeath - Process death:
DeathRecipient.binderDied()automatically cleans up the display
33.5 Identity and Limits
Virtual displays have unique IDs:
virtual:<package-name>:<uid>,<display-name>,<unique-index>
Resource-based limits enforced by VirtualDisplayAdapter:
config_virtualDisplayLimit— global maximum (default: 100)config_virtualDisplayLimitPerPackage— per-UID maximum (default: 50)
33.6 VirtualDisplayConfig Builder API
VirtualDisplayConfig uses a Builder pattern with the following parameters:
| Parameter | Builder Method | Required | Default | Description |
|---|---|---|---|---|
name |
Constructor | Yes | — | Human-readable display name |
width |
Constructor | Yes | — | Display width in pixels |
height |
Constructor | Yes | — | Display height in pixels |
densityDpi |
Constructor | Yes | — | Display density in DPI |
flags |
setFlags() |
No | 0 | Bitmask of VIRTUAL_DISPLAY_FLAG_* constants |
surface |
setSurface() |
No | null | Initial render target Surface |
uniqueId |
setUniqueId() |
No | null | Custom unique identifier suffix |
displayIdToMirror |
setDisplayIdToMirror() |
No | DEFAULT_DISPLAY |
Source display for mirroring |
windowManagerMirroringEnabled |
setWindowManagerMirroringEnabled() |
No | false | WM-based mirroring instead of DMS recording |
displayCategories |
setDisplayCategories() |
No | empty | Categories for matching activities to displays |
requestedRefreshRate |
setRequestedRefreshRate() |
No | 0.0 | Target refresh rate in Hz |
isHomeSupported |
setHomeSupported() |
No | false | Whether display supports home activity |
displayCutout |
setDisplayCutout() |
No | null | Custom display cutout shape |
defaultBrightness |
setDefaultBrightness() |
No | 0.0 | Default brightness (0.0-1.0) |
33.7 OverlayDisplayAdapter (Developer Overlay Displays)
OverlayDisplayAdapter provides simulated secondary displays rendered as overlay windows on the primary display, intended for development and testing.
Configuration via settings:
# Create a 1280x720 overlay display at 213 DPI
adb shell settings put global overlay_display_devices 1280x720/213
# Create a secure overlay with system decorations
adb shell settings put global overlay_display_devices 1920x1080/320,secure,should_show_system_decorations
# Create multiple overlays
adb shell settings put global overlay_display_devices "1920x1080/320,secure;1280x720/213"
# Multi-mode display (switch between resolutions)
adb shell settings put global overlay_display_devices "1920x1080/320|3840x2160/640"
# Remove all overlay displays
adb shell settings put global overlay_display_devices ""
Supported overlay flags:
| Flag String | Effect |
|---|---|
secure |
Creates a secure display (FLAG_SECURE) |
own_content_only |
Only shows own content; no mirroring |
should_show_system_decorations |
Enables system UI (nav bar, status bar) |
gravity_top_left |
Position overlay at top-left corner |
gravity_top_right |
Position overlay at top-right corner |
gravity_bottom_right |
Position overlay at bottom-right corner |
gravity_bottom_left |
Position overlay at bottom-left corner |
Overlay displays are always trusted since they are created by the system itself.
33.8 Virtual Display Security Model
graph TD
A["Private Virtual Display<br/>(default, no PUBLIC flag)"] -->|"Only creator<br/>can show content"| D[Creator App Windows Only]
B["Public Virtual Display<br/>(VIRTUAL_DISPLAY_FLAG_PUBLIC)"] -->|"Any app can<br/>show content"| E[All App Windows]
B -->|"Without OWN_CONTENT_ONLY"| F["Auto-mirrors default display<br/>when no own windows"]
C["Trusted Virtual Display<br/>(VIRTUAL_DISPLAY_FLAG_TRUSTED)"] -->|"System-level access"| G["System Decorations<br/>Navigation Bar, Status Bar"]
C -->|"Full input"| H[Touch and Keyboard Input]
C -->|"Can enable"| I["OWN_FOCUS<br/>Independent focus management"]
style A fill:#fdd,stroke:#c00
style B fill:#ffd,stroke:#cc0
style C fill:#dfd,stroke:#0c0
Reference files:
frameworks/base/core/java/android/hardware/display/VirtualDisplay.java— Client-side handle wrapping the SurfaceFlinger display tokenframeworks/base/core/java/android/hardware/display/VirtualDisplayConfig.java— Builder-pattern configurationframeworks/base/services/core/java/com/android/server/display/VirtualDisplayAdapter.java— Server-side virtual display managementframeworks/base/core/java/android/companion/virtual/VirtualDeviceManager.java— API for creating virtual devices with virtual displaysframeworks/base/services/core/java/com/android/server/display/OverlayDisplayAdapter.java— Developer overlay display configurationframeworks/base/media/java/android/media/projection/MediaProjection.java— Screen capture integration
34. Display Topology and Spatial Layout
34.1 Display Topology System
The topology system manages the spatial arrangement of multiple displays:
graph TB
subgraph "Display Topology Management"
DTC_T[DisplayTopologyCoordinator]
DTS[DisplayTopologyStore<br/>XML Persistence]
DTG[DisplayTopologyGraph<br/>Graph Structure]
LY[Layout]
DSTLM[DeviceStateToLayoutMap<br/>Foldable Mapping]
end
DTC_T --> DTS
DTC_T --> DTG
DTC_T --> LY
LY --> DSTLM
DisplayTopologyCoordinator manages:
- Physical arrangement of extended displays
- Persistent storage of user-configured layouts via XML
- Integration with device state (foldable configurations)
- Callbacks:
onTopologyChangedCallbacknotifies listeners of arrangement changes
Coordinator lifecycle methods:
| Method | Purpose |
|---|---|
onDisplayAdded() |
Adds display to topology tree with default placement |
onDisplayChanged() |
Updates dimensions, restores stored positioning |
onDisplayRemoved() |
Removes display, rebuilds tree from remaining |
restoreTopologyLocked() |
Loads topology from XML store using uniqueId mapping |
sendTopologyUpdateLocked() |
Broadcasts topology + graph to Input Manager and clients |
Default placement algorithm: 1st display becomes root at (0,0). 2nd display aligned to top, centered horizontally. Subsequent displays attached to rightmost display. Normalization prevents overlaps after layout changes.
34.2 DisplayTopologyGraph — Spatial Adjacency
DisplayTopologyGraph represents displays as a graph for spatial input routing:
Structure:
DisplayNode[]— array of displays, each withmDisplayId,mDensity,mBoundsInGlobalDp(absolute bounds in density-independent pixels), andmAdjacentEdgesmPrimaryDisplayId— root of topology tree
AdjacentEdge describes a neighbor relationship:
AdjacentEdge {
mDisplayNode — neighbor display
mPosition — POSITION_LEFT / POSITION_TOP / POSITION_RIGHT / POSITION_BOTTOM
mOffsetDp — alignment offset along shared edge
}
Graph generation (DisplayTopology.getGraph()): identifies touching displays using findDisplayPlacements() with a MAX_GAP = 5 dp tolerance for corner adjacency. Returns fully populated adjacency edges for native input routing.
Input integration: DisplayManagerService forwards the graph to InputManagerInternal.setDisplayTopology(graph), which passes it to native InputDispatcher for cross-display pointer routing. When the cursor reaches a display edge, the adjacent edge determines which display and offset to continue on.
34.3 Topology Persistence
Storage locations:
- User-specific:
data/system_ce/{userId}/display_topology.xml - Vendor defaults:
vendor/etc/displayconfig/display_topology.xml - Product defaults:
product/etc/displayconfig/display_topology.xml
XML schema (display-topology.xsd):
graph TB
DTS["displayTopologyState (version)"]
TOPO["topology (id, order, immutable)"]
DISP["display (id, primary)"]
POS["position (left|top|right|bottom)"]
OFF["offset (float in density-independent pixels)"]
CHILD["children (nested display hierarchy)"]
DTS --> TOPO
TOPO --> DISP
DISP --> POS
DISP --> OFF
DISP --> CHILD
DisplayTopologyXmlStore handles persistence:
saveTopology()/restoreTopology()— serialize/deserialize topology trees- LRU ordering:
prependTopology()moves active topology to head; max 100 stored topologies - Unique ID mapping: converts between runtime
displayIdand persistentuniqueIdfor surviving display reconnection MIN_REORDER_WHICH_TRIGGERS_PERSISTENCE = 10— avoids excessive disk writes
34.4 Foldable State Mapping
DeviceStateToLayoutMap maps device state IDs to display arrangements:
SparseArray<Layout> mLayoutMap— each foldable state (closed, open, half-open, concurrent) maps to aLayout- Each layout defines enabled/disabled displays, position (
POSITION_FRONT/POSITION_REAR), brightness throttling, and lead display for synchronized displays
Configuration example (display_layout_configuration.xml):
<layout>
<state>4</state> <!-- CONCURRENT_INNER_DEFAULT -->
<display defaultDisplay="true" enabled="true">
<address>4619827353912518656</address>
<position>front</position>
<brightnessThrottlingMapId>concurrent</brightnessThrottlingMapId>
</display>
<display enabled="true">
<address>4619827353912518657</address>
<position>rear</position>
<leadDisplayAddress>4619827353912518656</leadDisplayAddress>
</display>
</layout>
34.5 Display Viewport and Input Mapping
DisplayViewport maps physical display coordinates to logical coordinates:
VIEWPORT_INTERNAL- Built-in displayVIEWPORT_EXTERNAL- HDMI/USB-C displayVIEWPORT_VIRTUAL- Software display- Contains:
displayId,orientation,logicalFrame,physicalFrame,deviceWidth/Height
Coordinate transformation: all spatial calculations use density-independent pixels via pxToDp() / dpToPx() for cross-display consistency. DisplayTopology.normalize() prevents overlaps by clamping offsets within parent bounds and re-parenting displays that lose contact after repositioning.
Reference files:
frameworks/base/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java— Coordinates display spatial arrangement, persistent layout storage, and foldable state mappingframeworks/base/core/java/android/hardware/display/DisplayTopology.java— Tree structure with normalization, absolute bounds calculation, and graph generationframeworks/base/core/java/android/hardware/display/DisplayTopologyGraph.java— Spatial adjacency graph for native input routingframeworks/base/services/core/java/com/android/server/display/DisplayTopologyXmlStore.java— XML persistence layer with LRU orderingframeworks/base/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java— Foldable device state to display layout mappingframeworks/base/services/core/java/com/android/server/display/layout/Layout.java— Display arrangement configurationframeworks/base/core/java/android/view/DisplayInfo.java— Display properties including density, dimensions, rotation, and unique IDframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java— Display layout metrics for DPI conversion and bounds calculation
35. Cross-Display Window Movement
This section traces the complete flow when a window (task) moves from one display to another — for example, dragging a freeform window from the primary display to an external HDMI display.
35.1 Trigger Mechanisms
A cross-display move can be triggered in three ways:
| Trigger | Entry Point | Context |
|---|---|---|
| System API | ActivityTaskManagerService.moveRootTaskToDisplay(taskId, displayId) |
Requires INTERNAL_SYSTEM_WINDOW permission |
| Desktop drag | DesktopTasksController.moveToDisplay(task, displayId, bounds) |
User drags window caption across displays |
| Cross-display drag gesture | CaptionWindowDecorViewModel detects MotionEvent on different displayId |
Connected displays with ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG flag |
35.2 Complete Server-Side Flow
The core reparenting traverses four layers:
sequenceDiagram
participant ATMS as ActivityTaskManagerService
participant RWC as RootWindowContainer
participant Task as Task
participant WC as WindowContainer
participant AR as ActivityRecord
participant WS as WindowState
participant TDA as TaskDisplayArea (target)
ATMS->>RWC: moveRootTaskToDisplay(taskId, displayId)
RWC->>RWC: Validate target DisplayContent exists
RWC->>RWC: moveRootTaskToTaskDisplayArea(task, targetTDA)
RWC->>Task: reparent(targetTDA, onTop)
Task->>Task: canBeLaunchedOnDisplay(displayId)?
Task->>Task: Adjust windowingMode if unsupported
Task->>WC: reparent(newParent, position)
Note over WC: Core reparenting logic
WC->>WC: transitionController.collectReparentChange()
WC->>WC: oldParent.removeChild(this)
WC->>WC: newParent.addChild(this, position)
Note over WC,WS: If display changed (prevDc != newDc)
WC->>Task: onDisplayChanged(newDisplayContent)
Task->>AR: onDisplayChanged(dc) [recursive]
AR->>WS: onDisplayChanged(dc) [recursive]
WC->>WC: onParentChanged(newParent, oldParent)
WC->>WC: reparentSurfaceControl(tx, newParent.mSurfaceControl)
WC->>WC: mParent.assignChildLayers()
Task->>TDA: onTaskMoved(task, onTop)
TDA->>TDA: notifyTaskMovedToFront/Back()
RWC->>Task: resumeNextFocusAfterReparent()
Task->>Task: adjustFocusToNextFocusableTask()
Task->>RWC: resumeFocusedTasksTopActivities()
Task->>RWC: ensureActivitiesVisible()
Key steps in WindowContainer.reparent():
- Transition collection:
transitionController.collectReparentChange()records the reparent for shell transition animation - Hierarchy update: Remove from old parent, add to new parent
- Display change propagation: If display changed,
onDisplayChanged()cascades recursively through Task → TaskFragment → ActivityRecord → WindowState - Surface reparenting:
reparentSurfaceControl()moves theSurfaceControlto the new parent’s surface tree viaSurfaceControl.Transaction.reparent() - Z-order reassignment:
mParent.assignChildLayers()recalculates layer ordering on new display - Layout invalidation: Both old and new
DisplayContentmarkedsetLayoutNeeded()
35.3 Activity Lifecycle During Display Move
When an Activity’s task moves to a different display, the system decides between three paths:
flowchart TD
A[Task reparented to new display] --> B[ActivityRecord.onDisplayChanged]
B --> C[Configuration diff calculated]
C --> D{Config changes detected?}
D -->|No changes| E["Path A: Move notification only"]
E --> E1["scheduleActivityMovedToDisplay()"]
E1 --> E2["Activity.onMovedToDisplay(displayId, config)"]
D -->|Changes detected| F{"App handles changes?<br/>android:configChanges"}
F -->|Yes: app handles| G["Path B: Reconfigure in-place"]
G --> G1["MoveToDisplayItem scheduled"]
G1 --> G2["Activity.onMovedToDisplay()"]
G2 --> G3["Activity.onConfigurationChanged()"]
F -->|No: unhandled changes| H["Path C: Destroy and recreate"]
H --> H1["ActivityRelaunchItem scheduled"]
H1 --> H2["Activity.onDestroy()"]
H2 --> H3["Activity.onCreate() with new config"]
H3 --> H4["Activity.onResume()"]
Path A — No configuration change (rare, same-spec displays):
- Only
Activity.onMovedToDisplay(int displayId, Configuration config)fires ViewRootImpl.onMovedToDisplay()updates display reference and IME state
Path B — App handles config changes (declares in manifest):
MoveToDisplayItemtransaction sent to client- Activity receives
onMovedToDisplay()first, thenonConfigurationChanged() - No destruction — Activity adapts its layout in-place
Path C — Activity relaunched (unhandled config changes):
- Full lifecycle:
onPause()→onStop()→onDestroy()→onCreate()→onStart()→onResume() ActivityRelaunchItempreserves pending results and intentspreserveWindowflag may allow window surface reuse to reduce flicker
Configuration changes that commonly differ between displays:
- Screen density (
CONFIG_DENSITY) - Screen size (
CONFIG_SCREEN_SIZE) - Smallest screen width (
CONFIG_SMALLEST_SCREEN_SIZE) - Orientation (
CONFIG_ORIENTATION) - Layout direction (
CONFIG_LAYOUT_DIRECTION) - Display ID (always changes)
35.4 Client-Side Processing
On the app side, ActivityThread processes the display move:
sequenceDiagram
participant Server as WM Server
participant AT as ActivityThread
participant Act as Activity
participant VRI as ViewRootImpl
participant RM as ResourcesManager
Server->>AT: MoveToDisplayItem.execute()
AT->>RM: updateResourcesForActivity(token, config, displayId)
Note over RM: Load display-specific<br/>resources and metrics
AT->>Act: dispatchMovedToDisplay(displayId, config)
Act->>Act: onMovedToDisplay(displayId, config)
AT->>VRI: onMovedToDisplay(displayId, config)
VRI->>VRI: updateInternalDisplay(displayId)
VRI->>VRI: mImeFocusController.onMovedToDisplay()
VRI->>VRI: mView.dispatchMovedToDisplay(display, config)
AT->>Act: onConfigurationChanged(newConfig)
VRI->>VRI: requestLayout()
Note over VRI: Full measure/layout pass<br/>with new display metrics
ViewRootImpl updates:
- Internal
Displayreference updated to new display ID - IME focus controller notified (IME may differ per display)
- View tree receives
dispatchMovedToDisplay()cascade requestLayout()triggers full re-measure with new display metrics- Window surfaces may be reallocated for different display resolution
35.5 Shell-Side: Desktop Mode Cross-Display Drag
When using desktop mode, DesktopTasksController.moveToDisplay() orchestrates the move with additional desktop-specific logic:
Cross-display drag detection (during caption drag):
MotionEvent.displayId != task.displayId
AND ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG == true
AND target DisplayAreaInfo exists
DPI-aware bounds conversion:
- Source display insets converted:
sourceLayout.pxToDp()→ device-independent - Target display insets applied:
destLayout.dpToPx()→ destination pixels MultiDisplayDragMoveBoundsCalculator.constrainBoundsForDisplay()ensures window fits target display (shrinks if necessary whenENABLE_SHRINK_WINDOW_BOUNDS_AFTER_DRAGis set)
WCT operations for desktop cross-display move:
| Operation | Purpose |
|---|---|
wct.reparent(task.token, displayAreaInfo.token, true) |
Move task to target display’s TaskDisplayArea |
wct.setBounds(task.token, bounds) |
Set DPI-adjusted bounds on target display |
wct.setAppBounds(task.token, appBounds) |
Set app-content bounds |
wct.reorder(task.token, true, true) |
Bring to front including parents |
Transition animation:
DesktopModeMoveToDisplayTransitionHandler— 100ms alpha fade animation withLINEARinterpolatorWindowDragTransitionHandler— immediate surface position for drag drops (no animation delay)TransitionInfo.ChangetracksstartDisplayIdandendDisplayIdto identify cross-display changes- Jank monitoring:
CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY
35.6 Surface Reparenting Details
The surface tree must mirror the container hierarchy change:
graph LR
subgraph "Before"
B_DC0["DisplayContent[0]"] --> B_TDA0["TaskDisplayArea"]
B_TDA0 --> B_TASK["Task (SurfaceControl)"]
end
subgraph "After"
A_DC0["DisplayContent[0]"] --> A_TDA0["TaskDisplayArea<br/>(task removed)"]
A_DC1["DisplayContent[1]"] --> A_TDA1["TaskDisplayArea"]
A_TDA1 --> A_TASK["Task (SurfaceControl reparented)"]
end
WindowContainer.reparentSurfaceControl():
- Uses sync transaction:
transaction.reparent(mSurfaceControl, newParent.mSurfaceControl) - If an animator holds a leash on the surface, reparenting is deferred (leash stays with animator until animation completes)
- After reparent,
assignChildLayers()recalculates z-ordering on the new display
Task.reparentSurfaceControl() override:
- Skips reparenting for always-on-top organized tasks (e.g., bubbles) that manage their own surface hierarchy
35.7 Post-Move Updates
After reparenting completes, several systems update:
| System | Update | Method |
|---|---|---|
| Focus | Focus moves to target display | adjustFocusToNextFocusableTask("reparent") |
| Visibility | All activities rechecked | ensureActivitiesVisible() |
| Activity lifecycle | Top activity resumed | resumeFocusedTasksTopActivities() |
| Task listeners | Display change broadcast | notifyTaskDisplayChanged(taskId, displayId) |
| TaskFragment organizers | Parent info updated | sendTaskFragmentParentInfoChangedIfNeeded() |
| Input routing | Per-display focus updated | Display input focus recalculated |
| IME | IME controller notified | mImeFocusController.onMovedToDisplay() |
| Letterboxing | Recalculated for new display | onMovedToDisplay(displayId) on compat policy |
35.8 Feature Flags
| Flag | Purpose |
|---|---|
ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG |
Enable cross-display drag gestures |
ENABLE_SHRINK_WINDOW_BOUNDS_AFTER_DRAG |
Constrain oversized windows to fit target display |
ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS |
Bring destination display to focus after move |
ENABLE_WINDOW_DROP_SMOOTH_TRANSITION |
Smooth visual indicator cleanup on drop |
ENABLE_MULTIPLE_DESKTOPS_BACKEND |
Enable desk-based reparenting across displays |
Reference files:
frameworks/base/services/core/java/com/android/server/wm/Task.java— Task.reparent() and reparentToDisplay() implement cross-display task movement with surface reparentingframeworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java— moveRootTaskToDisplay() and moveRootTaskToTaskDisplayArea() orchestrate server-side cross-display movesframeworks/base/services/core/java/com/android/server/wm/DisplayContent.java— Per-display hierarchy receiving reparented tasks, triggers layout invalidation and focus recalculation
Part VIII: Rendering Pipeline
36. Graphics Buffer Pipeline: From Buffer to Activity
This section traces the complete path of a rendered frame from a View’s invalidate() call through to pixels appearing on the display.
36.1 Complete Pipeline Overview
sequenceDiagram
participant View as View (UI Thread)
participant VRI as ViewRootImpl
participant Choreo as Choreographer
participant TR as ThreadedRenderer
participant RT as RenderThread
participant Pipeline as SkiaGL/VulkanPipeline
participant GPU as GPU
participant BQ as BufferQueue
participant SF as SurfaceFlinger
participant HW as Display Hardware
View->>VRI: invalidate()
VRI->>Choreo: scheduleTraversals() / postCallback(TRAVERSAL)
Note over Choreo: Next VSYNC
Choreo->>VRI: performTraversals()
VRI->>TR: performDraw()
TR->>TR: view.updateDisplayListIfDirty() (record display list)
TR->>RT: createRenderRequest().syncAndDraw()
Note over RT: RenderThread (off main thread)
RT->>Pipeline: CanvasContext.draw()
Pipeline->>GPU: Skia GPU command buffer
GPU->>BQ: Render to BufferQueue slot (from ANativeWindow)
BQ->>SF: queueBuffer() + SurfaceControl.Transaction.setBuffer()
Note over SF: Next VSYNC
SF->>SF: acquireBuffer() / HWC or GPU composition
SF->>HW: Pixels on screen
36.2 ViewRootImpl — performDraw()
Source: frameworks/base/core/java/android/view/ViewRootImpl.java (line 5548)
performDraw() is the entry point for every frame draw. It:
- Checks whether hardware rendering is enabled (
mAttachInfo.mHardwareRenderer != null) - Calls
draw(fullRedrawNeeded, surfaceSyncGroup, mSyncBuffer)(line 5578) - Inside
draw(): if hardware renderer available, callsmAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this)(line 5927)
36.3 ThreadedRenderer and HardwareRenderer
Source: frameworks/base/core/java/android/view/ThreadedRenderer.java
ThreadedRenderer.draw() (line 828) creates a FrameRenderRequest:
createRenderRequest()
.setVsyncTime(vsync)
.syncAndDraw();
Source: frameworks/base/graphics/java/android/graphics/HardwareRenderer.java (lines 430–546)
FrameRenderRequest is a builder that accumulates frame parameters:
setVsyncTime(long)(line 464): sets frame timing for Choreographer syncsetFrameCommitCallback(Executor, Runnable)(line 487): fires when frame is submitted to GPUsyncAndDraw()(line 523): invokes nativenSyncAndDrawFrame()— this blocks the calling thread until the RenderThread has synced the display list tree and submitted the frame to the GPU pipeline
36.4 RenderThread — Frame Loop
Source: frameworks/base/libs/hwui/renderthread/RenderThread.cpp
threadLoop()(line 393): main RenderThread event loop; waits for messages and Choreographer callbacksframeCallback()(line 73): called on VSYNC; triggersdispatchFrameCallbacks()(line 368)postFrameCallback()(line 427): registers a Choreographer frame callback from the render thread
The RenderThread drives CanvasContext.draw(), which calls into the active pipeline (OpenGL or Vulkan) to render the recorded display list.
36.5 Surface and BufferQueue
Source: frameworks/base/core/java/android/view/Surface.java
Surface wraps a native ANativeWindow, which itself wraps an IGraphicBufferProducer (the producer side of a BufferQueue).
Source: frameworks/native/libs/gui/Surface.cpp (lines 121–150)
The native Surface implements ANativeWindow hooks:
dequeueBuffer()— asks BufferQueue for a free slot to render intoqueueBuffer()— returns the rendered buffer to the consumer (SurfaceFlinger)cancelBuffer()— returns a buffer without queuing (on error)
Buffer lifecycle:
sequenceDiagram
participant App as Producer (App)
participant BQ as BufferQueue
participant SF as Consumer (SurfaceFlinger)
App->>BQ: dequeueBuffer(slot, fence)
Note over App: Render into slot
App->>BQ: queueBuffer(slot, fence)
BQ->>SF: acquireBuffer(slot, fence)
Note over SF: Wait on acquire fence
Note over SF: Composite
SF->>BQ: releaseBuffer(slot, fence)
BQ->>App: dequeueBuffer() (reuse slot)
Source: frameworks/native/libs/gui/include/gui/BufferQueue.h, BufferQueueCore.h
36.6 SurfaceControl.Transaction.setBuffer()
Source: frameworks/base/core/java/android/view/SurfaceControl.java (lines 4658–4761)
public @NonNull Transaction setBuffer(
@NonNull SurfaceControl sc,
@Nullable HardwareBuffer buffer,
@Nullable SyncFence acquireFence) {
// ...
nativeSetBuffer(mNativeObject, sc.mNativeObject,
buffer == null ? 0 : buffer.getNativeHandle(),
acquireFence == null ? -1 : acquireFence.getFd());
return this;
}
This JNI call translates into a SurfaceFlinger transaction that attaches the buffer to the Layer. On apply(), SurfaceFlinger receives the transaction atomically and, on the next VSYNC, composites it.
36.7 SurfaceFlinger Composition
Source: AOSP Graphics Architecture
On each VSYNC, SurfaceFlinger:
- Walks its layer list; acquires new buffers from
BufferQueuewhere available - Calls
HWC::validateDisplay()— the HWC decides which layers go to hardware overlay planes and which need GPU compositing - If GPU compositing needed:
RenderEnginedraws CLIENT layers into a framebuffer - Calls
HWC::presentDisplay()— commits to display hardware with a present fence - Returns release fences to producers via
BufferQueue::releaseBuffer()
Reference files:
frameworks/base/core/java/android/view/ViewRootImpl.java—performDraw()(line 5548)frameworks/base/core/java/android/view/ThreadedRenderer.java—draw()(line 828)frameworks/base/graphics/java/android/graphics/HardwareRenderer.java—FrameRenderRequest(lines 430–546)frameworks/base/libs/hwui/renderthread/RenderThread.cpp—threadLoop()(line 393)frameworks/native/libs/gui/Surface.cpp— BufferQueue producer side (lines 121–150)frameworks/base/core/java/android/view/SurfaceControl.java—setBuffer()(lines 4658–4761)frameworks/base/core/java/android/view/Surface.java— Client-side Surface API wrapping BufferQueue producerframeworks/native/libs/gui/include/gui/BufferQueue.h— Native BufferQueue producer-consumer interfaceframeworks/native/libs/gui/include/gui/BufferQueueCore.h— BufferQueue internal slot management- BufferQueue and Gralloc — AOSP Docs
- Graphics Architecture — AOSP Docs
37. HWUI Performance Architecture
HWUI (Hardware UI) is Android’s hardware-accelerated rendering library. Every View drawing command passes through HWUI’s pipeline. Performance comes from display list caching, GPU pipeline selection, shader caching, and frame pipelining.
37.1 Pipeline Selection
Source: frameworks/base/libs/hwui/renderthread/CanvasContext.cpp (lines 82–107)
HWUI supports three rendering pipelines, selected at runtime. CanvasContext::create() instantiates the correct pipeline and wraps it in a CanvasContext:
// CanvasContext::create() — simplified
auto renderType = Properties::getRenderPipelineType();
switch (renderType) {
case RenderPipelineType::SkiaGL:
return new CanvasContext(thread, ...,
std::make_unique<SkiaOpenGLPipeline>(thread), ...); // default
case RenderPipelineType::SkiaVulkan:
return new CanvasContext(thread, ...,
std::make_unique<SkiaVulkanPipeline>(thread), ...); // opt-in
case RenderPipelineType::SkiaCpu: // #ifndef ANDROID
return new CanvasContext(thread, ...,
std::make_unique<SkiaCpuPipeline>(thread), ...); // non-Android only
}
Switch to Vulkan at runtime (no recompile needed):
adb shell setprop debug.hwui.renderer skiavk
37.2 OpenGL Pipeline (Default)
Source: frameworks/base/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
Key stages per frame:
makeCurrent()(line 58): EGL context switch to window surfacegetFrame()(line 110):mEglManager.beginFrame()— dequeue buffer fromANativeWindowdraw()(line 116):- Creates
GrGLFramebufferInfowrapping FBO0 (line 128) - Creates
SkSurfacefrom the framebuffer (line 157) - Renders the display list tree into
SkSurface - Flushes Skia’s deferred command buffer (line 182)
- Creates
swapBuffers()(line 194):eglSwapBuffers()with damage regions for partial updatesflush()(line 261): Creates release fence for buffer synchronization
Incremental updates: mEglManager.damageFrame(frame, dirty) (line 123) reports changed regions, allowing EGL_KHR_partial_update to only re-render dirty areas.
37.3 Vulkan Pipeline
Source: frameworks/base/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
Differences from OpenGL pipeline:
makeCurrent()(line 58): CreatesVkSurfacefromANativeWindowif not yet createdgetFrame()(line 69):vulkanManager().dequeueNextBuffer()— Vulkan swapchain bufferfinishFrame()(line 118): Vulkan-native submission (noeglSwapBuffers)swapBuffers()(line 130): Returns present fence for hardware synchronization- HDR support (line 184):
setTargetSdrHdrRatio()— Vulkan enables per-frame HDR headroom control
Vulkan advantages: Reduced CPU overhead (explicit synchronization, no hidden driver state), better pipeline caching, native AHardwareBuffer integration, and HDR tone mapping support.
37.4 Display List and RenderNode Caching
Source: frameworks/base/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
Every View maintains a RenderNode. When a view is not invalidated, its display list is replayed from cache rather than regenerated. RenderNodeDrawable:
- Stores
mRecordedTransform(line 44): the view’s matrix at recording time - On replay: applies the cached transform without re-recording
- Projected nodes (e.g., ripple effects from child to parent): handled via backward projection (lines 54–87)
- Clip outlines (rounded corners): applied from
RenderNode’s clip outline (line 89)
Display list execution is deferred — Skia accumulates commands in its own internal SkPicture-like structure and submits them to the GPU in one batch.
37.5 Shader and Pipeline Cache
Source: frameworks/base/libs/hwui/pipeline/skia/ directory (48 files)
| File | Purpose |
|---|---|
ShaderCache.cpp |
Disk-persistent Skia shader program cache; avoids recompilation on relaunch |
PipelineCache.cpp |
GPU pipeline state object cache |
PersistentGraphicsCache.cpp |
Cross-session persistent resource cache |
SkiaMemoryTracer.cpp |
Memory usage tracking for GPU resources |
The shader cache significantly reduces first-frame jank for apps that have run before. Skia serializes compiled shader programs to disk; on next launch, these are loaded and submitted to the GPU driver directly.
37.6 Frame Pipelining and Vsync
Source: frameworks/base/libs/hwui/renderthread/RenderThread.cpp
HWUI uses a producer-consumer frame pipeline:
- Main thread: records display list changes (via
View.draw()→SkCanvas) - RenderThread: executes display list → GPU submission, runs on its own VSYNC offset
- GPU: executes commands asynchronously
- SurfaceFlinger: composites on VSYNC
This 3-stage pipeline (UI thread + RenderThread + GPU) allows the app to prepare the next frame while the current frame is still being composited, targeting sub-frame latency.
37.7 Full Render Pipeline: View.draw() to SurfaceFlinger
The complete HWUI render pipeline bridges the Java View system to native GPU rendering and SurfaceFlinger composition:
sequenceDiagram
participant App as UI Thread (View.draw)
participant RC as RecordingCanvas (SkiaRecordingCanvas)
participant RN as RenderNode Tree
participant RP as RenderProxy
participant DFT as DrawFrameTask
participant RT as RenderThread
participant CC as CanvasContext
participant PL as IRenderPipeline (GL or Vulkan)
participant ANW as ANativeWindow (Surface)
participant SF as SurfaceFlinger
App->>RC: Canvas.drawRect(), drawText(), etc.
RC->>RC: Record ops into DisplayList
App->>RN: RenderNode.endRecording()
Note over RN: Staging DisplayList updated
App->>RP: HardwareRenderer.syncAndDraw()
RP->>DFT: postAndWait()
DFT->>RT: Run on RenderThread (DrawFrameTask::run)
DFT->>DFT: syncFrameState()
DFT->>CC: prepareTree()
Note over CC: Sync staging DisplayLists → active<br/>compute damage regions
DFT-->>App: Unblock UI thread
CC->>PL: makeCurrent()
CC->>PL: getFrame()
PL->>ANW: dequeueBuffer()
ANW-->>PL: Buffer (gralloc-backed)
CC->>PL: draw()
Note over PL: Skia renders RenderNode tree<br/>into dequeued buffer via<br/>GrDirectContext (GL/Vulkan)
CC->>PL: swapBuffers()
PL->>ANW: queueBuffer(buffer, fence)
ANW->>SF: BufferQueue delivers frame
SF->>SF: Composite with other layers
Key components in the chain:
| Component | Thread | Role |
|---|---|---|
HardwareRenderer (Java) |
UI | Java entry point, owns RenderNode root |
RenderProxy (C++) |
UI→RT | Bridge: posts DrawFrameTask to RenderThread |
DrawFrameTask |
RT | Orchestrates sync + draw phases |
CanvasContext |
RT | Per-window rendering context, owns pipeline |
IRenderPipeline |
RT | GPU backend (SkiaOpenGLPipeline or SkiaVulkanPipeline) |
RenderThread |
RT | Singleton thread with GPU context, Choreographer |
37.8 RenderNode, DisplayList, and Recording
The recording phase captures all draw operations from View.draw() into a replayable DisplayList:
graph TB
subgraph "Recording (UI Thread)"
VIEW["View.draw(Canvas)"]
SRC["SkiaRecordingCanvas"]
DL["DisplayList<br/>(SkiaDisplayList)"]
RN["RenderNode<br/>(staging DisplayList)"]
end
subgraph "Sync (RenderThread)"
SYNC["prepareTree()"]
ACTIVE["RenderNode<br/>(active DisplayList)"]
DAMAGE["DamageAccumulator<br/>(dirty regions)"]
end
subgraph "Render (RenderThread)"
RND["RenderNodeDrawable"]
SKIA["Skia SkCanvas"]
GPU["GrDirectContext<br/>(Ganesh GPU)"]
end
VIEW --> SRC
SRC -->|"finishRecording()"| DL
DL -->|"setStagingDisplayList()"| RN
RN -->|"syncFrameState()"| SYNC
SYNC --> ACTIVE
SYNC --> DAMAGE
ACTIVE -->|"replay"| RND
RND --> SKIA
SKIA --> GPU
- RenderNode (
RenderNode.h): EachViewowns aRenderNodecontaining a staging and activeDisplayList. Properties (transforms, alpha, clip, elevation) are tracked viaRenderPropertieswith aDirtyPropertyMaskto detect changes. - RecordingCanvas (
RecordingCanvas.h): Extends Skia’sSkNoDrawCanvasto record (not execute) draw calls intoDisplayListData. - SkiaRecordingCanvas (
SkiaRecordingCanvas.h): WrapsRecordingCanvaswithSkiaDisplayList, adding support for layer rendering, animated image updates, andVectorDrawable. - DamageAccumulator: Propagates dirty regions up the RenderNode tree, enabling partial-frame updates via
EGL_KHR_partial_update(GL) or Vulkan damage rects. - DeferredLayerUpdater (
DeferredLayerUpdater.h): ManagesTextureViewcontent — holds anASurfaceTextureconsumer and maps buffer slots toSkImageobjects viaImageSlot.
37.9 GPU Memory and Cache Management
CacheManager (CacheManager.h): Manages Skia’s GPU resource budget per pipeline:
configureContext()— setsGrContextOptions(resource limits, shader cache)trimMemory()— responds toComponentCallbacks2.onTrimMemory()levelsgetMemoryUsage()— reports CPU and GPU resource consumptiononFrameCompleted()— frame-end resource cleanup
GrDirectContext (Ganesh): Shared singleton GPU context on RenderThread. Created lazily on first GPU access. Manages shader programs, pipeline state objects, texture atlases, and buffer pools. Backed by either EGL (GL) or Vulkan device.
HWUI directory structure:
libs/hwui/
├── pipeline/skia/ GPU rendering pipelines (GL + Vulkan)
├── renderthread/ RenderThread, CanvasContext, DrawFrameTask, RenderProxy
├── renderstate/ GPU state management
├── jni/ Java↔C++ JNI bindings
├── canvas/ Canvas implementation (recording)
├── RenderNode.h View's renderable representation
├── DisplayList.h Recorded draw operations
├── DamageAccumulator.h Dirty region tracking
├── DeferredLayerUpdater.h TextureView/layer content management
├── LightingInfo.h Shadow light source
├── RenderProperties.h Per-node rendering properties (elevation, alpha, etc.)
└── Properties.h Global HWUI configuration (pipeline type, etc.)
Reference files:
frameworks/base/graphics/java/android/graphics/HardwareRenderer.java— Java entry point, creates RenderProxy via JNIframeworks/base/libs/hwui/renderthread/RenderProxy.h— UI thread → RenderThread bridgeframeworks/base/libs/hwui/renderthread/DrawFrameTask.h— Frame sync/draw orchestrationframeworks/base/libs/hwui/renderthread/RenderThread.h— Singleton render thread with GPU contextframeworks/base/libs/hwui/renderthread/RenderThread.cpp— RenderThread event loop and frame callbacksframeworks/base/libs/hwui/renderthread/CanvasContext.h— Per-window rendering context, pipeline ownerframeworks/base/libs/hwui/renderthread/CanvasContext.cpp— Pipeline selection logic and draw orchestrationframeworks/base/libs/hwui/renderthread/IRenderPipeline.h— Pipeline interface (makeCurrent/draw/swapBuffers)frameworks/base/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp— GL pipeline (EGL + FBO0)frameworks/base/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp— Vulkan pipeline (VkSurface + swapchain)frameworks/base/libs/hwui/pipeline/skia/SkiaPipeline.h— Base Skia pipeline (renderFrame)frameworks/base/libs/hwui/RenderNode.h— View’s renderable node (DisplayList, RenderProperties)frameworks/base/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h— Draw op recordingframeworks/base/libs/hwui/DisplayList.h— Recorded draw operations containerframeworks/base/libs/hwui/DamageAccumulator.h— Dirty region propagationframeworks/base/libs/hwui/DeferredLayerUpdater.h— TextureView layer managementframeworks/base/libs/hwui/renderthread/CacheManager.h— GPU memory managementframeworks/base/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp— Display list replay and cachingframeworks/base/libs/hwui/pipeline/skia/ShaderCache.cpp— Persistent shader cacheframeworks/base/libs/hwui/renderthread/EglManager.h— EGL surface/context management
38. Window Shadow System
Android’s shadow system has two distinct layers: view-level shadows (per-View elevation) and window-level shadows (per-WindowState compositor rendering). Both ultimately use the same underlying Skia tessellation algorithms.
38.1 Two Shadow Types
Android renders two shadow components for every elevated surface, matching Material Design’s physical lighting model:
| Type | Light Source | Characteristics |
|---|---|---|
| Ambient shadow | Omnidirectional (no position) | Soft, equal in all directions, represents ambient fill light |
| Spot shadow | Point light at (displayWidth/2, 0, 600dp), radius 800dp |
Harder edge, offset downward, simulates overhead key light |
Default Material alpha values (API 28+):
ambientShadowAlpha= 0.039 (3.9%)spotShadowAlpha= 0.19 (19%)
38.2 Skia Shadow Tessellator
Source: external/skia/src/utils/SkShadowTessellator.cpp and .h
Shadows are rendered by Skia’s SkShadowTessellator, which tessellates the shadow geometry into triangles for GPU rendering.
Ambient Shadow Algorithm (SkAmbientShadowTessellator)
Lines 912–956 in SkShadowTessellator.cpp:
// Compute blur radius from z-height
auto outset = SkDrawShadowMetrics::AmbientBlurRadius(baseZ);
auto inset = outset * SkDrawShadowMetrics::AmbientRecipAlpha(baseZ) - outset;
// Generate umbra (inner) + penumbra (outer) rings
// Inner ring (umbra): SK_ColorBLACK (opaque core)
// Outer ring (penumbra): SK_ColorTRANSPARENT (fades to clear)
kUmbraColor = SK_ColorBLACK;
kPenumbraColor = SK_ColorTRANSPARENT;
For convex paths: computeConvexShadow() — walks the polygon vertices outward.
For concave paths: computeConcaveShadow() — handles interior angles with special tessellation.
Spot Shadow Algorithm (SkSpotShadowTessellator)
Lines 1010–1071:
// Project geometry from light source position
SkDrawShadowMetrics::GetSpotShadowTransform(
lightPos, lightRadius, ctm, zPlaneParams, ...);
// Three rings: penumbra (outer) + umbra (middle) + inner core
// Light position creates offset → realistic downward shadow offset
The spot shadow is geometrically projected from the light source: surfaces closer to the light cast larger, softer shadows; surfaces far from the light (high elevation) cast farther, more offset shadows.
38.3 Light Source Configuration
Source: frameworks/base/libs/hwui/LightingInfo.h
static Vector3 getLightCenter() {
if (Properties::overrideLightPosY > 0 || Properties::overrideLightPosZ > 0) {
Vector3 adjusted = mLightCenter;
adjusted.y = -Properties::overrideLightPosY; // negated: shifts up
adjusted.z = Properties::overrideLightPosZ;
return adjusted;
}
return mLightCenter;
}
Source: frameworks/base/libs/hwui/Lighting.h
struct LightGeometry {
Vector3 center; // light position (x, y, z)
float radius; // penumbra radius of the light source
};
The default light is positioned at approximately (display_width/2, 0, 600dp) with radius ~800dp. This is why Android spot shadows are offset slightly downward — the light is above and slightly behind the device screen.
38.4 RenderNode Elevation → Shadow Rendering
Source: frameworks/base/libs/hwui/RenderProperties.h (lines 256–297)
bool setElevation(float elevation) {
return RP_SET(mPrimitiveFields.mElevation, elevation);
}
// Z = elevation + translationZ
float getZ() const { return getElevation() + getTranslationZ(); }
Flow from Java to GPU:
View.setElevation(dp)→RenderNode.setElevation(px)RenderProperties.mElevationset (in pixels)- During
RenderNoderecording, Skia readsgetZ()as the z-plane parameter SkCanvas::drawShadow()is called with the shape, z, light geometry, and colorSkShadowTessellatorgenerates triangle strips- GPU rasterizes the triangle strips as the shadow geometry
38.5 Window-Level Shadows
Source: frameworks/base/core/java/com/android/internal/policy/DecorView.java (lines 1728–1732)
Window shadows can be rendered in two ways:
final boolean renderShadowsInCompositor = mWindow.mRenderShadowsInCompositor;
if (winConfig.hasWindowShadow() && !renderShadowsInCompositor) {
// Force translucent background to make view-level shadow visible
// View-level elevation shadow drawn by Skia into the window's own buffer
}
// If renderShadowsInCompositor: SurfaceFlinger/HWC handles the shadow layer
Mode 1 — View-level shadow: DecorView has elevation set; Skia draws the shadow into the window’s color buffer. The window surface must be translucent (RGBA) for the shadow to be visible outside the window bounds.
Mode 2 — Compositor shadow: SurfaceFlinger renders the shadow as a separate layer below the window’s layer. This avoids requiring the window to be translucent and enables shadows on opaque surfaces.
Reference files:
external/skia/src/utils/SkShadowTessellator.cpp— tessellation algorithmsexternal/skia/src/core/SkDrawShadowInfo.h— shadow metrics (blur radius formulas)frameworks/base/libs/hwui/LightingInfo.h— light position configurationframeworks/base/libs/hwui/Lighting.h—LightGeometrystructframeworks/base/libs/hwui/RenderProperties.h— elevation → z coordinate (lines 256–297)frameworks/base/core/java/com/android/internal/policy/DecorView.java— window shadow mode selection- SpotShadow.cpp AOSP mirror (Oreo)
39. HDR for Window and Surface
Android’s HDR support spans four layers: display capability detection, surface metadata, tone mapping, and mixed SDR+HDR composition.
39.1 HDR Display Capabilities
Source: frameworks/base/core/java/android/view/Display.java (lines 2904–2948)
public static final class HdrCapabilities implements Parcelable {
public static final int HDR_TYPE_DOLBY_VISION = 1; // DV — proprietary dynamic metadata
public static final int HDR_TYPE_HDR10 = 2; // HDR10 — static SMPTE ST 2084 (PQ)
public static final int HDR_TYPE_HLG = 3; // HLG — broadcast-friendly
public static final int HDR_TYPE_HDR10_PLUS = 4; // HDR10+ — dynamic metadata
// Luminance metadata from display EDID
private float mMaxLuminance; // peak brightness (nits)
private float mMaxAverageLuminance; // max average picture level (nits)
private float mMinLuminance; // minimum black level (nits)
}
Query display HDR support:
Display.HdrCapabilities caps = display.getHdrCapabilities();
if (caps != null) {
int[] supportedTypes = caps.getSupportedHdrTypes();
// Check for HDR10: Arrays.asList(supportedTypes).contains(HDR_TYPE_HDR10)
}
39.2 Surface HDR Headroom
Source: frameworks/base/core/java/android/view/SurfaceControl.java (lines 4955–4964)
// Set desired HDR-to-SDR headroom for the layer
// Ratio = max HDR brightness / SDR reference brightness
// 0 = disabled (auto), ≥ 1.0 = enable with specified ratio
public Transaction setDesiredHdrHeadroom(
@NonNull SurfaceControl sc,
@FloatRange(from = 0.0f) float desiredRatio) {
nativeSetDesiredHdrHeadroom(mNativeObject, sc.mNativeObject, desiredRatio);
return this;
}
This is the primary API for apps to request HDR brightness extension. A ratio of 4.0f allows the HDR content to be 4× brighter than SDR reference white.
39.3 DataSpace — Color Space Declaration
Source: frameworks/base/core/java/android/view/SurfaceControl.java (lines 4861–4865)
public Transaction setDataSpace(@NonNull SurfaceControl sc,
@DataSpace.NamedDataSpace int dataspace) {
nativeSetDataSpace(mNativeObject, sc.mNativeObject, dataspace);
return this;
}
Source: frameworks/base/core/java/android/hardware/DataSpace.java (lines 541–563)
| Constant | Value | Use Case |
|---|---|---|
DATASPACE_SRGB |
142671872 | Standard SDR (default) |
DATASPACE_DISPLAY_P3 |
143261696 | Wide color gamut (P3 primaries) |
DATASPACE_BT2020_HLG |
168165376 | HLG HDR video |
DATASPACE_BT2020_PQ |
163971072 | HDR10 / HDR10+ video (ST 2084 PQ) |
The dataspace is sent with each buffer to SurfaceFlinger. SurfaceFlinger uses it to select the appropriate tone-mapping path.
39.4 HDR Metadata
Source: frameworks/native/libs/gui/include/gui/HdrMetadata.h (lines 29–71)
struct HdrMetadata : public LightFlattenable<HdrMetadata> {
enum Type : uint32_t {
SMPTE2086 = 1 << 0, // Static: display mastering color volume
CTA861_3 = 1 << 1, // Static: content light level
HDR10PLUS = 1 << 2, // Dynamic: per-frame HDR10+ metadata
};
uint32_t validTypes{0};
android_smpte2086_metadata smpte2086{}; // primaries, white point, luminance
android_cta861_3_metadata cta8613{}; // MaxCLL, MaxFALL
std::vector<uint8_t> hdr10plus{}; // dynamic metadata blob
};
Metadata types:
- SMPTE ST 2086: Mastering display color volume (static per-content)
- CTA-861.3: Content Light Level metadata (MaxCLL, MaxFALL)
- HDR10+: Frame-by-frame dynamic metadata (scene-adaptive tone mapping)
39.5 Tone Mapping — libtonemap
Source: frameworks/native/libs/tonemap/include/tonemap/tonemap.h (lines 86–151)
class ToneMapper {
// Generate SkSL shader code for a given source→destination dataspace conversion
virtual std::string generateTonemapGainShaderSkSL(
Dataspace sourceDataspace,
Dataspace destinationDataspace) = 0;
// Generate shader uniform values from HDR metadata
virtual std::vector<ShaderUniform> generateShaderSkSLUniforms(
const Metadata& metadata) = 0;
};
struct Metadata {
float displayMaxLuminance; // Display peak brightness (nits)
float contentMaxLuminance; // Content peak brightness (nits)
float currentDisplayLuminance; // Current APL-adjusted brightness
AHardwareBuffer* buffer; // HDR metadata attached to buffer
RenderIntent renderIntent;
};
libtonemap is a vendor-configurable static library. OEMs implement ToneMapper to define their display-specific HDR→SDR tone-mapping curve. The output is an SkSL shader that SurfaceFlinger executes in its RenderEngine (Skia Graphite/Ganesh) when compositing HDR content onto SDR displays.
39.6 Mixed SDR + HDR Composition
Source: Mixed SDR and HDR Composition — AOSP Docs
Android 13+ supports displaying HDR and SDR content simultaneously:
- HDR layer: rendered with full HDR brightness
- SDR layers: dimmed relative to HDR (
hdrSdrRatio=desiredHdrHeadroom) - SurfaceFlinger uses
setHdrSdrRatioto scale SDR layers down proportionally
The Display.HdrCapabilities is checked; if isForceSdr is set (e.g., screen recording), all content is tone-mapped to SDR range.
Reference files:
frameworks/base/core/java/android/view/Display.java—HdrCapabilities(lines 2904–2948)frameworks/base/core/java/android/view/SurfaceControl.java—setDesiredHdrHeadroom(),setDataSpace(),setHdrMetadata()frameworks/base/core/java/android/hardware/DataSpace.java— dataspace constantsframeworks/native/libs/gui/include/gui/HdrMetadata.h— HDR metadata structureframeworks/native/libs/tonemap/include/tonemap/tonemap.h—ToneMapperinterface- Tone Mapping HDR — AOSP Docs
- HDR Video Playback — AOSP Docs
- Mixed SDR and HDR — AOSP Docs
40. Android’s Direct Rendering Infrastructure
The Linux Direct Rendering Infrastructure (DRI) consists of the DRM kernel module, libdrm userspace API, Mesa (GL/Vulkan implementation), KMS for display control, GEM/GBM for GPU buffer management, and DMA-BUF for zero-copy inter-process buffer sharing. Android has a direct functional analog to every component of this stack.
40.1 Android vs DRI: Component Map
| Linux DRI Component | Purpose | Android Equivalent | Android Location |
|---|---|---|---|
DRM kernel module (/dev/dri/card*) |
GPU command submission, display control | Linux DRM kernel (used directly by HWC) | /dev/dri/card* |
| libdrm userspace | Thin wrapper over DRM ioctls | DrmClient in HWC3 HAL |
device/generic/goldfish/hals/hwc3/DrmClient.cpp |
| Mesa (GL/Vulkan driver) | GPU rendering implementation | HWUI + Skia (GL/Vulkan backend) + vendor GPU drivers | frameworks/base/libs/hwui/, frameworks/native/libs/renderengine/ |
| KMS (Kernel Mode Setting) | Display enumeration, mode setting | HWComposer 2 (HWC2) | hardware/interfaces/graphics/composer/ |
| DRM overlay planes | Hardware compositing without GPU | HWC layer composition (DEVICE type) | hardware/libhardware/include_all/hardware/hwcomposer2.h |
| GEM/GBM buffer allocation | GPU-accessible buffer allocation | Gralloc HAL | hardware/interfaces/graphics/allocator/ |
| DMA-BUF file descriptor | Zero-copy buffer sharing | native_handle_t / AHardwareBuffer |
frameworks/native/libs/nativewindow/AHardwareBuffer.cpp |
sync_file / explicit fences |
GPU-CPU-display synchronization | Fence / SyncFence |
frameworks/native/libs/ui/Fence.cpp |
EGLImage (EGL_KHR_image_base) |
Zero-copy GPU texture from buffer | EGLImageKHR / GLConsumer |
frameworks/native/libs/gui/include/gui/GLConsumer.h |
40.2 DRM/KMS Usage — HWC3 DrmClient
Source: device/generic/goldfish/hals/hwc3/DrmClient.cpp (lines 33–93)
Android’s HWC3 (Hardware Composer 3) implementation uses DRM/KMS directly:
// Open DRM device — iterates through /dev/dri/card0 to card9
for (int i = 0; i < 10; i++) {
const std::string path = "/dev/dri/card" + std::to_string(i); // line 35
// ...
int drmFd = open(path.c_str(), O_RDWR | O_CLOEXEC); // line 38
// ...
}
// Set required DRM client capabilities
drmSetClientCap(drmFd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); // line 72
drmSetClientCap(drmFd, DRM_CLIENT_CAP_ATOMIC, 1); // line 78
// Enumerate display connectors via KMS
drmSetMaster(drmFd); // line 84
loadDrmDisplays(); // line 93 (uses member mFd)
DRM_CLIENT_CAP_ATOMIC enables the atomic KMS API — the only mode that supports proper synchronization. This is equivalent to a Wayland compositor calling drmModeAtomicCommit() to page-flip the display.
40.3 Gralloc — Android’s GEM/GBM
Source: hardware/interfaces/graphics/allocator/4.0/IAllocator.hal (lines 21–46)
graph LR
CALL["IAllocator::allocate<br/>(BufferDescriptor, count)"] --> RET["Returns: stride +<br/>BufferHandle[]<br/>(each is native_handle_t with FDs)"]
The returned native_handle_t contains file descriptors that are DMA-BUF fds on Linux-based Android — the same type used by DRI’s GBM and KMS. A native_handle_t allocated by a DRM-based Gralloc driver is literally a DRM GEM buffer exported as a DMA-BUF file descriptor.
Source: frameworks/native/libs/ui/include/ui/Gralloc4.h (lines 34–185)
class Gralloc4Mapper {
status_t lock(buffer_handle_t buffer, uint64_t usage,
const Rect& accessRegion, int acquireFence,
void** outData); // CPU-accessible pointer
status_t getDataspace(buffer_handle_t buffer, Dataspace* outDataspace);
status_t setDataspace(buffer_handle_t buffer, Dataspace dataspace);
};
Source: frameworks/native/libs/ui/GraphicBuffer.cpp (lines 89–95)
// Zero-copy conversion between GraphicBuffer and AHardwareBuffer
AHardwareBuffer* GraphicBuffer::toAHardwareBuffer() {
return reinterpret_cast<AHardwareBuffer*>(this); // same memory layout
}
AHardwareBuffer is a thin wrapper around GraphicBuffer, which itself wraps a Gralloc buffer. The AHardwareBuffer NDK API is Android’s public equivalent of GBM’s gbm_bo.
40.4 HWC2 — Android’s KMS Display Planes
Source: hardware/libhardware/include_all/hardware/hwcomposer2.h (lines 128–176)
typedef enum hwc2_composition {
HWC2_COMPOSITION_CLIENT = 1, // GPU compositing (fallback, like DRM CLIENT plane)
HWC2_COMPOSITION_DEVICE = 2, // Hardware overlay (like DRM primary/overlay plane)
HWC2_COMPOSITION_SOLID_COLOR = 3,
HWC2_COMPOSITION_CURSOR = 4, // DRM cursor plane
HWC2_COMPOSITION_SIDEBAND = 5, // Video decoder direct path
} hwc2_composition_t;
The composition decision mirrors KMS plane assignment:
DEVICE→ hardware overlay plane (DRMDRM_PLANE_TYPE_OVERLAY)CLIENT→ GPU-rendered framebuffer → DRMDRM_PLANE_TYPE_PRIMARYCURSOR→ DRMDRM_PLANE_TYPE_CURSOR
Source: frameworks/native/services/surfaceflinger/DisplayHardware/HWC2.cpp (line 422)
// Present the display — equivalent to drmModeAtomicCommit()
Error Display::present(sp<Fence>* outPresentFence) {
int32_t presentFenceFd = -1;
auto error = mComposer.presentDisplay(mId, &presentFenceFd);
*outPresentFence = sp<Fence>::make(presentFenceFd); // DRM present fence
return error;
}
40.5 Buffer Sharing — DMA-BUF Equivalent
Source: frameworks/native/libs/nativewindow/AHardwareBuffer.cpp
Android uses native_handle_t (containing DMA-BUF fds) for zero-copy buffer sharing:
- Producer allocates buffer via Gralloc → receives
native_handle_twith DMA-BUF fds - Handle is shared cross-process via Binder (fd duplication)
- Consumer imports the handle via Gralloc Mapper → accesses same physical memory
- GPU uses
EGLImageKHRor VulkanVkDeviceMemoryfrom the same DMA-BUF fd
Source: frameworks/native/libs/gui/include/gui/GLConsumer.h (line 107)
// Bind BufferQueue buffer as GL texture — zero-copy via EGLImage
status_t GLConsumer::updateTexImage() {
// Acquire buffer from BufferQueue
// Create EGLImage from native_handle_t (DMA-BUF)
// glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, eglImage)
}
40.6 Sync Fences — Android’s sync_file
Source: frameworks/native/libs/ui/Fence.cpp (lines 27–88)
#include <sync/sync.h> // Linux sync framework
status_t Fence::wait(int timeout) {
return sync_wait(mFenceFd, timeout); // Linux sync_wait() syscall
}
sp<Fence> Fence::merge(const char* name, const sp<Fence>& f1, const sp<Fence>& f2) {
int result = sync_merge(name, f1->mFenceFd, f2->mFenceFd);
return new Fence(result);
}
Android’s Fence class wraps the Linux sync_file framework (the same underlying mechanism as DRI’s explicit sync). Every buffer transfer in Android’s BufferQueue carries acquire and release fences — identical to how DRM atomic commits use IN_FENCE_FD and OUT_FENCE_FD.
40.7 Vulkan Surface — vkCreateAndroidSurfaceKHR
Source: frameworks/native/vulkan/libvulkan/swapchain.cpp (line 610+)
// Android Vulkan swapchain uses ANativeWindow → BufferQueue directly
VkResult vkCreateAndroidSurfaceKHR(VkInstance instance,
const VkAndroidSurfaceCreateInfoKHR* pCreateInfo, ...) {
// Maps ANativeWindow transform → VK_SURFACE_TRANSFORM_*
// Sets gralloc usage flags for Vulkan rendering
// Creates VkSwapchainKHR backed by IGraphicBufferProducer
}
This is structurally identical to Wayland’s VK_KHR_wayland_surface — both create a Vulkan surface from an OS-native window handle backed by a DMA-BUF-capable buffer queue.
Reference files:
device/generic/goldfish/hals/hwc3/DrmClient.cpp— DRM device open and KMS setuphardware/interfaces/graphics/allocator/4.0/IAllocator.hal— Gralloc HAL interfacehardware/libhardware/include_all/hardware/hwcomposer2.h— HWC2 layer typesframeworks/native/services/surfaceflinger/DisplayHardware/HWC2.cpp— present flowframeworks/native/libs/ui/GraphicBuffer.cpp— AHardwareBuffer ↔ GraphicBufferframeworks/native/libs/ui/Fence.cpp— sync_file fence wrapperframeworks/native/vulkan/libvulkan/swapchain.cpp— Vulkan surfaceframeworks/native/libs/gui/include/gui/GLConsumer.h— GL texture consumer (SurfaceTexture)frameworks/native/libs/nativewindow/AHardwareBuffer.cpp— AHardwareBuffer NDK implementationframeworks/native/libs/ui/include/ui/Gralloc4.h— Gralloc4 mapper interface- DRM (Direct Rendering Manager) — Wikipedia
- Android enabling mainline graphics — Collabora
- Android finally using DRM/KMS — Collabora
- BufferQueue and Gralloc — AOSP Docs
41. Hardware Composer (HWC)
The Hardware Composer (HWC) is Android’s display composition HAL. SurfaceFlinger delegates layer composition to HWC, which determines whether each layer can be composited by dedicated display hardware (overlay planes) or must fall back to GPU rendering. This is the single most important optimization for display power efficiency.
41.1 Composition Flow
sequenceDiagram
participant SF as SurfaceFlinger
participant HWC as HWComposer
participant HAL as IComposerClient
participant HW as Display Hardware
SF->>HWC: setClientTarget(gpuBuffer)
SF->>HWC: Set layer properties<br/>(buffer, displayFrame, blendMode, etc.)
SF->>HAL: validateDisplay()
HAL-->>SF: changedCompositionTypes[]
Note over SF: Some layers changed<br/>DEVICE → CLIENT
SF->>SF: GPU-render CLIENT layers<br/>into client target buffer
SF->>HAL: presentDisplay()
HAL->>HW: Atomic KMS commit
HAL-->>SF: presentFence, releaseFences[]
SF->>SF: Distribute release fences<br/>to buffer producers
Composition types (Composition.aidl):
| Type | Description |
|---|---|
CLIENT |
GPU-rendered into client target buffer by SurfaceFlinger |
DEVICE |
Hardware-composited via overlay plane (zero GPU cost) |
CURSOR |
Async cursor plane with setCursorPosition() updates |
SOLID_COLOR |
Single color fill (no buffer needed) |
SIDEBAND |
External content stream (e.g., hardware video decoder) |
DISPLAY_DECORATION |
Anti-aliasing/rounded corner buffers |
REFRESH_RATE_INDICATOR |
Like DEVICE but does not reset HWC activity timers (power-save aware) |
41.2 HWC Version Evolution
timeline
title Hardware Composer Evolution
section Legacy
HWC 1.0 (ICS) : Synchronous prepare/set cycle
section HIDL Era
HWC 2.0 (Nougat) : Async fences, validate/present cycle, per-layer negotiation
HWC 2.1 (Oreo) : Display attributes, power modes
HWC 2.2 (Pie) : Per-frame HDR metadata, RenderIntent
HWC 2.3 (Android 10) : Display identification via EDID, content sampling
HWC 2.4 (Android 11) : VRR constraints, content types, ALLM
section AIDL Era
HWC3 (Android 13+) : AIDL stability, VRR, LUTs, picture profiles, HDCP
Key transitions:
- HWC 1.0 → 2.0: Fundamental redesign. Blocking
prepare()/set()replaced by asyncvalidateDisplay()/presentDisplay()with explicit fence synchronization. Composition type negotiation introduced — HAL can request CLIENT fallback per-layer - HWC 2.x → HWC3: Migration from HIDL to AIDL (
@VintfStability). AddedDisplayConfigurationwith VRR support,DisplayLutsfor color lookup tables, picture profiles for per-layer processing, andstartHdcpNegotiation()for lazy HDCP activation
41.3 Framework Integration
HWComposer (frameworks/native/services/surfaceflinger/DisplayHardware/HWComposer.h) orchestrates the HAL:
getDeviceCompositionChanges()— validate phase, returnsDeviceRequestedChangeswith changed composition typespresentAndGetReleaseFences()— present phase, returns present fence and per-layer release fencessetClientTarget()— submits GPU-rendered client target buffer
HAL abstraction supports both HIDL and AIDL simultaneously:
AidlComposer(AidlComposerHal.h) wraps HWC3 AIDL (IComposerClient)HidlComposer(HidlComposerHal.h) wraps HWC 2.x HIDL- Selection via service name pattern:
"composer@"(HIDL) vs AIDL service discovery
41.4 HWC3 AIDL Interface
IComposer — factory creating IComposerClient instances, reports global Capability set
IComposerClient (~970 lines) — core interface with ~46 methods:
- Display management:
createLayer(),destroyLayer(),createVirtualDisplay() - Configuration:
getActiveConfig(),setActiveConfigWithConstraints() - Composition:
executeCommands(DisplayCommand[])— batched layer/display operations - Color:
setColorMode(),getHdrCapabilities(),getLuts()
IComposerCallback — async notifications:
onHotplug()/onHotplugEvent()— display connect/disconnectonVsync()/onVsyncIdle()— VSYNC timingonRefresh()— display needs recomposition
41.5 Emulator HWC3 (Goldfish)
The emulator (device/generic/goldfish/hals/hwc3/) implements HWC3 with two composition strategies:
GuestFrameComposer — CPU-based composition using libyuv for format conversion. Composes layers into DrmSwapchain buffers, then presents via DRM atomic commits through DrmClient. Used when host GPU acceleration is unavailable.
HostFrameComposer — Offloads composition to the host GPU via gfxstream’s HostConnection. Layers are forwarded to the QEMU host which composites using the real GPU, achieving near-native performance.
Both paths output via DrmClient → DrmDisplay → DRM atomic modesetting (drmModeAtomicCommit()) through the virtio-gpu kernel driver.
graph TB
subgraph "Emulator HWC3"
CC[ComposerClient]
D[Display]
L[Layer]
end
subgraph "Composition Strategy"
GFC[GuestFrameComposer<br/>CPU + libyuv]
HFC[HostFrameComposer<br/>Host GPU via gfxstream]
CFC[ClientFrameComposer<br/>All CLIENT fallback]
end
subgraph "Display Output"
DC[DrmClient]
DD[DrmDisplay]
DS[DrmSwapchain]
KMS["Kernel DRM/KMS<br/>(virtio-gpu)"]
end
CC --> D
D --> L
D --> GFC
D --> HFC
D --> CFC
GFC --> DC
HFC --> DC
DC --> DD
DD --> DS
DS --> KMS
Reference files:
hardware/interfaces/graphics/composer/aidl/android/hardware/graphics/composer3/IComposerClient.aidl— HWC3 AIDL core interfacehardware/interfaces/graphics/composer/aidl/android/hardware/graphics/composer3/Composition.aidl— Composition type enumframeworks/native/services/surfaceflinger/DisplayHardware/HWComposer.h— Framework-side HWC orchestrationframeworks/native/services/surfaceflinger/DisplayHardware/HWC2.h— HWC2 Display/Layer abstractionsframeworks/native/services/surfaceflinger/DisplayHardware/AidlComposerHal.h— AIDL HAL wrapperdevice/generic/goldfish/hals/hwc3/GuestFrameComposer.h— Emulator CPU compositiondevice/generic/goldfish/hals/hwc3/HostFrameComposer.h— Emulator host GPU compositiondevice/generic/goldfish/hals/hwc3/DrmClient.h— Emulator DRM/KMS display output
42. Gralloc — Graphics Memory Allocator
Gralloc (Graphics Allocator) is the HAL responsible for allocating GPU-accessible graphics buffers. It provides a split architecture: an Allocator service for buffer creation, and a Mapper library for buffer import, locking, and metadata access.
42.1 Architecture
graph TB
subgraph "App / Framework"
AHB["AHardwareBuffer<br/>(NDK C API)"]
GB["GraphicBuffer<br/>(native C++)"]
GBA["GraphicBufferAllocator<br/>(singleton)"]
GBM["GraphicBufferMapper<br/>(singleton)"]
end
subgraph "HAL Layer"
IA["IAllocator<br/>(AIDL service)"]
IM["AIMapper<br/>(stable-C library)"]
end
subgraph "Vendor Implementation"
VA["Vendor Allocator<br/>(e.g., ranchu, minigbm)"]
VM["Vendor Mapper<br/>(e.g., mapper.ranchu.so)"]
end
AHB ---|"binary compatible"| GB
GB --> GBA
GB --> GBM
GBA --> IA
GBM --> IM
IA --> VA
IM --> VM
Framework layer:
AHardwareBuffer— NDK C API, binary-compatible wrapper aroundGraphicBufferGraphicBuffer— extendsANativeWindowBuffer, carriesbuffer_handle_t(native_handle with fds)GraphicBufferAllocator— singleton that selects the correct allocator version at runtimeGraphicBufferMapper— singleton providinglock()/unlock(), metadata queries, buffer import/free
Version selection (GraphicBufferAllocator.cpp): tries Gralloc5 → Gralloc4 → Gralloc3 → Gralloc2, using the first available mapper version.
42.2 Buffer Usage Flags
BufferUsage (BufferUsage.aidl) is a 64-bit bitmask controlling buffer allocation:
| Category | Flags | Purpose |
|---|---|---|
| CPU access | CPU_READ_OFTEN, CPU_WRITE_OFTEN |
Software rendering, readback |
| GPU | GPU_TEXTURE, GPU_RENDER_TARGET |
Sampling, rendering |
| Composer | COMPOSER_OVERLAY, COMPOSER_CLIENT_TARGET, COMPOSER_CURSOR |
HWC composition |
| Media | VIDEO_ENCODER, VIDEO_DECODER, CAMERA_OUTPUT |
Hardware codec, camera |
| Security | PROTECTED |
DRM-protected content |
| Display | FRONT_BUFFER |
Front-buffer rendering (gaming) |
Usage flags determine memory placement (system RAM, GPU VRAM, secure memory), alignment, and cacheability.
42.3 Gralloc Version Evolution
| Version | Android | Interface | Key Changes |
|---|---|---|---|
| Gralloc 0.x | Pre-Nougat | Legacy HAL module | Basic alloc/free, no versioning |
| Mapper 2.0 / Allocator 2.0 | Oreo | HIDL | Split allocator/mapper, BufferDescriptor, import/free/lock |
| Mapper 3.0 / Allocator 3.0 | Android 10 | HIDL | isSupported() for capability queries |
| Mapper 4.0 / Allocator 4.0 | Android 11 | HIDL | Metadata support (StandardMetadataType), PlaneLayout, buffer ID |
| Mapper 5.0 / Allocator V2 | Android 14+ | Stable-C / AIDL | AIMapper C API replaces HIDL, allocate2(BufferDescriptorInfo), mandatory metadata |
Current standard: IAllocator AIDL V2 + AIMapper stable-C V5. The mapper moved to a stable C API (/vendor/lib64/hw/mapper.<suffix>.so) for reduced overhead — loaded as a shared library instead of a binder service.
Mandatory metadata for V5: STRIDE, DATASPACE, SMPTE2086, CTA861_3, BLEND_MODE.
42.4 Buffer Metadata
StandardMetadataType defines queryable buffer properties:
| ID | Type | Description |
|---|---|---|
| 1 | BUFFER_ID |
Unique 64-bit identifier |
| 3-4 | WIDTH, HEIGHT |
Buffer dimensions |
| 6-8 | PIXEL_FORMAT_* |
Requested format, FourCC, modifier |
| 9 | USAGE |
Allocation usage flags |
| 10 | ALLOCATION_SIZE |
Total bytes allocated |
| 15 | PLANE_LAYOUTS |
Per-plane offset, stride, component layout |
| 17 | DATASPACE |
Color space, transfer, range |
| 19-20 | SMPTE2086, CTA861_3 |
HDR metadata |
| 23 | STRIDE |
Row stride in pixels |
PlaneLayout describes each buffer plane (critical for multi-planar YUV):
offsetInBytes,strideInBytes,totalSizeInBytessampleIncrementInBits— bits per pixel samplehorizontalSubsampling/verticalSubsampling— for chroma planes (e.g., 2 for 4:2:0 UV)PlaneLayoutComponent[]— per-component type (R/G/B/Y/Cb/Cr), bit offset, bit width
42.5 Emulator Gralloc (Goldfish/Ranchu)
The emulator’s Gralloc (device/generic/goldfish/hals/gralloc/) bridges guest buffer allocation with the host GPU:
Allocator (allocator.cpp): Implements IAllocator AIDL. Checks needGpuBuffer() for GPU_TEXTURE/GPU_RENDER_TARGET usage. Allocates buffers via goldfish_address_space (shared memory with host) and registers with the host’s ColorBuffer service through gralloc_cb callbacks. Enforces resource limits.
Mapper (mapper.cpp): Implements AIMapper stable-C. Provides lock/unlock with sync fence integration, metadata queries (StandardMetadataType), and buffer import via goldfish_address_space.
Host integration: GPU buffers are backed by host-side ColorBuffer objects (GL textures or Vulkan images) in the gfxstream backend. The HostConnectionSession manages exclusive access to the host communication channel. Buffer data flows through shared memory, avoiding copies for GPU-accelerated rendering.
graph LR
subgraph "Guest VM"
APP[App / SurfaceFlinger]
ALLOC["Allocator Service<br/>(allocator.cpp)"]
MAP["Mapper Library<br/>(mapper.cpp)"]
GAS[goldfish_address_space<br/>Shared Memory]
end
subgraph "Host (QEMU)"
CB["ColorBuffer Pool<br/>(GL Textures / VkImages)"]
FB[FrameBuffer<br/>Manager]
GPU[Host GPU]
end
APP --> ALLOC
APP --> MAP
ALLOC --> GAS
MAP --> GAS
GAS -.->|"virtio-gpu / pipe"| CB
CB --> FB
FB --> GPU
42.6 Case Study: MaruOS mflinger — Cross-OS Buffer Sharing via Gralloc
MaruOS demonstrates a real-world use of gralloc’s buffer sharing capabilities to bridge Android and a full Linux desktop environment. mflinger is a lightweight display server that exposes Android’s gralloc-backed surfaces to a Linux container (LXC/chroot), enabling zero-copy rendering from a Linux desktop directly into Android’s compositor pipeline.
Components:
- mflinger — Android-side daemon that creates
Surfaceobjects viaSurfaceComposerClientand shares their underlying gralloc buffer fds with Linux clients - mclient — Linux-side client that captures the X11 desktop (via XShm), maps the gralloc buffer, and writes pixels directly into it
- mlib — Shared IPC library implementing the wire protocol over Unix domain sockets
The buffer sharing mechanism:
sequenceDiagram
participant MC as mclient (Linux Container)
participant MF as mflinger (Android Daemon)
participant SF as SurfaceFlinger
participant HW as Display / HWC
MC->>MF: M_CREATE_BUFFER (width, height)
MF->>SF: SurfaceComposerClient::createSurface()<br/>PIXEL_FORMAT_BGRA_8888
SF-->>MF: SurfaceControl (gralloc-backed)
loop Per Frame
MC->>MF: M_LOCK_BUFFER
MF->>SF: Surface::lockWithHandle()
Note over MF: Dequeues buffer from BufferQueue,<br/>obtains buffer_handle_t
MF-->>MC: gralloc fd via SCM_RIGHTS
Note over MC: mmap(fd) → direct write access<br/>to gralloc buffer memory
MC->>MC: XShmGetImage() → copy pixels<br/>into mmap'd gralloc buffer
MC->>MF: M_UNLOCK_AND_POST (no response)
MF->>SF: Surface::unlockAndPost()
SF->>HW: Composite & display
end
How gralloc enables this:
-
Buffer allocation: mflinger creates a
SurfaceviaSurfaceComposerClient. Android’sBufferQueueallocates the backing buffer through gralloc (GraphicBufferAllocator), producing abuffer_handle_tcontaining a DMA-BUF or ION file descriptor. -
Cross-process fd passing: When mclient requests a lock, mflinger calls a custom
Surface::lockWithHandle()(a MaruOS AOSP patch) to obtain thebuffer_handle_t. The gralloc buffer’s fd (handle->data[0]) is sent to mclient over a Unix domain socket usingSCM_RIGHTSancillary data. -
Zero-copy shared memory: mclient
mmap()s the received fd withPROT_READ | PROT_WRITE | MAP_SHARED. This maps the same physical memory (DMA-BUF) that SurfaceFlinger will read from — no pixel copies between processes. -
Lock/unlock discipline: The
lockWithHandle()/unlockAndPost()cycle dequeues and requeues buffers in theBufferQueue, ensuring mutual exclusion between the Linux writer and Android’s compositor.
IPC protocol (Unix abstract namespace socket "maru-bridge"):
| Opcode | Purpose | Has Response? |
|---|---|---|
M_GET_DISPLAY_INFO |
Query HDMI display dimensions | Yes |
M_CREATE_BUFFER |
Allocate Surface via SurfaceFlinger | Yes |
M_LOCK_BUFFER |
Lock buffer, receive gralloc fd via SCM_RIGHTS | Yes + fd |
M_UNLOCK_AND_POST_BUFFER |
Unlock and post to SurfaceFlinger | No (streaming) |
M_UPDATE_BUFFER |
Update buffer position (x, y) | Yes |
M_RESIZE_BUFFER |
Resize buffer dimensions | Yes |
Key design decisions:
- Abstract namespace sockets (
sun_path[0] = '\0') work across the container/host boundary without filesystem path issues, since they are kernel-level and shared when the network namespace is shared - Streaming optimization:
M_UNLOCK_AND_POST_BUFFERdeliberately omits the response, allowing the client to immediately begin capturing the next frame - Custom AOSP patch: Standard
ANativeWindow_lock()/Surface::lock()returns a CPU-mapped pointer but does not expose thebuffer_handle_t. MaruOS patchesSurfaceto addlockWithHandle()to extract the fd for cross-process sharing - Vendor gralloc limitation: Some vendors (e.g., Rockchip) set the gralloc buffer fd to read-only when transferring to userspace, preventing the container from writing — a real-world example of how gralloc vendor implementations can restrict buffer sharing
This case study illustrates gralloc’s role beyond simple allocation: its
buffer_handle_t(containing DMA-BUF fds) is the fundamental mechanism enabling zero-copy buffer sharing across process and OS boundaries.
Reference files:
hardware/interfaces/graphics/allocator/aidl/android/hardware/graphics/allocator/IAllocator.aidl— Allocator AIDL interfacehardware/interfaces/graphics/mapper/stable-c/include/android/hardware/graphics/mapper/IMapper.h— Mapper stable-C APIhardware/interfaces/graphics/common/aidl/android/hardware/graphics/common/BufferUsage.aidl— Buffer usage flagshardware/interfaces/graphics/common/aidl/android/hardware/graphics/common/PixelFormat.aidl— Pixel format definitionshardware/interfaces/graphics/common/aidl/android/hardware/graphics/common/StandardMetadataType.aidl— Buffer metadata typesframeworks/native/libs/ui/include/ui/GraphicBuffer.h— Framework GraphicBuffer classframeworks/native/libs/ui/GraphicBufferAllocator.cpp— Allocator version selection logicframeworks/native/libs/ui/include/ui/GraphicBufferMapper.h— Mapper with version enum (GRALLOC_2-5)frameworks/native/libs/nativewindow/include/android/hardware_buffer.h— AHardwareBuffer NDK APIdevice/generic/goldfish/hals/gralloc/allocator.cpp— Emulator allocator (goldfish_address_space + host ColorBuffer)device/generic/goldfish/hals/gralloc/mapper.cpp— Emulator mapper (stable-C AIMapper)- MaruOS mflinger — mflinger/mclient source (Apache 2.0)
43. RenderEngine — SurfaceFlinger’s GPU Compositor
RenderEngine is SurfaceFlinger’s GPU composition backend, responsible for rendering layers when Hardware Composer (HWC) cannot handle them (CLIENT composition). It uses Skia over OpenGL ES or Vulkan to composite layers with effects like background blur, rounded corners, shadows, color matrices, and HDR tone mapping.
43.1 Architecture and Class Hierarchy
classDiagram
class RenderEngine {
<<abstract>>
+create(RenderEngineCreationArgs): unique_ptr
+drawLayers(DisplaySettings, layers, buffer, fence): Future~FenceResult~
#drawLayersInternal()*
#updateProtectedContext()
}
class SkiaRenderEngine {
<<abstract>>
#mContext: SkiaGpuContext
#mProtectedContext: SkiaGpuContext
+drawLayersInternal()
#createContexts()*
}
class SkiaGLRenderEngine {
-mEGLDisplay: EGLDisplay
-mEGLContext: EGLContext
-mProtectedEGLContext: EGLContext
+create(args): unique_ptr
#createContexts()
}
class SkiaVkRenderEngine {
<<abstract>>
#createContexts()
}
class GaneshVkRenderEngine {
+create(args): unique_ptr
}
class GraphiteVkRenderEngine {
+create(args): unique_ptr
+primeCache(config): future~void~
}
class RenderEngineThreaded {
-mThread: thread
-mFunctionCalls: queue~Work~
-mRenderEngine: unique_ptr~RenderEngine~
+getRenderEngineTid(): pid_t
}
RenderEngine <|-- SkiaRenderEngine
RenderEngine <|-- RenderEngineThreaded
SkiaRenderEngine <|-- SkiaGLRenderEngine
SkiaRenderEngine <|-- SkiaVkRenderEngine
SkiaVkRenderEngine <|-- GaneshVkRenderEngine
SkiaVkRenderEngine <|-- GraphiteVkRenderEngine
RenderEngineThreaded *-- RenderEngine : wraps
43.2 Backend Selection
SurfaceFlinger selects the RenderEngine backend at startup via chooseRenderEngineType():
flowchart TD
START["SurfaceFlinger::init()"]
DBG{"debug.renderengine.backend<br/>property set?"}
VK{"Vulkan supported AND<br/>vulkan_renderengine flag?"}
GR{"shouldUseGraphiteIfSupported()?"}
GRAPHITE["GraphiteVkRenderEngine<br/>(Skia Graphite + Vulkan)"]
GANESH_VK["GaneshVkRenderEngine<br/>(Skia Ganesh + Vulkan)"]
GANESH_GL["SkiaGLRenderEngine<br/>(Skia Ganesh + GL ES)"]
THREADED["RenderEngineThreaded<br/>(always wraps)"]
START --> DBG
DBG -->|"Yes"| THREADED
DBG -->|"No"| VK
VK -->|"Yes"| GR
VK -->|"No"| GANESH_GL
GR -->|"Yes"| GRAPHITE
GR -->|"No"| GANESH_VK
GRAPHITE --> THREADED
GANESH_VK --> THREADED
GANESH_GL --> THREADED
RenderEngineCreationArgs configure the engine:
| Parameter | Values | Purpose |
|---|---|---|
graphicsApi |
GL, Vk | Graphics API selection |
skiaBackend |
Ganesh, Graphite | Skia backend selection |
blurAlgorithm |
None, Gaussian, Kawase, KawaseDualFilter, KawaseDualFilterV2 | Background blur strategy |
contextPriority |
Low, Medium, High, Realtime | GPU context priority |
threaded |
Yes (always) | Async composition on dedicated thread |
enableProtectedContext |
bool | DRM protected content support |
imageCacheSize |
count | Max cached GPU textures |
43.3 Composition Flow: HWC CLIENT Layers
When HWC’s validate phase marks layers as CLIENT composition, RenderEngine renders them:
sequenceDiagram
participant SF as SurfaceFlinger
participant CE as CompositionEngine (Output)
participant RE as RenderEngine (Threaded)
participant SKIA as SkiaRenderEngine
participant GPU as GPU
SF->>CE: present()
CE->>CE: Collect CLIENT layers<br/>Generate LayerSettings[]
CE->>CE: generateClientCompositionDisplaySettings()
CE->>RE: drawLayers(displaySettings, layers, outputBuffer, fence)
RE->>RE: Queue work on render thread
RE->>SKIA: drawLayersInternal()
SKIA->>SKIA: updateProtectedContext()<br/>(switch if needed)
SKIA->>GPU: Create SkSurface from output buffer
SKIA->>GPU: Wait on input fence
loop For each layer in Z-order
SKIA->>GPU: Apply blur filter (if needed)
SKIA->>GPU: Apply transforms, clip, corner radius
SKIA->>GPU: Draw layer content (buffer or solid color)
SKIA->>GPU: Apply alpha, color matrix, effects
end
SKIA->>GPU: flushAndSubmit()
GPU-->>RE: Completion fence
RE-->>CE: FenceResult
CE->>SF: CLIENT framebuffer ready for HWC
Key parameters per layer (LayerSettings):
- Geometry (source crop, destination rect, transforms)
- Buffer (AHardwareBuffer) or solid color
- Blend mode, alpha, color matrix
- Background blur radius
- Corner radius, shadow settings
- Edge extension flags
43.4 GPU Context Management
SkiaGpuContext abstracts the GPU backend:
| Factory Method | Backend | GPU API |
|---|---|---|
MakeGL_Ganesh() |
Ganesh | OpenGL ES (EGL) |
MakeVulkan_Ganesh() |
Ganesh | Vulkan |
MakeVulkan_Graphite() |
Graphite | Vulkan |
Each SkiaRenderEngine maintains two GPU contexts:
- mContext — standard rendering context for normal layers
- mProtectedContext — protected rendering context for DRM content (
GRALLOC_USAGE_PROTECTEDbuffers)
Context switching happens in updateProtectedContext() before each drawLayersInternal() call.
VulkanInterface (singleton): Manages VkInstance, VkPhysicalDevice, VkDevice, VkQueue. Provides semaphore creation and sync fd import/export for fence interop with Android’s sync framework.
43.5 RenderEngine Version Evolution
| Era | Backend | GPU API | Notes |
|---|---|---|---|
| Legacy (pre-2020) | Direct GLES calls | OpenGL ES 2.0 | Removed; no Skia abstraction |
| Skia GL / Ganesh (2020+) | Skia Ganesh | OpenGL ES | Default fallback for non-Vulkan devices |
| Skia Vulkan / Ganesh (2022+) | Skia Ganesh | Vulkan | Better sync, explicit GPU control |
| Skia Vulkan / Graphite (2024+) | Skia Graphite | Vulkan | Current default; next-gen Skia backend, pipeline precompilation |
Graphite advantages over Ganesh: Designed for modern GPU APIs, better command batching, pipeline precompilation (via primeCache() which spawns GraphitePipelineManager::PrecompilePipelines() on a background thread), and improved multi-threaded recording.
43.6 ThreadedRenderEngine
RenderEngineThreaded wraps any backend to run GPU composition on a dedicated thread:
- Spawns a rendering thread at construction
- Queues work as
std::function<void(RenderEngine&)>lambdas to astd::queue - Thread wakes via
std::condition_variable - Exposes
getRenderEngineTid()for ADPF (Android Dynamic Performance Framework) power hints - All RenderEngine instances are now always threaded (
Threaded::Yesenforced)
Reference files:
frameworks/native/libs/renderengine/include/renderengine/RenderEngine.h— Base abstraction (RenderEngineCreationArgs, enums)frameworks/native/libs/renderengine/RenderEngine.cpp— Factory method, backend selectionframeworks/native/libs/renderengine/skia/SkiaRenderEngine.h— Skia base (drawLayersInternal, context management)frameworks/native/libs/renderengine/skia/SkiaRenderEngine.cpp— Core drawing logic (layer iteration, effects)frameworks/native/libs/renderengine/skia/SkiaGLRenderEngine.h— OpenGL ES variant (EGL context)frameworks/native/libs/renderengine/skia/SkiaVkRenderEngine.h— Vulkan base (VulkanInterface)frameworks/native/libs/renderengine/skia/GaneshVkRenderEngine.h— Ganesh + Vulkanframeworks/native/libs/renderengine/skia/GraphiteVkRenderEngine.h— Graphite + Vulkan (current default)frameworks/native/libs/renderengine/threaded/RenderEngineThreaded.h— Async composition wrapperframeworks/native/libs/renderengine/skia/compat/SkiaGpuContext.h— GPU context abstractionframeworks/native/libs/renderengine/skia/VulkanInterface.h— Vulkan instance/device singletonframeworks/native/libs/renderengine/include/renderengine/LayerSettings.h— Per-layer composition parametersframeworks/native/libs/renderengine/include/renderengine/DisplaySettings.h— Display composition parametersframeworks/native/services/surfaceflinger/SurfaceFlinger.cpp— RenderEngine initialization (chooseRenderEngineType)frameworks/native/services/surfaceflinger/CompositionEngine/src/Output.cpp— CLIENT composition trigger
Part IX: Cross-Cutting Topics
44. Input System and Focus Management
44.1 Input Subsystem Architecture
The Android input system delivers hardware events (touch, key, stylus, mouse, gamepad) from kernel event devices through a multi-stage native pipeline to the correct window. The system is tightly coupled with both the window manager (which defines window hierarchy and visibility) and SurfaceFlinger (which provides per-frame window geometry and coordinate transforms). For drag-and-drop input routing via DragInputEventReceiver and InputInterceptor, see §50.4.
graph TB
subgraph "Kernel"
EVD["/dev/input/eventN<br/>evdev devices"]
end
subgraph "InputFlinger Native Pipeline"
EH[EventHub<br/>inotify + epoll on /dev/input]
IR[InputReader<br/>InputDevice + InputMapper]
UIB[UnwantedInteractionBlocker<br/>palm rejection heuristics]
IP[InputProcessor<br/>MotionClassifier ML model]
PC[PointerChoreographer<br/>pointer icon management]
IF[InputFilter<br/>accessibility: bounce/slow/sticky keys]
ID[InputDispatcher<br/>target resolution + ANR tracking]
end
subgraph "SurfaceFlinger"
SF_WI[WindowInfosListenerInvoker<br/>per-commit window geometry]
SF_BW[buildWindowInfos<br/>layer snapshot → WindowInfo]
end
subgraph "System Server"
IMS[InputManagerService<br/>Java wrapper + policy]
PWM[PhoneWindowManager<br/>key interception + shortcuts]
KGC[KeyGestureController<br/>gesture detection]
IM[InputMonitor<br/>per DisplayContent]
WMS_F[WMS Focus<br/>updateFocusedWindowLocked]
end
subgraph "App Process"
IC[InputChannel<br/>socketpair per window]
IER[InputEventReceiver<br/>Looper fd watch]
VRI[ViewRootImpl<br/>InputStage chain]
DV[DecorView<br/>dispatchKeyEvent]
VH[View Hierarchy<br/>dispatchTouchEvent / onKeyDown]
end
EVD -->|"raw events"| EH
EH -->|"RawEvent"| IR
IR -->|"NotifyMotionArgs"| UIB
UIB --> IP
IP -->|"classified events"| PC
PC --> IF
IF --> ID
SF_BW --> SF_WI
SF_WI -->|"WindowInfosUpdate<br/>(windowInfos + displayInfos + vsyncId)"| ID
IMS -->|"policy callbacks"| ID
PWM -->|"interceptKeyBeforeQueueing<br/>interceptKeyBeforeDispatching"| IMS
KGC --> PWM
IM -->|"InputConsumer registration"| ID
WMS_F -->|"setFocusedWindow"| ID
ID -->|"InputMessage via socket"| IC
IC --> IER
IER --> VRI
VRI --> DV
DV --> VH
44.2 InputFlinger Pipeline — Native Architecture
InputManager (frameworks/native/services/inputflinger/InputManager.h) orchestrates the full native input pipeline. The pipeline stages are connected via InputListenerInterface, a chain-of-responsibility pattern where each stage processes and forwards events.
Pipeline stages in order:
graph LR
IR[InputReader] -->|NotifyArgs| UIB[UnwantedInteraction<br/>Blocker]
UIB -->|NotifyArgs| IP[InputProcessor]
IP -->|NotifyArgs| PC[PointerChoreographer]
PC -->|NotifyArgs| IF[InputFilter]
IF -->|NotifyArgs| ID[InputDispatcher]
style IR fill:#e1f5fe
style ID fill:#fff3e0
44.2.1 EventHub and InputReader
EventHub (frameworks/native/services/inputflinger/reader/include/EventHub.h) monitors /dev/input/ using inotify for device hotplug and epoll for event delivery. It reads raw input_event structs from the kernel’s evdev interface.
InputReader (frameworks/native/services/inputflinger/reader/include/InputReader.h) runs on a dedicated thread (InputReaderThread). For each input device, it maintains an InputDevice object containing one or more InputMapper instances:
classDiagram
class InputReader {
-mDevices: Map~int32_t, InputDevice~
+loopOnce()
+getInputDevices()
}
class InputDevice {
-mId: int32_t
-mMappers: vector~InputMapper~
+process(RawEvent)
+configure()
}
class InputMapper {
<<abstract>>
+process(RawEvent)*
+getSources()*
+configure()*
}
class KeyboardInputMapper {
-mKeyDowns: vector~KeyDown~
+processKey(RawEvent)
}
class TouchInputMapper {
-mRawPointerAxes: RawPointerAxes
-mCalibration: Calibration
+processRawTouches()
+cookAndDispatch()
}
class CursorInputMapper {
-mCursorButtonAccumulator: CursorButtonAccumulator
+processMovement()
}
class StylusInputMapper {
+processPressure()
+processTilt()
}
class SensorInputMapper {
+processAccelerometer()
}
class RotaryEncoderInputMapper {
+processScroll()
}
InputReader "1" *-- "*" InputDevice
InputDevice "1" *-- "*" InputMapper
InputMapper <|-- KeyboardInputMapper
InputMapper <|-- TouchInputMapper
InputMapper <|-- CursorInputMapper
InputMapper <|-- StylusInputMapper
InputMapper <|-- SensorInputMapper
InputMapper <|-- RotaryEncoderInputMapper
InputReader.loopOnce() calls EventHub.getEvents() (with epoll timeout), then dispatches raw events to the appropriate InputDevice.process(). Each mapper converts raw events into typed NotifyArgs:
KeyboardInputMapper→NotifyKeyArgs(scancode → keycode via key layout files)TouchInputMapper→NotifyMotionArgs(multi-touch slot protocol → pointer coordinates with pressure, size, orientation)CursorInputMapper→NotifyMotionArgs(relative movement → absolute cursor position)
TouchInputMapper (frameworks/native/services/inputflinger/reader/mapper/TouchInputMapper.cpp) is the most complex mapper (~2600 lines), handling multi-touch calibration, palm detection, gesture recognition, and coordinate transformation from raw touch panel coordinates to display coordinates.
44.2.2 UnwantedInteractionBlocker and InputProcessor
UnwantedInteractionBlocker (frameworks/native/services/inputflinger/UnwantedInteractionBlocker.h) applies heuristic-based palm rejection. It analyzes touch size, pressure, and position patterns to filter out accidental palm touches before they enter the dispatch pipeline.
InputProcessor (frameworks/native/services/inputflinger/InputProcessor.h) provides ML-based motion classification via MotionClassifier. It runs a TFLite model to classify touch events (e.g., palm vs. finger), adding a MotionClassification field (NONE, AMBIGUOUS_GESTURE, DEEP_PRESS) to motion events. The classifier runs asynchronously on a HAL thread.
44.2.3 PointerChoreographer and InputFilter
PointerChoreographer (frameworks/native/services/inputflinger/PointerChoreographer.h) manages pointer icon display. It tracks cursor position per display, handles mouse pointer visibility, and coordinates with SurfaceFlinger to render pointer sprites as dedicated layers. It also manages TouchSpotController for showing multi-touch contact points.
InputFilter (frameworks/native/services/inputflinger/InputFilter.h) implements accessibility input processing:
- Bounce keys: Ignores rapid repeated key presses (debouncing)
- Slow keys: Requires keys to be held for a minimum duration before acceptance
- Sticky keys: Allows modifier keys (Shift, Ctrl, Alt) to be pressed and released before the modified key
44.3 InputDispatcher — Core Event Router
InputDispatcher (frameworks/native/services/inputflinger/dispatcher/InputDispatcher.h) is the central routing engine that determines which window receives each input event. It runs on its own thread (InputDispatcherThread) and maintains the complete set of window information received from SurfaceFlinger.
44.3.1 Window Target Resolution
flowchart TD
EVENT[Input Event Arrives] --> TYPE{Event Type?}
TYPE -->|Motion/Touch| FTW[findTouchedWindowTargets]
TYPE -->|Key| FKW[findFocusedWindowTargetLocked]
FTW --> ITER[Iterate windows in z-order<br/>top to bottom]
ITER --> HIT{Point in window<br/>touchableRegion?}
HIT -->|No| NEXT[Next window]
NEXT --> ITER
HIT -->|Yes| FLAGS{Check inputConfig flags}
FLAGS -->|NOT_TOUCHABLE| NEXT
FLAGS -->|DROP_INPUT| DROP[Drop event]
FLAGS -->|WATCH_OUTSIDE_TOUCH| OUTSIDE[Add as OUTSIDE target]
OUTSIDE --> NEXT
FLAGS -->|Normal| TOUCH_TARGET[Add as touch target]
TOUCH_TARGET --> OCCLUDE{Check<br/>touchOcclusionMode}
OCCLUDE -->|BLOCK_UNTRUSTED| BLOCK[Block if untrusted overlay]
OCCLUDE -->|USE_OPACITY| OPACITY[Apply opacity threshold<br/>alpha check]
OCCLUDE -->|ALLOW| DISPATCH
FKW --> FR[FocusResolver<br/>per-display focus]
FR --> FOCUSED{Focused window<br/>exists?}
FOCUSED -->|Yes| DISPATCH[Dispatch to target]
FOCUSED -->|No| ANR_CHECK{Focused app<br/>set?}
ANR_CHECK -->|Yes| WAIT[Wait for window<br/>start ANR timer]
ANR_CHECK -->|No| DROP_KEY[Drop key event]
DISPATCH --> CONN[Connection<br/>outboundQueue → waitQueue]
CONN --> SOCKET[Write InputMessage<br/>to socketpair]
Touch target finding — findTouchedWindowTargets() (InputDispatcher.h:408-412):
- Iterates through windows in z-order (maintained from SurfaceFlinger’s per-commit updates)
- Tests touch coordinates against each window’s
touchableRegion(which accounts for frame, transform, and crop) - Respects
inputConfigflags:NOT_TOUCHABLE,NOT_FOCUSABLE,DROP_INPUT,DROP_INPUT_IF_OBSCURED - SPY windows (
IS_SPYflag) receive a copy of touch events without consuming them WATCH_OUTSIDE_TOUCHwindows receiveACTION_OUTSIDEfor touches outside their bounds- Touch occlusion security: untrusted overlays (non-system UIDs) are blocked from obscuring touches based on
touchOcclusionModeand cumulative opacity
Key target finding — findFocusedWindowTargetLocked() (InputDispatcher.h:817-819):
- Uses
FocusResolver(frameworks/native/services/inputflinger/dispatcher/FocusResolver.h) which maintains per-display focus state - Each display independently tracks its focused window via
setFocusedWindow()calls from WMS - If no focused window exists but a focused application is set, the dispatcher waits (with ANR timeout) for the window to appear
44.3.2 Dispatch State Tracking
Each window connection is managed by a Connection object (frameworks/native/services/inputflinger/dispatcher/Connection.h):
outboundQueue: Events waiting to be sent to the windowwaitQueue: Events sent but not yet acknowledged (finished) by the windowresponsiveflag: Set to false when the connection stops acknowledging events (ANR condition)
TouchedWindow (frameworks/native/services/inputflinger/dispatcher/TouchedWindow.h) tracks per-device touch state including active pointers, dispatch modes (DispatchMode::AS_IS, OUTSIDE, SLIPPERY_EXIT, SLIPPERY_ENTER, HOVER_ENTER, HOVER_EXIT), and the window’s hover state.
44.3.3 ANR (Application Not Responding) Detection
AnrTracker (frameworks/native/services/inputflinger/dispatcher/AnrTracker.h) uses a std::multiset of (timeoutTime, connectionToken) pairs to efficiently track the next ANR deadline. When the dispatcher sends an event:
- A timeout entry is added to
AnrTrackerbased on the window’s ANR timeout (typically 5 seconds) - When the window finishes (acknowledges) the event, the timeout entry is removed
- If the timeout fires before acknowledgment,
InputDispatchercallsonAnrLocked()which notifiesInputManagerService→ActivityManagerServiceto show the ANR dialog
For key events specifically, the dispatcher computes a “no focused window” ANR if a focused application exists but no window has appeared within the timeout.
44.4 SurfaceFlinger ↔ InputFlinger Relationship
SurfaceFlinger provides InputDispatcher with per-frame window geometry, making it the authoritative source for window positions, transforms, and visibility during input event routing.
sequenceDiagram
participant SF as SurfaceFlinger
participant LSB as LayerSnapshotBuilder
participant WILI as WindowInfosListenerInvoker
participant ID as InputDispatcher
participant WMS as WindowManagerService
Note over SF: Per-frame commit cycle
SF->>LSB: updateSnapshots()
LSB->>LSB: updateInput(snapshot)<br/>fillInputFrameInfo()<br/>getInputTransform()
SF->>SF: updateInputFlinger()
SF->>SF: buildWindowInfos()<br/>iterate layer snapshots
SF->>WILI: windowInfosChanged(update)
Note over WILI: WindowInfosUpdate contains:<br/>vector of WindowInfo<br/>vector of DisplayInfo<br/>vsyncId
WILI->>ID: onWindowInfosChanged(update)
ID->>ID: updateWindows()<br/>rebuild window list for targeting
Note over WMS: Focus is set separately
WMS->>ID: setFocusedWindow(token, displayId)
44.4.1 WindowInfo — The Bridge Data Structure
WindowInfo (frameworks/native/libs/gui/include/gui/WindowInfo.h) is the primary data structure that SurfaceFlinger sends to InputDispatcher. It contains all information needed for input routing:
| Field | Purpose |
|---|---|
token (IBinder) |
Unique window identity, links to WMS WindowState |
id (int32_t) |
Window ID for debugging |
name (string) |
Window name for debugging |
frame (Rect) |
Window bounds in display coordinates |
transform (ui::Transform) |
Coordinate transform from display to window space |
touchableRegion (Region) |
Precise touchable area (may differ from frame) |
inputConfig (Flags) |
Bit flags: NOT_TOUCHABLE, NOT_FOCUSABLE, IS_SPY, DROP_INPUT, etc. |
alpha (float) |
Window opacity (used for touch occlusion calculations) |
displayId (ui::LogicalDisplayId) |
Which display this window belongs to |
ownerPid / ownerUid |
Process identity (used for security checks) |
touchOcclusionMode |
BLOCK_UNTRUSTED, USE_OPACITY, or ALLOW |
inputTransferToken |
Enables cross-process input transfer (e.g., embedded windows) |
canOccludePresentation |
Whether this window can occlude sensitive content |
44.4.2 Per-Frame Window Geometry Updates
SurfaceFlinger updates InputDispatcher’s window list on every commit cycle via WindowInfosListenerInvoker:
LayerSnapshotBuilder.updateInput()(frameworks/native/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp): For each layer snapshot, computes input-relevant geometry:fillInputFrameInfo(): Computes the window frame by applying layer bounds, crop, and parent transformsgetInputTransform(): Computes the coordinate transform from display space to the window’s buffer space, accounting for buffer transforms, layer transforms, and parent chain transforms
-
SurfaceFlinger.updateInputFlinger()(frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp:4620): Called during the commit phase, triggersbuildWindowInfos()which iterates all layer snapshots and constructs aWindowInfosUpdate WindowInfosListenerInvoker.windowInfosChanged()(frameworks/native/services/surfaceflinger/WindowInfosListenerInvoker.h): Dispatches the update to all registered listeners, primarily InputDispatcher. The update includes:windowInfos: Vector of all window geometrydisplayInfos: Vector of display transforms and dimensionsvsyncId: Associates the window state with a specific frame
This per-frame update mechanism ensures InputDispatcher always has window positions that are synchronized with what is visually on screen, preventing touches from being routed to stale window positions.
44.4.3 Coordinate Transform Pipeline
When a touch event arrives, InputDispatcher must transform coordinates from the raw display space to the target window’s coordinate space. The transform chain is:
graph LR
RAW[Raw display<br/>coordinates] -->|DisplayInfo.transform| LOGICAL[Logical display<br/>coordinates]
LOGICAL -->|"WindowInfo.transform<br/>(inverse)"| WINDOW[Window-local<br/>coordinates]
WINDOW -->|"buffer transform"| BUFFER[Buffer<br/>coordinates]
SurfaceComposerClient (frameworks/native/libs/gui/include/gui/SurfaceComposerClient.h:701-703) provides the Java-to-native bridge:
setInputWindowInfo(): Associates aWindowInfowith aSurfaceControlsetFocusedWindow(): Requests focus change for a specific window
44.5 InputMonitor — WMS-to-InputDispatcher Bridge
InputMonitor (frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java:78) bridges the window manager and input system at the Java level. Each DisplayContent owns one InputMonitor instance.
44.5.1 Window List Generation
populateInputWindowHandle() (line 251-325) converts each WindowState into native input window properties:
sequenceDiagram
participant DC as DisplayContent
participant IM as InputMonitor
participant UIC as UpdateInputForAllWindowsConsumer
participant IWH as InputWindowHandleWrapper
participant SF as SurfaceFlinger
DC->>IM: updateInputWindowsLw()
IM->>UIC: accept(windowState)<br/>for each window in z-order
UIC->>IM: populateInputWindowHandle(ws)
Note over IM: Sets: frame, touchableRegion,<br/>inputConfig flags, ownerPid/Uid,<br/>displayId, alpha, transform
IM->>IWH: setToken(windowState.mInputChannelToken)
IM->>IWH: setTouchableRegion(region)
IM->>IWH: setInputConfigFlags(flags)
Note over IM: Window properties flow to<br/>InputDispatcher via SurfaceFlinger
IM->>SF: SurfaceControl.setInputWindowInfo()
Key properties set by populateInputWindowHandle():
- Touchable region: Computed from window frame minus system gesture exclusion areas, accounting for rounded corners
- Input config flags:
NOT_TOUCHABLEfor non-interactive windows,NOT_FOCUSABLEfor FLAG_NOT_FOCUSABLE windows,IS_SPYfor monitoring channels,INTERCEPTS_STYLUSfor stylus-aware windows - Touch occlusion: UID-based trust (system UID windows are always trusted)
44.5.2 Input Consumers
InputMonitor manages special InputConsumer objects that intercept input at specific z-positions without being real windows:
| Consumer | Purpose |
|---|---|
INPUT_CONSUMER_WALLPAPER |
Receives touches that pass through transparent areas to wallpaper |
INPUT_CONSUMER_PIP |
Intercepts taps on PiP window for PiP controls |
INPUT_CONSUMER_RECENTS_ANIMATION |
Captures input during recents animation for gesture navigation |
Consumers are created via createInputConsumer() (line 222-248) and positioned in the z-order using SurfaceControl layering.
44.6 Focus Management
Window focus is a critical link between the window hierarchy and input dispatch. For multi-display focus routing, see §32.4.
sequenceDiagram
participant WMS as WindowManagerService
participant Root as RootWindowContainer
participant DC as DisplayContent
participant FR as FocusResolver
participant ID as InputDispatcher
Note over WMS: Activity change, window add/remove,<br/>or visibility change triggers focus update
WMS->>Root: updateFocusedWindowLocked(mode)
Root->>DC: updateFocusedWindowLocked()
DC->>DC: findFocusedWindow()<br/>traverses z-order
DC-->>Root: focusChanged = true
Root-->>WMS: changed
WMS->>ID: setFocusedWindow(token, displayId)
ID->>FR: setFocusedWindow(request)
FR->>FR: resolve per-display focus<br/>validate token matches expectations
ID->>ID: update focused window<br/>for key dispatch
WMS->>ID: setFocusedApplication(displayId, app)
Note over ID: Used for ANR attribution<br/>when no window is focused
Focus determination (WindowManagerService.updateFocusedWindowLocked(), line 6846):
- Delegates to
RootWindowContainer.updateFocusedWindowLocked() - Each
DisplayContentfinds its focused window by traversing windows in z-order - The first visible, focusable window (not
FLAG_NOT_FOCUSABLE) wins - Focus changes trigger
setFocusedWindow()on InputDispatcher FocusResolver(frameworks/native/services/inputflinger/dispatcher/FocusResolver.h) validates focus requests per-display, handling race conditions where focus tokens may become stale
Per-display focus: Each display maintains independent focus. setFocusedDisplay() controls which display receives key events from physical keyboards. Only one display is the “focused display” at a time for keys, but each display can independently track its own focused window for touch.
44.7 InputChannel — Transport Mechanism
InputChannel uses a Unix domain socketpair for zero-copy event transport between InputDispatcher (native) and each window’s InputEventReceiver (Java).
sequenceDiagram
participant WMS as WMS.addWindow()
participant ID as InputDispatcher
participant IP as InputPublisher
participant SOCKET as socketpair
participant ICO as InputConsumer native
participant IER as InputEventReceiver
WMS->>SOCKET: create socketpair
WMS->>ID: registerInputChannel(serverFd)
WMS-->>IER: return clientFd to app
Note over ID: During event dispatch
ID->>IP: publishMotionEvent() / publishKeyEvent()
IP->>SOCKET: write InputMessage
SOCKET->>ICO: read InputMessage
ICO->>IER: consumeEvent()
Note over IER: After processing
IER->>SOCKET: sendFinishedSignal(seq, handled)
SOCKET->>IP: receiveFinishedSignal()
IP->>ID: event acknowledged<br/>remove from waitQueue
InputMessage format: A fixed-size union structure containing either KeyEvent or MotionEvent data, serialized efficiently for socket transmission. The seq field provides reliable delivery tracking — InputDispatcher waits for per-event acknowledgment before sending more events to a window (back-pressure).
Lifecycle:
- Creation:
WMS.addWindow()creates anInputChannelpair — server side registered with InputDispatcher, client side returned to the app - Registration: Server channel associated with the window’s
InputWindowHandle - Event delivery: InputDispatcher writes
InputMessageviaInputPublisher;InputConsumeron the app side reads and delivers throughInputEventReceiver - Back-pressure: If the window’s
waitQueuegrows too large (events sent but unacknowledged), the dispatcher stops sending to that window — this is the early ANR signal - Cleanup: When the window is removed, the input channel is unregistered and both ends closed
Windows with INPUT_FEATURE_NO_INPUT_CHANNEL (e.g., some system overlays) skip channel creation entirely.
44.8 App-Side Input Processing — ViewRootImpl InputStage Chain
When an input event arrives at the app process via InputEventReceiver, ViewRootImpl processes it through a chain of InputStage objects. This chain implements the Strategy pattern, allowing each stage to handle, forward, or consume events.
graph TD
IER[WindowInputEventReceiver<br/>onInputEvent callback] --> DIE[deliverInputEvent]
DIE --> NPIS[NativePreImeInputStage<br/>native pre-IME processing]
NPIS --> VPIS[ViewPreImeInputStage<br/>View.dispatchKeyEventPreIme]
VPIS --> IIS[ImeInputStage<br/>forward to InputMethodManager]
IIS --> EPIS[EarlyPostImeInputStage<br/>system shortcuts, autofill hints]
EPIS --> NOPIS[NativePostImeInputStage<br/>native post-IME processing]
NOPIS --> VOPIS[ViewPostImeInputStage<br/>View hierarchy dispatch]
VOPIS --> SIS[SyntheticInputStage<br/>synthesize trackball → keys, etc.]
style VPIS fill:#e8f5e9
style IIS fill:#fff3e0
style VOPIS fill:#e8f5e9
Stage responsibilities:
| Stage | Purpose |
|---|---|
NativePreImeInputStage |
Pre-IME processing in native code (accessibility) |
ViewPreImeInputStage |
Calls View.dispatchKeyEventPreIme() — apps can intercept keys before IME |
ImeInputStage |
Forwards key events to InputMethodManager for soft keyboard processing |
EarlyPostImeInputStage |
Handles system shortcuts (back, menu), accessibility, autofill events |
NativePostImeInputStage |
Post-IME native processing |
ViewPostImeInputStage |
Main view hierarchy dispatch — mView.dispatchKeyEvent(), mView.dispatchPointerEvent() |
SyntheticInputStage |
Converts trackball/joystick events to synthetic key or scroll events |
WindowInputEventReceiver (ViewRootImpl.java:10812-10877) extends InputEventReceiver and is registered on the app’s main Looper. When a new event arrives on the InputChannel socket, the Looper wakes and calls onInputEvent(), which enqueues the event into mPendingInputEventHead and calls deliverInputEvent().
deliverInputEvent() (line 10659-10704) sends each event through the stage chain. If a stage returns FORWARD, the event passes to the next stage. If a stage returns FINISH, processing stops. Each stage can also defer processing by returning DEFER.
44.9 View Hierarchy Input Dispatch
After the InputStage chain, events reach the View hierarchy through different paths for touch and key events:
sequenceDiagram
participant VOPIS as ViewPostImeInputStage
participant DV as DecorView
participant ACT as Activity
participant PW as PhoneWindow
participant VG as ViewGroup
participant V as View
Note over VOPIS: Touch event path
VOPIS->>DV: dispatchPointerEvent(event)
DV->>VG: dispatchTouchEvent(event)
VG->>VG: onInterceptTouchEvent()
VG->>V: dispatchTouchEvent(event)
V->>V: onTouchEvent(event)
Note over VOPIS: Key event path
VOPIS->>DV: dispatchKeyEvent(event)
DV->>ACT: dispatchKeyEvent(event)
ACT->>PW: superDispatchKeyEvent(event)
PW->>DV: superDispatchKeyEvent(event)
DV->>VG: dispatchKeyEvent(event)
VG->>V: dispatchKeyEvent(event)
V->>V: onKeyDown() / onKeyUp()
Note over ACT: If unhandled by views
ACT->>ACT: onKeyDown() / onKeyUp()
Note over VOPIS: Key shortcut path (Menu key)
VOPIS->>DV: dispatchKeyShortcutEvent(event)
DV->>ACT: dispatchKeyShortcutEvent(event)
ACT->>PW: superDispatchKeyShortcutEvent(event)
PW->>DV: superDispatchKeyShortcutEvent(event)
DV->>VG: dispatchKeyShortcutEvent(event)
Touch dispatch (ViewPostImeInputStage.processPointerEvent(), ViewRootImpl.java:8329-8389):
DecorView.dispatchPointerEvent()→ViewGroup.dispatchTouchEvent()ViewGroup.onInterceptTouchEvent()allows parent views to steal touch sequences from children- Touch events traverse the view tree top-down (dispatch) and bottom-up (handling)
View.onTouchListenertakes priority overView.onTouchEvent()
Key dispatch (DecorView.dispatchKeyEvent(), frameworks/base/core/java/com/android/internal/policy/DecorView.java:356):
DecorViewroutes toActivity.dispatchKeyEvent()via theWindow.CallbackinterfaceActivitygivesPhoneWindow.superDispatchKeyEvent()first chancePhoneWindowdelegates back toDecorView.superDispatchKeyEvent()→ ViewGroup hierarchy- If no View consumes the key,
Activity.onKeyDown()/onKeyUp()gets the final chance dispatchKeyShortcutEvent()(line 394) follows a parallel path for menu-triggered shortcuts
44.10 Keyboard Shortcut Subsystem
The keyboard shortcut system spans from native InputDispatcher through system policy to per-app shortcut handling.
44.10.1 System Key Interception — PhoneWindowManager
PhoneWindowManager (frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java) provides two interception points that process key events before they reach the focused window:
flowchart TD
ID[InputDispatcher] -->|"key event"| IMS[InputManagerService]
IMS -->|"1. interceptKeyBeforeQueueing()"| IKBQ[PhoneWindowManager<br/>interceptKeyBeforeQueueing]
IKBQ -->|"policyFlags: PASS_TO_USER?"| QUEUE{Queue or<br/>consume?}
QUEUE -->|"PASS_TO_USER=0"| CONSUMED[Event consumed<br/>e.g., POWER, VOLUME]
QUEUE -->|"PASS_TO_USER=1"| DISPATCH_QUEUE[Enter dispatch queue]
DISPATCH_QUEUE --> IMS2[InputDispatcher<br/>dispatches to focused window]
IMS2 -->|"2. interceptKeyBeforeDispatching()"| IKBD[PhoneWindowManager<br/>interceptKeyBeforeDispatching]
IKBD --> KGC[KeyGestureController]
KGC --> ACTIONS{Recognized<br/>gesture?}
ACTIONS -->|"Meta+Tab"| RECENTS[Launch Recents]
ACTIONS -->|"Meta+N"| NOTIF[Open Notifications]
ACTIONS -->|"Meta+H"| HOME[Go Home]
ACTIONS -->|"Meta+Enter"| DMODE[Toggle Desktop Mode]
ACTIONS -->|"Not recognized"| WINDOW[Forward to<br/>focused window]
interceptKeyBeforeQueueing() (line 4386): Called on the InputDispatcher thread before the event enters the dispatch queue. Handles hardware keys:
- POWER: Screen on/off, long-press for power menu
- VOLUME_UP/DOWN: Volume adjustment, long-press for skip track
- MUTE: Audio mute toggle
- CALL/ENDCALL: Telephony integration
- Returns
ACTION_PASS_TO_USERflag to determine if the event should proceed to the focused window
interceptKeyBeforeDispatching() (line 3359): Called after the event is queued but before delivery to the focused window. Handles system shortcuts and gestures:
- Delegates to
KeyGestureControllerfor complex gesture detection - Handles screenshot (Power+VolumeDown), bugreport, and other multi-key combinations
44.10.2 KeyGestureController and Key Combination Handling
KeyGestureController (frameworks/base/services/core/java/com/android/server/input/KeyGestureController.java) manages gesture detection for multi-key combinations:
classDiagram
class KeyGestureController {
-mKeyGestureMap: Map
+interceptKeyBeforeDispatching(event): long
+handleKeyGesture(event, focusedToken)
}
class KeyCombinationManager {
-mRules: List~TwoKeysCombinationRule~
+interceptKey(event): boolean
+addRule(rule)
}
class ModifierShortcutManager {
-mIntentShortcuts: SparseArray~Intent~
+getIntent(event): Intent
}
class GlobalKeyManager {
-mKeyMapping: SparseArray~ComponentName~
+handleGlobalKey(event): boolean
}
KeyGestureController --> KeyCombinationManager
KeyGestureController --> ModifierShortcutManager
KeyGestureController --> GlobalKeyManager
KeyCombinationManager: Handles two-key combinations (e.g., Power+VolumeDown for screenshot). Rules are defined as TwoKeysCombinationRule objects that match specific key pairs.
ModifierShortcutManager (frameworks/base/services/core/java/com/android/server/policy/ModifierShortcutManager.java): Manages Meta+letter shortcuts for app launching:
- Reads shortcut definitions from
bookmarks.xmlresource - Meta+B → launch browser, Meta+E → launch email, Meta+C → launch contacts, etc.
- Constructs
Intentobjects from the shortcut table and launches the corresponding activity
GlobalKeyManager: Routes specific global keycodes (defined in global_keys.xml) directly to designated system components, bypassing the focused window entirely.
44.10.3 Key Dispatch Order — Complete Flow
The complete key event flow from hardware to final handling:
sequenceDiagram
participant HW as Hardware
participant ID as InputDispatcher
participant IMS as InputManagerService
participant PWM as PhoneWindowManager
participant KGC as KeyGestureController
participant APP as Focused Window
participant DV as DecorView
participant ACT as Activity
participant VIEW as View Hierarchy
HW->>ID: Key event (via InputReader)
ID->>IMS: interceptKeyBeforeQueueing()
IMS->>PWM: interceptKeyBeforeQueueing()
PWM-->>IMS: policyFlags (PASS_TO_USER?)
IMS-->>ID: policyFlags
Note over ID: Event enters dispatch queue
ID->>IMS: interceptKeyBeforeDispatching()
IMS->>PWM: interceptKeyBeforeDispatching()
PWM->>KGC: interceptKeyBeforeDispatching()
alt System shortcut recognized
KGC-->>PWM: timeout (consume event)
Note over PWM: Execute system action<br/>(e.g., launch recents)
else Not a system shortcut
KGC-->>PWM: 0 (pass through)
PWM-->>IMS: 0
IMS-->>ID: 0
ID->>APP: dispatch via InputChannel
APP->>DV: dispatchKeyEvent()
DV->>ACT: dispatchKeyEvent()
ACT->>ACT: dispatchKeyShortcutEvent()
ACT->>VIEW: View.dispatchKeyEvent()
alt View consumes key
VIEW-->>APP: handled = true
else Unhandled
ACT->>ACT: onKeyDown() / onKeyUp()
end
APP->>ID: finishInputEvent(handled)
end
44.10.4 Keyboard Shortcut Helper UI
The keyboard shortcut helper (Meta+/) displays available shortcuts:
KeyboardShortcuts (frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java) provides the UI:
- When Meta+/ is pressed,
PhoneWindowManagertriggers the shortcut helper KeyboardShortcutsqueries the currentActivityfor its shortcuts viaActivity.onProvideKeyboardShortcuts()- Also collects system shortcuts from
PhoneWindowManager - Displays them in a categorized dialog showing shortcut key combinations and their descriptions
Apps declare shortcuts by overriding Activity.onProvideKeyboardShortcuts() and adding KeyboardShortcutGroup entries:
- Each group has a label and a list of
KeyboardShortcutInfo(key + modifiers + label) - Groups are displayed as categories in the helper dialog
Meta state constants (KeyEvent.java):
META_META_ON(0x10000) — Meta/Windows/Super keyMETA_CTRL_ON(0x1000) — Ctrl keyMETA_ALT_ON(0x02) — Alt keyMETA_SHIFT_ON(0x01) — Shift key
44.11 Touch Interception and System Gestures
The system provides multiple layers for intercepting input before it reaches app windows:
-
InputFilter (accessibility):
InputManagerServicecan install anInputFilterthat sees all events before dispatch, used for accessibility services -
Input consumers (WM level):
InputMonitor.mInputConsumersinsert invisible input-receiving surfaces at specific z-positions. PiP usesINPUT_CONSUMER_PIPto intercept taps on the PiP window -
SPY windows: Windows with
inputConfig::IS_SPYreceive copies of all touch events within their bounds without consuming them. Used by system gesture monitors (back gesture, edge swipe) and the status bar -
TaskPositioner: Used in freeform/desktop mode to intercept touches on window title bars for drag-to-move and drag-to-resize operations
-
System gesture exclusion: Apps can declare
setSystemGestureExclusionRects()to prevent the system back gesture from firing in specific areas, coordinated throughDisplayPolicy -
Launcher quickstep:
TouchInteractionService(packages/apps/Launcher3/quickstep/src/com/android/quickstep/TouchInteractionService.java) usesInputMonitorCompatto create a monitoring channel that observes all touch events on the display for gesture navigation -
InputTransferToken: Enables input event stream transfer between windows (e.g., from a parent window to an embedded
SurfaceControlViewHost). The token allows the drag gesture to be smoothly transferred without breaking the touch sequence
Reference files:
frameworks/native/services/inputflinger/InputManager.h— Pipeline orchestrationframeworks/native/services/inputflinger/reader/include/InputReader.h— Event reading from EventHubframeworks/native/services/inputflinger/reader/include/EventHub.h— Raw device access via epoll/inotifyframeworks/native/services/inputflinger/reader/mapper/TouchInputMapper.cpp— Touch processing (~2600 lines)frameworks/native/services/inputflinger/reader/mapper/KeyboardInputMapper.h— Key event generationframeworks/native/services/inputflinger/InputProcessor.h— ML-based motion classificationframeworks/native/services/inputflinger/UnwantedInteractionBlocker.h— Palm rejectionframeworks/native/services/inputflinger/PointerChoreographer.h— Pointer icon managementframeworks/native/services/inputflinger/InputFilter.h— Accessibility input filteringframeworks/native/services/inputflinger/dispatcher/InputDispatcher.h— Core routing engine (line 408: findTouchedWindowTargets, line 817: findFocusedWindowTargetLocked)frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp— Dispatcher implementationframeworks/native/services/inputflinger/dispatcher/FocusResolver.h— Per-display focus stateframeworks/native/services/inputflinger/dispatcher/Connection.h— Per-channel dispatch stateframeworks/native/services/inputflinger/dispatcher/AnrTracker.h— ANR timeout trackingframeworks/native/services/inputflinger/dispatcher/TouchedWindow.h— Per-device touch stateframeworks/native/services/inputflinger/dispatcher/InputTarget.h— DispatchMode enumframeworks/native/libs/gui/include/gui/WindowInfo.h— WindowInfo data structureframeworks/native/libs/gui/include/gui/WindowInfosListener.h— onWindowInfosChanged callbackframeworks/native/libs/gui/include/gui/WindowInfosUpdate.h— Batched window/display updateframeworks/native/services/surfaceflinger/WindowInfosListenerInvoker.h— SF dispatches to listenersframeworks/native/services/surfaceflinger/SurfaceFlinger.cpp— updateInputFlinger() (line 4620), buildWindowInfos()frameworks/native/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp— fillInputFrameInfo(), getInputTransform()frameworks/native/libs/gui/include/gui/SurfaceComposerClient.h— setInputWindowInfo() (line 701-703)frameworks/base/services/core/java/com/android/server/input/InputManagerService.java— Java input serviceframeworks/base/services/core/java/com/android/server/wm/InputMonitor.java— WM-input bridge (line 251: populateInputWindowHandle, line 222: createInputConsumer)frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java— Focus management (line 6846: updateFocusedWindowLocked)frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java— Key interception (line 4386: interceptKeyBeforeQueueing, line 3359: interceptKeyBeforeDispatching)frameworks/base/services/core/java/com/android/server/input/KeyGestureController.java— Gesture detectionframeworks/base/services/core/java/com/android/server/policy/ModifierShortcutManager.java— Meta+letter shortcutsframeworks/base/core/java/android/view/ViewRootImpl.java— InputStage chain (line 1769-1781: init, line 10812: WindowInputEventReceiver, line 10659: deliverInputEvent)frameworks/base/core/java/com/android/internal/policy/DecorView.java— Key dispatch routing (line 356: dispatchKeyEvent, line 394: dispatchKeyShortcutEvent)frameworks/base/core/java/android/view/InputChannel.java— Input transport channelframeworks/base/core/java/android/view/InputEventReceiver.java— App-side event receiverframeworks/base/core/java/android/view/KeyEvent.java— Key codes and meta state constantsframeworks/base/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java— Shortcut helper UIpackages/apps/Launcher3/quickstep/src/com/android/quickstep/TouchInteractionService.java— Gesture navigation input monitoring
45. Per-Variant Shell and WM Customization
Android ships on phones, tablets, TVs, cars, and desktop-like form factors. Each variant customizes the window management layer through a well-defined Dagger-based dependency injection architecture that allows per-product feature selection without forking core code.
45.1 Architecture: Three-Layer Module System
The Shell uses a three-layer DI pattern:
graph TB
subgraph "Layer 1: Shared Base"
BASE[WMShellBaseModule<br/>All shared components]
end
subgraph "Layer 2: Variant-Specific Shell Modules"
PHONE[WMShellModule<br/>Phone/Tablet]
TV[TvWMShellModule<br/>Android TV]
CAR_SHELL[CarWMShellModule + AutoShellModule<br/>Android Automotive]
end
subgraph "Layer 3: Variant WMComponent"
WMC[WMComponent<br/>Phone/Tablet default]
TV_WMC[TvWMComponent<br/>TV]
CAR_WMC[CarWMComponent<br/>Automotive]
end
subgraph "SystemUI Integration"
SYSUI[SystemUI<br/>Phone]
TV_SYSUI[TvSystemUI]
CAR_SYSUI[CarSystemUI]
end
BASE --> PHONE
BASE --> TV
BASE --> CAR_SHELL
PHONE --> WMC
TV --> TV_WMC
CAR_SHELL --> CAR_WMC
WMC --> SYSUI
TV_WMC --> TV_SYSUI
CAR_WMC --> CAR_SYSUI
Layer 1 — WMShellBaseModule: Shared by all variants. Provides core components (ShellTaskOrganizer, Transitions, DisplayController) and uses @DynamicOverride to allow variant modules to replace default implementations.
Layer 2 — Variant Shell Modules: Each variant provides a Dagger @Module that selectively overrides base components via @DynamicOverride:
WMShellModule— Phone/tablet (includes PiP, Bubbles, Desktop mode, Freeform, BackAnimation)TvWMShellModule— TV (replaces SplitScreen withTvSplitScreenController, replaces StartingWindow algorithm)CarWMShellModule+AutoShellModule— Auto (replaces FullscreenTaskListener, adds ScalableUI, Panel system)
Layer 3 — WMComponent: Each variant declares which module it includes:
WMComponentincludesWMShellModule.class(default)TvWMComponentincludesTvWMShellModule.classCarWMComponentincludesCarWMShellModule.class
45.2 The @DynamicOverride Mechanism
@DynamicOverride (WMShell/src/com/android/wm/shell/dagger/DynamicOverride.java) is the key to variant customization. It enables:
- Base module declares an optional binding with
@BindsOptionalOf @DynamicOverride - Base module provides the feature with a check: if the
@DynamicOverrideOptional is present, use the override; otherwise use the default orOptional.empty() - Variant module can provide a
@DynamicOverrideimplementation to replace the default
Example pattern from WMShellBaseModule:
// Base declares optional override point
@BindsOptionalOf @DynamicOverride
abstract SplitScreenController optionalSplitScreenController();
// Base provides the feature, checking for override
@Provides
static Optional<SplitScreenController> providesSplitScreenController(
@DynamicOverride Optional<SplitScreenController> splitscreenController,
Context context) {
if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
return splitscreenController;
}
return Optional.empty();
}
Then TvWMShellModule provides its own override:
@Provides @DynamicOverride
static SplitScreenController provideSplitScreenController(...) {
return new TvSplitScreenController(...); // TV-specific implementation
}
45.3 Feature Matrix Per Variant
| Feature | Phone/Tablet | TV | Automotive | Desktop Mode |
|---|---|---|---|---|
| PiP | Phone PiP | TV PiP (different UX) | Optional | Phone PiP |
| Split Screen | SplitScreenController | TvSplitScreenController | No | SplitScreenController |
| Freeform | FreeformComponents | No | No | FreeformComponents |
| Bubbles | BubbleController | No | No | BubbleController |
| Desktop Mode | DesktopTasksController | No | No | DesktopTasksController |
| Back Animation | BackAnimationController | BackAnimationController | No | BackAnimationController |
| One-Handed Mode | OneHandedController | No | No | No |
| App-to-Web | AppToWebRepository | No | No | AppToWebRepository |
| Window Decorations | DesktopModeWindowDecor | No | AutoDisplayCompatWindowDecorViewModel | DesktopModeWindowDecor |
| Starting Window | Default algorithm | TvStartingWindowTypeAlgorithm | Default | Default |
| Activity Embedding | ActivityEmbeddingController | No | No | ActivityEmbeddingController |
| Letterbox | DefaultLetterboxDependencies | DefaultLetterboxDependencies | IgnoreLetterboxDependencies | DefaultLetterboxDependencies |
45.4 Android TV Customizations
WMComponent: TvWMComponent (packages/apps/TvSystemUI/src/com/android/systemui/tv/dagger/TvWMComponent.kt:26)
- Extends
WMComponent, includesTvWMShellModule - Minimal set — no Bubbles, no Freeform, no Desktop Mode, no One-Handed
Key TV overrides (TvWMShellModule.java):
-
TvSplitScreenController: TV-specific split-screen with D-pad navigation instead of touch. Uses the same
StageCoordinatorcore but different interaction model -
TvStartingWindowTypeAlgorithm: Different splash screen logic for TV apps (larger screens, different transition expectations)
- TV PiP (
TvPipModule): Entirely different PiP UX from phone:TvPipController— D-pad controlled, no drag gestures. State machine:STATE_NO_PIP→STATE_PIP→STATE_PIP_MENUTvPipBoundsAlgorithm— Gravity-based corner positioning (not free-form). Supports expanded PiP mode with fixed dimensionsTvPipMenuController— 10-second auto-close timeout, D-pad navigation for move/expand/close actionsTvPipTransition— Zoom animation withZOOM_ANIMATION_SCALE_FACTOR = 0.97fTvPipBoundsState— Persists last gravity position to SharedPreferences, RTL support
- No-touch navigation:
TvNotificationShadeWindowControlleris a no-op (no pull-down panel). All system UI is D-pad navigated viaKEYCODE_DPAD_*events
SystemUI: packages/apps/TvSystemUI/ — Minimal system bars, Leanback-focused navigation, no notification shade
45.5 Android Automotive Customizations
WMComponent: CarWMComponent (packages/apps/Car/SystemUI/src/com/android/systemui/wmshell/CarWMComponent.java:44)
- Extends
WMComponent, includesCarWMShellModule - Exports automotive-specific components:
AutoTaskStackController,AutoLayoutManager,AutoDecorManager,ScalableUIWMInitializer
Key Auto overrides (CarWMShellModule.java):
-
CarFullscreenTaskMonitorListener: Replaces default
FullscreenTaskListener— monitors tasks with automotive-specific behavior (driver distraction awareness, multi-display passenger zones) -
AutoDisplayCompatWindowDecorViewModel: Automotive window decorations — compatibility UI for apps not designed for automotive screens
- ScalableUI Panel System: Automotive’s unique panel-based layout architecture:
PanelConfigReader— reads XML panel definitionsTaskPanel,DecorPanel,BasePanel— panel typesPanelAutoTaskStackTransitionHandlerDelegate— custom transition handling per panel- Gated by
Flag.ScalableUIEnabled
-
DisplaySystemBarsController: Automotive-specific system bars with
CarWMUserHelperfor multi-user support - No PiP:
@BindsOptionalOf abstract Pip optionalPip()— PiP is not bound (Optional.empty)
Auto Shell Library (packages/services/Car/libs/car-wm-shell-lib/):
AutoShellModule— BindsAutoTaskStackController,AutoHomeTaskMonitorAutoLayoutManager— Manages layout of tasks, insets, and safe regions per displayAutoCaptionController— Caption/subtitle bars per display with safe region supportAutoTaskRepository— Maps task IDs to surface controls, tracks root task stacksAutoDecorManager— Window decoration management for automotive
Activity Interception (frameworks/opt/car/services/):
CarActivityInterceptorUpdatableImpl— Intercepts activity launches to route them to appropriate displays/task containersCarLaunchParamsModifier— Controls display assignment during activity launch, manages driver vs passenger display routingCarActivityManager— System API with launch behavior constants:LAUNCH_BEHAVIOR_DEFAULT,LAUNCH_BEHAVIOR_REMAIN_IN_SOURCE_ROOT_TASK,LAUNCH_BEHAVIOR_REPARENT_TO_SOURCE_ROOT_TASK
Multi-User Multi-Display (MUMD):
ExtraDisplayMonitor— Monitors display addition/removal, assigns users to extra displays (passenger screens)CarLaunchParamsModifierUpdatableImpl— Tracks driver user vs passenger, manages passenger display list, handles user switching
Display Area Policy: CarDisplayAreaPolicyProvider (frameworks/opt/car/services/builtInServices/src/com/android/server/wm/CarDisplayAreaPolicyProvider.java:36):
- Completely custom display area hierarchy for automotive
- Defines:
FOREGROUND_DISPLAY_AREA_ROOT,BACKGROUND_TASK_CONTAINER,CONTROL_BAR_DISPLAY_AREA,FEATURE_TITLE_BAR,FEATURE_VOICE_PLATE - Background and control bar TDAs use
WINDOWING_MODE_MULTI_WINDOWfor concurrent task containers - Non-default displays fall back to
DefaultProvider - Enables map/media/controls to run simultaneously in separate display areas
45.6 Phone/Tablet (Default) Configuration
WMComponent: WMComponent (frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java:56)
- Includes
WMShellModule— the richest feature set - Full PiP, Split Screen, Freeform, Bubbles, Desktop Mode, Back Animation, One-Handed, Activity Embedding, App-to-Web
Desktop Mode Activation (Phone/Tablet):
- Controlled by
DesktopExperienceFlags(133 flags) DesktopTasksControllerenables freeform windowing with title bars- Window decorations via
DesktopModeWindowDecorViewModel - Multi-display drag via
MultiDisplayDragMoveIndicatorController
Display Area Policy: Default DisplayAreaPolicy.DefaultProvider — standard hierarchy with system windows, app windows, and IME container
45.7 How Variant Selection Works at Build Time
The variant selection happens through the build system and SystemUI product configuration:
graph LR
subgraph "Build System"
PRODUCT[Product Config<br/>e.g., aosp_cf_phone, aosp_tv, aosp_car]
end
subgraph "SystemUI APK"
PHONE_SI[SystemUI<br/>frameworks/base/packages/SystemUI]
TV_SI[TvSystemUI<br/>packages/apps/TvSystemUI]
CAR_SI[CarSystemUI<br/>packages/apps/Car/SystemUI]
end
subgraph "Dagger Root Component"
PHONE_ROOT[GlobalRootComponent<br/>includes WMComponent]
TV_ROOT[TvGlobalRootComponent<br/>includes TvWMComponent]
CAR_ROOT[CarGlobalRootComponent<br/>includes CarWMComponent]
end
PRODUCT -->|"PRODUCT_PACKAGES += SystemUI"| PHONE_SI
PRODUCT -->|"PRODUCT_PACKAGES += TvSystemUI"| TV_SI
PRODUCT -->|"PRODUCT_PACKAGES += CarSystemUI"| CAR_SI
PHONE_SI --> PHONE_ROOT
TV_SI --> TV_ROOT
CAR_SI --> CAR_ROOT
Each product’s SystemUI APK declares its own Dagger root component, which includes the appropriate WMComponent subcomponent. The Shell library code is shared — only the DI wiring differs.
45.8 OEM Extension Points
OEMs can customize beyond the AOSP variants:
-
Custom DisplayAreaPolicy.Provider: Register via
R.string.config_deviceSpecificDisplayAreaPolicyProvider— allows completely custom display area hierarchies (like automotive’sCarDisplayAreaPolicyProvider) -
Custom WMShellModule: Create a new Dagger module extending the base, overriding specific
@DynamicOverridebindings -
Custom SystemUI: Ship a replacement SystemUI APK with a custom
WMComponentthat includes OEM-specific modules -
Feature Flags: Use
DesktopExperienceFlagsandDesktopModeFlagsto gate features at runtime without code changes -
DisplayAreaOrganizer: Register custom organizers for specific display area features via
FEATURE_VENDOR_FIRSTthroughFEATURE_VENDOR_LAST
Reference files:
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java— Shared base module with @DynamicOverride bindingsframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java— Phone/tablet module (richest feature set)frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java— TV module (TvSplitScreen, TvPip, TvStartingWindow)frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java— Base WMComponent interfaceframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java— Override mechanism annotationpackages/apps/TvSystemUI/src/com/android/systemui/tv/dagger/TvWMComponent.kt— TV WMComponentpackages/apps/Car/SystemUI/src/com/android/systemui/wmshell/CarWMComponent.java— Automotive WMComponentpackages/apps/Car/SystemUI/src/com/android/systemui/wmshell/CarWMShellModule.java— Automotive Shell modulepackages/services/Car/libs/car-wm-shell-lib/src/com/android/wm/shell/automotive/AutoShellModule.java— Auto Shell library moduleframeworks/opt/car/services/builtInServices/src/com/android/server/wm/CarDisplayAreaPolicyProvider.java— Automotive display area policyframeworks/base/services/core/java/com/android/server/wm/DisplayAreaPolicy.java— Display area policy base and DefaultProviderframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java— TV PiP controller (D-pad driven)frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java— TV split-screen with TvStageCoordinatorpackages/services/Car/libs/car-wm-shell-lib/src/com/android/wm/shell/automotive/AutoTaskRepository.java— Automotive task trackingframeworks/opt/car/services/updatableServices/src/com/android/server/wm/CarLaunchParamsModifierUpdatableImpl.java— Auto display routingframeworks/opt/car/services/updatableServices/src/com/android/internal/car/updatable/ExtraDisplayMonitor.java— MUMD passenger display monitorpackages/services/Car/car-lib/src/android/car/app/CarActivityManager.java— Automotive activity management API
46. SystemUI Multi-Display Architecture
SystemUI creates per-display instances of system decorations (status bar, navigation bar) using a PerDisplayStore pattern backed by Dagger subcomponents. Each display receives its own scoped set of controllers, views, and lifecycle management.
46.1 Display Tracking
DisplayTracker is the primary interface for monitoring display changes:
graph TB
subgraph "Display Events"
DM[DisplayManager]
DT["DisplayTracker<br/>(DisplayTrackerImpl)"]
DR["DisplayRepository<br/>(Flow-based)"]
end
subgraph "Per-Display Consumers"
NBC["NavigationBarController<br/>(SparseArray of NavigationBar)"]
SBWC["StatusBarWindowControllerStore<br/>(PerDisplayStore)"]
SBI["StatusBarInitializerStore<br/>(PerDisplayStore)"]
DSC["SystemUIDisplaySubcomponent<br/>(@PerDisplaySingleton)"]
end
DM -->|"DisplayListener"| DT
DT -->|"onDisplayAdded/Removed/Changed"| DR
DR -->|"displayRemovalEvent"| SBWC
DR -->|"displayRemovalEvent"| SBI
DT -->|"DisplayDecorationListener"| NBC
SBWC --> DSC
SBI --> DSC
DisplayTrackerImpl wraps DisplayManager.DisplayListener and dispatches three events:
onDisplayAdded(displayId)— triggers component creation on new displaysonDisplayRemoved(displayId)— triggers cleanup of per-display instancesonDisplayChanged(displayId)— notifies of configuration changes (density, rotation)
46.2 Per-Display Component Creation (PerDisplayStore Pattern)
The PerDisplayStore<T> interface provides lazy per-display instance management:
classDiagram
class PerDisplayStore~T~ {
<<interface>>
+forDisplay(displayId: Int): T?
}
class StatusBarPerDisplayStoreImpl~T~ {
-perDisplayInstances: MutableMap
+forDisplay(displayId): T?
#createInstanceForDisplay(displayId): T?
#onDisplayRemovalAction(instance: T)
}
class MultiDisplayStatusBarWindowControllerStore {
+createInstanceForDisplay(displayId): StatusBarWindowController?
+onDisplayRemovalAction(instance)
}
class MultiDisplayStatusBarInitializerStore {
+createInstanceForDisplay(displayId): StatusBarInitializer?
+onDisplayRemovalAction(instance)
}
PerDisplayStore <|.. StatusBarPerDisplayStoreImpl
StatusBarPerDisplayStoreImpl <|-- MultiDisplayStatusBarWindowControllerStore
StatusBarPerDisplayStoreImpl <|-- MultiDisplayStatusBarInitializerStore
Each PerDisplayStore implementation:
- Lazy-creates instances on first
forDisplay(displayId)call - Watches
displayRepository.displayRemovalEventfor cleanup - Calls
onDisplayRemovalAction(instance)to tear down views and cancel coroutine scopes
46.3 Navigation Bar Multi-Display
NavigationBarControllerImpl manages navigation bars per display via a SparseArray<NavigationBar>:
- Display decoration listener:
onDisplayAddSystemDecorations(displayId)creates a new navigation bar;onDisplayRemoved(displayId)removes it - Per-display context: Creates display-specific context via
context.createDisplayContext(display) - Per-display operations:
checkNavBarModes(displayId),finishBarAnimations(displayId),touchAutoDim(displayId),transitionTo(displayId, barMode, animate)— all route to the correct per-displayNavigationBarinstance - Fallback: Delegates to
TaskbarDelegatewhen no NavigationBar exists for a display
46.4 Status Bar Multi-Display
Status bar initialization follows a two-layer per-display pattern:
-
StatusBarWindowController (per display): Manages the status bar window (
WindowManager.LayoutParams) for a specific display. TracksmDisplayId, handlesWindowManager.InvalidDisplayExceptionif the display is removed during initialization. -
StatusBarInitializer (per display): Creates the status bar view hierarchy for a specific display. Retrieves per-display dependencies from
ReferenceSysUIDisplaySubcomponent:StatusBarWindowController— window managementStatusBarModePerDisplayRepository— translucency/opaque modeStatusBarConfigurationController— per-display config changesDarkIconDispatcher— icon tinting per display
46.5 Dagger Per-Display Scope
SystemUIDisplaySubcomponent provides a Dagger subcomponent scoped to a single display:
- Scope:
@PerDisplaySingleton— instances live as long as the display exists - Qualifiers:
@DisplayAwarefor display-aware dependencies,@DisplayIdfor the display ID binding - Lifecycle:
start()called when display added,stop()called on removal (cancelsdisplayCoroutineScope) - Provided instances:
displayStateRepository,statusBarWindowStateController,darkIconDispatcher,displayCoroutineScope
46.6 Multi-Display Initialization Flow
sequenceDiagram
participant WMS as WindowManagerService
participant DM as DisplayManager
participant DT as DisplayTrackerImpl
participant NBC as NavigationBarController
participant SBIS as StatusBarInitializerStore
participant SBWCS as StatusBarWindowControllerStore
WMS->>DM: Display connected
DM->>DT: onDisplayAdded(displayId)
DT->>NBC: DisplayDecorationListener.onDisplayAddSystemDecorations(displayId)
NBC->>NBC: createNavigationBar(display, null, null)
Note over NBC: context.createDisplayContext(display)<br/>NavigationBar added to mNavigationBars[displayId]
DT->>SBIS: forDisplay(displayId) triggered
SBIS->>SBWCS: forDisplay(displayId)
SBWCS->>SBWCS: createInstanceForDisplay(displayId)
Note over SBWCS: StatusBarWindowController created<br/>with display-specific context
SBIS->>SBIS: createInstanceForDisplay(displayId)
Note over SBIS: StatusBarInitializer created<br/>PhoneStatusBarView inflated for display
Note over WMS: Display disconnected
DM->>DT: onDisplayRemoved(displayId)
DT->>NBC: DisplayDecorationListener.onDisplayRemoved(displayId)
NBC->>NBC: removeNavigationBar(displayId)
DT->>SBIS: displayRemovalEvent collected
SBIS->>SBIS: onDisplayRemovalAction(initializer.stop())
SBIS->>SBWCS: displayRemovalEvent collected
SBWCS->>SBWCS: onDisplayRemovalAction(controller.stop())
46.7 Feature Gate
The multi-display status bar architecture is gated behind StatusBarConnectedDisplays, enabled via DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT. When disabled, SingleDisplayStatusBarWindowControllerStore and SingleDisplayStatusBarInitializerStore provide the legacy single-display behavior.
Reference files:
frameworks/base/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt— Display change notification interfaceframeworks/base/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt— DisplayManager.DisplayListener wrapperframeworks/base/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt— Per-display lazy instance patternframeworks/base/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt— Multi/Single display window controller storeframeworks/base/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt— Multi/Single display initializer storeframeworks/base/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt— Per-display status bar view creationframeworks/base/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java— Per-display window management (mDisplayId)frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java— Per-display navigation bar management (SparseArray)frameworks/base/packages/SystemUI/src/com/android/systemui/display/dagger/SystemUIDisplaySubcomponent.kt— @PerDisplaySingleton Dagger subcomponentframeworks/base/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConnectedDisplays.kt— Feature flag for multi-display status barframeworks/base/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt— Multi-display startup (registerStatusBarForAllDisplays)
47. Launcher3 and Recents Multi-Display Architecture
Launcher3 provides per-display home screens and recent task views through SecondaryDisplayLauncher and display-aware recents filtering. Each display can have its own launcher instance, taskbar, and recents container.
47.1 SecondaryDisplayLauncher
When a secondary display is connected, the system launches SecondaryDisplayLauncher on that display via the android.intent.category.SECONDARY_HOME intent category (registered in AndroidManifest-common.xml):
graph TB
subgraph "Default Display"
QL["QuickstepLauncher<br/>(CATEGORY_HOME)"]
TB1["TaskbarActivityContext"]
RV1["RecentsView"]
end
subgraph "Secondary Display N"
SDL["SecondaryDisplayLauncher<br/>(CATEGORY_SECONDARY_HOME)"]
SD_DEL["SecondaryDisplayDelegate"]
TB2["TaskbarActivityContext"]
RWM["RecentsWindowManager"]
end
subgraph "Shared Infrastructure"
IDP["InvariantDeviceProfile"]
TBM["TaskbarManagerImpl<br/>Map per displayId to TaskbarActivityContext"]
OCO["OverviewComponentObserver"]
end
IDP -->|"createDeviceProfileForSecondaryDisplay()"| SDL
TBM -->|"getTaskbarForDisplay(displayId)"| TB1
TBM -->|"getTaskbarForDisplay(displayId)"| TB2
OCO -->|"getContainerInterface(displayId)"| RWM
OCO -->|"getContainerInterface(DEFAULT)"| QL
SDL --> SD_DEL
SecondaryDisplayLauncher (SecondaryDisplayLauncher.java):
- Extends
BaseActivity, implementsBgDataModel.CallbacksandDragController.DragListener - Creates a display-specific
DeviceProfileviaInvariantDeviceProfile.createDeviceProfileForSecondaryDisplay(context)— adapts grid, density, bounds, and orientation for the secondary display - Has its own
SecondaryDragLayer,ActivityAllAppsContainerView, andSecondaryDragController— all scoped to the secondary display - Uses
SecondaryDisplayDelegate(withSecondaryDisplayQuickstepDelegateImplfor Quickstep builds) for display-specific operations
Display-Specific DeviceProfile (InvariantDeviceProfile.java):
createDeviceProfileForSecondaryDisplay(displayContext):
- Info from displayContext (density, size, rotation)
- setIsMultiDisplay(false)
- setExternalDisplay(true)
- setWindowBounds(getRealBounds(displayContext))
- setTransposeLayoutWithOrientation(false)
- setSecondaryDisplayOptionSpec()
47.2 Per-Display Taskbar
TaskbarManagerImpl maintains independent taskbar instances per display:
| Data Structure | Type | Purpose |
|---|---|---|
mTaskbars |
Map<Integer, TaskbarActivityContext> |
Active taskbar per display |
mWindowContexts |
SparseArray<Context> |
Display-specific window contexts |
mRootLayouts |
SparseArray<FrameLayout> |
Per-display root layout |
mNavButtonControllers |
SparseArray<TaskbarNavButtonController> |
Per-display nav buttons |
mExternalDeviceProfiles |
SparseArray<DeviceProfile> |
Per-display device profiles |
Key method: getTaskbarForDisplay(displayId) returns the TaskbarActivityContext for a specific display, or null if no taskbar exists on that display.
Taskbar recreation is triggered on per-display events: CHANGE_DENSITY, CHANGE_NAVIGATION_MODE, CHANGE_DESKTOP_MODE, CHANGE_ROTATION.
47.3 Recents Across Displays
AOSP maintains a single global recent tasks list (not per-display), but provides display-specific filtering and animation at the presentation layer:
graph TB
subgraph "Server (system_server)"
RT["RecentTasks<br/>(single ArrayList of Task)"]
RUN["RunningTasks<br/>(per-display filtering)"]
RWC["RootWindowContainer"]
end
subgraph "Shell (SystemUI process)"
RTH["RecentsTransitionHandler"]
RC1["RecentsController<br/>mDisplayId = 0"]
RC2["RecentsController<br/>mDisplayId = 2"]
STO["ShellTaskOrganizer<br/>getRunningTasks(displayId)"]
end
subgraph "Launcher3"
RFS["RecentsFilterState<br/>getFilter(pkg, displayId)"]
RV["RecentsView<br/>mContainer.getDisplayId()"]
GT["GroupTask<br/>matchesDisplayId(displayId)"]
end
RT -->|"getRecentTasks()<br/>(no displayId param)"| RWC
RWC -->|"getRunningTasks(displayId)"| RUN
RUN -->|"filters by DisplayContent"| STO
RTH --> RC1
RTH --> RC2
STO --> RC1
STO --> RC2
RFS --> RV
GT --> RFS
Server-side (RecentTasks.java):
mTasksis a singleArrayList<Task>ordered by recency — all displays combinedgetRecentTasks(maxNum, flags, userId)has no displayId parameter — returns tasks from all displays- Each
Taskcarries its display viagetDisplayId()
Display-specific filtering (RunningTasks.java):
getTasks()accepts aWindowContainerroot — eitherRootWindowContainer(all displays) or a specificDisplayContent(one display)RootWindowContainer.getRunningTasks(maxNum, list, flags, callingUid, profileIds, displayId)extracts the specificDisplayContentwhendisplayId != INVALID_DISPLAY
Shell-side (RecentsTransitionHandler.java):
- Maintains multiple
RecentsControllerinstances — one per active recents animation per display - Each controller tracks
mDisplayIdand filters tasks viamShellTaskOrganizer.getRunningTasks(displayId) findControllerForDisplay(displayId)searches the active controllers list
Launcher3-side (RecentsView.java, RecentsFilterState.java):
- When
enableOverviewOnConnectedDisplays()is true, each display gets its ownRecentsWindowManagerinstance RecentsFilterState.getFilter(packageName, displayId)creates a predicate that includesgroupTask.matchesDisplayId(displayId)— only showing tasks from the current displayGroupTaskcarriesdisplayIdfromTask.TaskKey.displayIdRecentsView.onTaskDisplayChanged(taskId, newDisplayId)dismisses tasks that move to a different display
47.4 Display-Aware Recents Routing
OverviewComponentObserver routes recents to the correct per-display container:
getContainerInterface(displayId):
if enableOverviewOnConnectedDisplays() && displayId != DEFAULT_DISPLAY:
return mRecentsWindowManagerRepository.get(displayId)
else:
return mDefaultDisplayContainerInterface
getHomeIntent(displayId):
if displayId == DEFAULT_DISPLAY:
return mCurrentPrimaryHomeIntent // QuickstepLauncher
else:
return getSecondaryHomeIntent() // SecondaryDisplayLauncher
47.5 Cross-Display Task Movement
CrossDisplayMoveTransition handles task movement between displays:
- Takes a screenshot on the source display
- Shows the task surface on the destination display
- Manages display-specific animation roots via
CrossDisplayMoveTransitionInfoandCrossDisplayMoveAnimator
Reference files:
packages/apps/Launcher3/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java— Secondary display home activitypackages/apps/Launcher3/src/com/android/launcher3/secondarydisplay/SecondaryDisplayDelegate.java— Display-specific delegate basepackages/apps/Launcher3/quickstep/src/com/android/launcher3/secondarydisplay/SecondaryDisplayQuickstepDelegateImpl.java— Quickstep delegate for secondary displayspackages/apps/Launcher3/quickstep/src/com/android/launcher3/taskbar/TaskbarManagerImpl.java— Per-display taskbar management (Map)packages/apps/Launcher3/quickstep/src/com/android/quickstep/RecentsFilterState.java— Display-aware task filteringpackages/apps/Launcher3/quickstep/src/com/android/quickstep/views/RecentsView.java— Display-aware recents viewpackages/apps/Launcher3/quickstep/src/com/android/quickstep/util/GroupTask.kt— Per-task displayId trackingpackages/apps/Launcher3/quickstep/src/com/android/quickstep/OverviewComponentObserver.java— Per-display container routingpackages/apps/Launcher3/quickstep/src/com/android/quickstep/window/RecentsWindowManager.kt— Per-display recents windowpackages/apps/Launcher3/quickstep/src/com/android/quickstep/util/CrossDisplayMoveTransition.java— Cross-display task animationpackages/apps/Launcher3/src/com/android/launcher3/InvariantDeviceProfile.java— Secondary display DeviceProfile creationpackages/apps/Launcher3/AndroidManifest-common.xml— SECONDARY_HOME intent filter registrationframeworks/base/services/core/java/com/android/server/wm/RecentTasks.java— Global recent tasks list (no per-display API)frameworks/base/services/core/java/com/android/server/wm/RunningTasks.java— Per-display task filteringframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java— Multi-display recents animation controllersframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java— getRunningTasks(displayId) filtering
48. Multi-Window and Multi-Display Test Infrastructure
AOSP verifies multi-window and multi-display implementations through a three-tier testing strategy: CTS integration tests for functional compliance, WM unit tests for internal correctness, and Shell component tests for UI behavior.
48.1 CTS Multi-Display Tests
The primary multi-display test suite lives in cts/tests/framework/base/windowmanager/src/android/server/wm/multidisplay/:
| Test Class | Methods | Coverage |
|---|---|---|
MultiDisplayActivityLaunchTests |
~30 | Activity launch ordering, display selection, cross-display operations, launch flags, PendingIntent behavior |
MultiDisplaySecurityTests |
~20 | Permission enforcement, display access control, broadcast receiver security boundaries |
MultiDisplayPolicyTests |
~25 | IME placement, rotation policy, configuration overrides, multi-user display assignment |
MultiDisplaySystemDecorationTests |
~15 | Status/nav bar rendering on secondary displays, cutout handling |
MultiDisplayClientTests |
~5 | Activity displayId tracking, configuration change handling, activity relocation |
MultiDisplayPrivateDisplayTests |
~3 | Private display isolation, content security |
Additional display tests in src/android/server/wm/display/:
| Test Class | Coverage |
|---|---|
FreeformWindowingModeTests |
Freeform window creation on virtual displays, bounds management |
DisplayTests |
Virtual display creation, display configuration, IME policy, activity targeting |
PresentationTest |
Presentation API on secondary displays |
WindowContextTests |
Window context behavior across displays |
48.2 CTS Multi-Window Tests
Multi-window tests in src/android/server/wm/window/:
| Test Class | Coverage |
|---|---|
MultiWindowTests |
Split-screen, freeform, windowing modes, resize, task stacking |
MultiWindowAvailabilityTests |
Device support detection, minimum screen size validation |
Activity embedding multi-display tests in jetpack/src/android/server/wm/jetpack/embedding/:
| Test Class | Coverage |
|---|---|
MultiDisplayActivityEmbeddingLaunchTests |
Embedded activity launch on secondary displays, split rules |
MultiDisplayActivityEmbeddingLifecycleTests |
Lifecycle callbacks, focus changes across display boundaries |
MultiDisplayActivityEmbeddingBoundsTests |
Split bounds calculation, placeholder sizing |
MultiDisplayActivityEmbeddingPlaceholderTests |
Placeholder rules on multi-display |
IME and keyguard multi-display tests:
| Test Class | Coverage |
|---|---|
MultiDisplayImeTests |
IME visibility, display assignment, input focus |
MultiDisplaySecurityImeTests |
IME security boundaries across displays |
MultiDisplayKeyguardTests |
Keyguard visibility, unlock behavior per display |
MultiDisplayLockedKeyguardTests |
Locked state handling across displays |
48.3 Virtual Display Test Infrastructure
Tests create virtual displays using two mechanisms:
graph TB
subgraph "Test Infrastructure"
VDS["VirtualDisplaySession<br/>(ActivityManagerTestBase)"]
ODS["OverlayDisplayDevicesSession<br/>(simulated displays)"]
VDH["VirtualDisplayHelper<br/>(real VirtualDisplay)"]
end
subgraph "Display Creation Path"
DM["DisplayManager"]
ODA["OverlayDisplayAdapter<br/>(Settings.Global)"]
VDA["VirtualDisplayAdapter"]
end
VDS -->|"setSimulateDisplay(true)"| ODS
VDS -->|"setSimulateDisplay(false)"| VDH
ODS -->|"OVERLAY_DISPLAY_DEVICES setting"| ODA
VDH -->|"createVirtualDisplay()"| VDA
ODA --> DM
VDA --> DM
VirtualDisplaySession (main test API):
Configuration:
setDensityDpi(222) // Custom density
setSimulateDisplay(true/false) // Overlay vs real VirtualDisplay
setSimulationDisplaySize(1024, 768) // Display dimensions
setDisplayImePolicy(LOCAL/FALLBACK) // IME placement policy
setPublicDisplay(true/false) // Accessibility
setShowSystemDecorations(true/false) // Status/nav bars
setOwnContentOnly(true/false) // Content isolation
setSupportsTouch(true/false) // Touch support
Creation:
createDisplay() // Single display
createDisplays(count) // Multiple displays
resizeDisplay() // Resize (virtual only)
OverlayDisplayDevicesSession (simulated displays):
- Uses
Settings.Global.OVERLAY_DISPLAY_DEVICESfor lightweight simulated displays - Format:
WIDTHxHEIGHT/DENSITY[,flags](semicolons for multiple) - Flags:
own_content_only,should_show_system_decorations,fixed_content_mode
VirtualDisplayHelper (real displays):
- Creates real
VirtualDisplayviaDisplayManager.createVirtualDisplay() - Uses
ImageReaderfor surface backing - Default: 800x480 @ 160 dpi
48.4 WM Unit Test Infrastructure
Unit tests in frameworks/base/services/tests/wmtests/ use mock displays:
TestDisplayContent (builder pattern for mock displays):
new TestDisplayContent.Builder(service, width, height)
.setSystemDecorations(true) // Nav/status bar setup
.setCanRotate(true) // Rotation support
.setWindowingMode(FULLSCREEN) // Windowing mode
.setDensityDpi(300) // Display density
.setStatusBarHeight(25) // Inset configuration
.setCanHostTasks(true) // Task hosting
.build()
Features:
- Mockito spy/mock setup with
DisplayRotation,InputMonitor,InsetsPolicystubbing - Automatic registration in
RootWindowContainer - Unique ID generation for multi-display test isolation
WindowTestsBase (base class):
newDisplay(width, height)— creates test displaycreateWindow(parent, type)— creates window on specific displaycreateTask(displayContent, windowingMode)— creates task on displaycreateActivityRecord(displayContent)— creates activity record
Key unit test classes:
| Test Class | Coverage |
|---|---|
DisplayContentTests |
Display state management, task organization, configuration propagation |
RunningTasksTest |
Per-display task filtering (verifies display0 gets even tasks, display1 gets odd tasks) |
DisplayContentDeferredUpdateTests |
Deferred layout and focus operations |
48.5 Shell Component Tests
Shell tests in frameworks/base/libs/WindowManager/Shell/tests/:
| Test Class | Coverage |
|---|---|
SplitMultiDisplayHelperTests |
Split-screen behavior across multiple displays |
MultiDisplayDragMoveBoundsCalculatorTest |
Window drag bounds calculation across displays |
MultiDisplayDragMoveIndicatorControllerTest |
Drag indicator positioning on multi-display |
MultiDisplayVeiledResizeTaskPositionerTest |
Resize affordance positioning per display |
RecentsTransitionHandlerTest |
Multi-display recents animation controllers |
E2E tests:
| Test Class | Coverage |
|---|---|
DragAppWindowMultiWindow |
Drag operations in multi-window mode |
ResizeAppCornerMultiWindow |
Corner-based window resizing |
DragAppWindowMultiWindowAndPipTest |
Drag with PiP enabled |
48.6 Test Assertion Patterns
Display state queries:
mWmState.computeState(activityName, displayId) // Query activity state on display
mWmState.assertResumedActivities(mapping) // Multiple display resume check
mWmState.assertFrontStackOnDisplay(...) // Stack position per display
mWmState.waitAndAssertImeWindowShownOnDisplay(displayId) // IME on display
Multi-display task isolation:
graph LR
subgraph "RunningTasksTest: per-display filtering"
D0["display0"] -->|"getTasks()"| R0["tasks 0, 2, 4, 6, 8"]
D1["display1"] -->|"getTasks()"| R1["tasks 1, 3, 5, 7, 9"]
ROOT["rootContainer"] -->|"getTasks()"| RALL["all 10 tasks"]
end
Security boundary testing:
// Verify permission denial on private displays
createManagedVirtualDisplaySession().setPublicDisplay(false).createDisplay()
expectException(() -> launchActivityOnDisplay(activity, privateDisplay.mId))
48.7 Test Coverage Summary
graph LR
subgraph "CTS (200+ tests)"
CTS_MD["Multi-Display<br/>~100 tests"]
CTS_MW["Multi-Window<br/>~25 tests"]
CTS_EMB["Activity Embedding<br/>~50 tests"]
CTS_IME["IME Multi-Display<br/>~12 tests"]
CTS_KG["Keyguard Multi-Display<br/>~10 tests"]
end
subgraph "Unit Tests"
UT_DC["DisplayContentTests"]
UT_RT["RunningTasksTest"]
UT_TD["TestDisplayContent<br/>(Builder)"]
end
subgraph "Shell Tests"
ST_SPLIT["SplitMultiDisplay"]
ST_DRAG["DragMultiDisplay"]
ST_REC["RecentsTransition"]
end
CTS_MD --- UT_DC
CTS_MW --- ST_SPLIT
ST_REC --- UT_RT
Reference files:
cts/tests/framework/base/windowmanager/src/android/server/wm/multidisplay/MultiDisplayActivityLaunchTests.java— Primary multi-display CTS testscts/tests/framework/base/windowmanager/src/android/server/wm/multidisplay/MultiDisplaySecurityTests.java— Security boundary testscts/tests/framework/base/windowmanager/src/android/server/wm/multidisplay/MultiDisplayPolicyTests.java— Display policy testscts/tests/framework/base/windowmanager/src/android/server/wm/multidisplay/MultiDisplaySystemDecorationTests.java— System decoration testscts/tests/framework/base/windowmanager/src/android/server/wm/window/MultiWindowTests.java— Multi-window CTS testscts/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java— VirtualDisplaySession infrastructurects/tests/framework/base/windowmanager/util/src/android/server/wm/MultiDisplayTestBase.java— Multi-display test base classcts/tests/framework/base/windowmanager/util/src/android/server/wm/VirtualDisplayHelper.java— Real VirtualDisplay helpercts/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/embedding/MultiDisplayTestHelper.java— Activity embedding multi-display helperframeworks/base/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java— Mock display builder for unit testsframeworks/base/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java— WM unit test base classframeworks/base/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java— Per-display task filtering verificationframeworks/base/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiDisplayTestUtil.kt— Shell multi-display test utility
49. Caption Bar Architecture
The caption bar (title bar) is the window decoration that provides drag-to-move, minimize, maximize, and close controls in freeform and desktop windowing modes. Android has two distinct caption bar implementations: the legacy DecorView-based approach and the modern Shell-based approach using separate SurfaceControl layers.
49.1 Window Visual Component Ownership
A visible window in freeform/desktop mode is composed of multiple visual elements managed by different subsystems:
graph TB
subgraph "Window Visual Components"
subgraph "SurfaceFlinger (Native)"
SHADOW[Window Shadow<br/>SurfaceControl.setBoxShadowSettings<br/>or setShadowRadius]
BORDER[Window Border<br/>SurfaceControl.setBorderSettings]
CORNER[Rounded Corners<br/>SurfaceControl.setCornerRadius]
end
subgraph "WM Shell (SystemUI Process)"
CAPTION[Caption Bar Surface<br/>SurfaceControlViewHost<br/>buttons: back, minimize,<br/>maximize, close]
INPUT_SINK[Input Sink Surface<br/>resize edge/corner detection]
RESIZE_VEIL[Resize Veil<br/>overlay during resize]
ADDITIONAL[Additional Windows<br/>menus, context popups]
end
subgraph "Application Process"
DECOR[DecorView<br/>app content root]
CONTENT[Content View Hierarchy<br/>Activity layout]
end
end
subgraph "SurfaceControl Hierarchy"
TASK_SC[Task Surface<br/>task leash from WMS]
DECOR_CONTAINER[Decoration Container<br/>setContainerLayer, child of task]
CAPTION_SC[Caption SurfaceControl<br/>Z-order: -1]
SINK_SC[Input Sink SurfaceControl<br/>Z-order: -2]
APP_SC[App Buffer Surface<br/>main content buffer]
TASK_SC --> DECOR_CONTAINER
TASK_SC --> APP_SC
DECOR_CONTAINER --> CAPTION_SC
DECOR_CONTAINER --> SINK_SC
end
Component ownership summary:
| Component | Owner | Rendering Mechanism | Purpose |
|---|---|---|---|
| Shadow | SurfaceFlinger | setBoxShadowSettings() on task surface |
Window elevation visual |
| Border | SurfaceFlinger | setBorderSettings() on task surface |
Window outline |
| Rounded corners | SurfaceFlinger | setCornerRadius() on task surface |
Corner clipping |
| Caption bar | WM Shell | SurfaceControlViewHost in decoration container |
Title + window controls |
| Input sink | WM Shell | Transparent input surface | Resize edge detection |
| Resize veil | WM Shell | Color layer + icon surface | Visual feedback during resize |
| Content area | App process | Standard View hierarchy via DecorView | App UI |
| Inset calculation | WM Shell → WMS | WindowContainerTransaction.addInsetsSource() |
Content layout offset |
49.2 Legacy Caption Bar — DecorView Integration
The legacy caption bar is rendered within the app’s own DecorView (frameworks/base/core/java/com/android/internal/policy/DecorView.java), making it part of the app’s view hierarchy:
graph TB
subgraph "App Window (Single Surface)"
DV[DecorView<br/>FrameLayout]
DV --> CAPTION_LEGACY[DecorCaptionView<br/>caption_decor_title layout<br/>back, minimize, maximize, close]
DV --> CONTENT_ROOT[Content Root<br/>application layout]
end
subgraph "WindowManager"
WMS[WindowManagerService]
WMS -->|"caption inset consumed<br/>by DecorView"| DV
end
Key characteristics:
- Caption is part of the app’s view hierarchy — rendered in the same buffer as app content
- Caption insets consumed via
WindowInsets.Type.captionBar()(WindowInsets.java:2043) DecorViewlines 1255-1272: HandlesAPPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUNDflag for apps that want to draw behind the caption- Feature flag
customizableWindowHeaders()(line 1263) controls whether apps can customize caption appearance ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTIONforces caption insets to be consumed even when the app requests transparent caption- Content root margin adjusted by caption height (lines 1277-1287)
Limitations:
- No separate surface — caption rendering impacts app rendering performance
- Limited to app window process — cannot provide system-level controls
- No built-in drag-to-move or drag-to-resize support
- Caption is part of the app’s screenshots and thumbnails
49.3 Modern Caption Bar — Shell-Based Architecture
The modern approach renders the caption bar on a separate SurfaceControl layer managed by WM Shell, completely outside the app’s view hierarchy:
graph TB
subgraph "SurfaceControl Tree"
TASK[Task Surface<br/>leash from ShellTaskOrganizer]
subgraph "Decoration Container (child of task)"
DC[mDecorationContainerSurface<br/>setContainerLayer<br/>Z: TASK_CHILD_LAYER_WINDOW_DECORATIONS]
CAPTION_SURFACE[Caption Surface<br/>SurfaceControlViewHost output<br/>Z: CAPTION_LAYER_Z_ORDER = -1]
INPUT_SURFACE[Input Sink Surface<br/>TYPE_INPUT_CONSUMER<br/>Z: INPUT_SINK_Z_ORDER = -2]
end
APP_SURFACE[App Content Surface<br/>standard app buffer]
end
subgraph "Shell Process (SystemUI)"
WD[WindowDecoration<br/>abstract base class]
CWD[CaptionWindowDecoration<br/>or DesktopModeWindowDecoration]
VIEWHOST[WindowDecorViewHost<br/>SurfaceControlViewHost wrapper]
WDL[WindowDecorLinearLayout<br/>caption buttons view]
end
WD --> CWD
CWD --> VIEWHOST
VIEWHOST --> CAPTION_SURFACE
VIEWHOST -.->|"inflates"| WDL
TASK --> DC
TASK --> APP_SURFACE
DC --> CAPTION_SURFACE
DC --> INPUT_SURFACE
49.4 WindowDecoration Class Hierarchy
classDiagram
class WindowDecoration~T~ {
<<abstract>>
#mTaskSurface: SurfaceControl
#mDecorationContainerSurface: SurfaceControl
#mViewHost: WindowDecorViewHost
#mIsCaptionVisible: boolean
+relayout(taskInfo, startT, finishT, wct): void
+close(): void
#getCaptionHeight(windowingMode): int*
#getCaptionViewId(): int*
-updateDecorationContainerSurface(): void
-updateCaptionInsets(wct): void
-inflateLayout(context, layoutResId): T
}
class CaptionWindowDecoration {
-mOnCaptionTouchListener: OnTouchListener
-mOnCaptionButtonClickListener: OnClickListener
+relayout(taskInfo): void
+setCaptionListeners(click, touch): void
+setCaptionColor(statusBarColor): void
-setupRootView(): void
-calculateValidDragArea(): Rect
}
class DesktopModeWindowDecoration {
-mAppHeaderController: AppHeaderController
-mAppHandleController: AppHandleController
+relayout(taskInfo): void
-updateAppHeaderMenu(): void
}
class WindowDecorLinearLayout {
-mIsTaskFocused: boolean
+setTaskFocusState(focused): void
#onCreateDrawableState(extraSpace): int[]
}
class WindowDecorViewModel {
<<interface>>
+onTaskOpening(taskInfo, leash, startT, finishT)
+onTaskInfoChanged(taskInfo)
+onTaskChanging(taskInfo, leash, startT, finishT)
+onTaskClosing(taskInfo, startT, finishT)
+onTaskVanished(taskInfo)
}
class CaptionWindowDecorViewModel {
-mWindowDecorByTaskId: SparseArray
+onTaskOpening(): void
+onTaskInfoChanged(): void
}
WindowDecoration <|-- CaptionWindowDecoration
WindowDecoration <|-- DesktopModeWindowDecoration
WindowDecorViewModel <|.. CaptionWindowDecorViewModel
CaptionWindowDecoration ..> WindowDecorLinearLayout: inflates
DesktopModeWindowDecoration ..> WindowDecorLinearLayout: inflates
WindowDecoration (frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java, ~937 lines) is the abstract base providing:
- Surface management: Creates and maintains
mDecorationContainerSurfaceas a container layer child of the task surface (lines 458-476) - View hosting: Uses
WindowDecorViewHostto render caption views viaSurfaceControlViewHostin aWindowlessWindowManager— no full WMS overhead - Inset registration: Registers caption bar as
WindowInsets.Type.captionBar()inset source viaWindowDecorationInsets(lines 496-526) - Snapshot exclusion: Caption surface excluded from task screenshots via
setExcludeLayersFromTaskSnapshot()(lines 486-493) - Trace points: Performance monitoring at key stages (
WindowDecoration#relayout,#relayout-inflateIfNeeded,#relayout-updateSurfacesAndInsets,#relayout-updateViewHost)
49.5 Caption Layout Variants
Two main caption layouts exist for different windowing modes:
Freeform caption (caption_window_decor.xml):
WindowDecorLinearLayout [caption background]
├── Button [back_button]
├── Space [flexible spacer, weight=1]
├── Button [minimize_window]
├── Button [maximize_window]
└── Button [close_window]
Desktop mode app header (desktop_mode_app_header.xml):
WindowDecorLinearLayout [no background, transparent]
├── LinearLayout [open_menu_button - app chip]
│ ├── ImageView [application_icon]
│ ├── TextView [application_name]
│ ├── ImageView [expand_menu_error]
│ └── ImageButton [expand_menu_button - dropdown arrow]
├── View [caption_handle - drag area, weight=1]
├── ImageButton [minimize_window]
├── MaximizeButtonView [maximize_button_view]
└── ImageButton [close_window]
The desktop mode header adds app identity (icon + name), a menu dropdown, and uses a custom MaximizeButtonView for snap-layout previews.
49.6 Caption Inset Communication
The Shell communicates caption dimensions to the app’s layout system through the insets framework:
sequenceDiagram
participant SHELL as WMShell WindowDecoration
participant WDI as WindowDecorationInsets
participant WCT as WindowContainerTransaction
participant WMS as WindowManagerService
participant IS as InsetsState
participant APP as App ViewRootImpl
SHELL->>SHELL: relayout() → getCaptionHeight()
SHELL->>WDI: update(wct, captionFrame, boundingRects)
WDI->>WCT: addInsetsSource(captionBar, frame)
WDI->>WCT: addInsetsSource(mandatorySystemGestures, frame)
WCT->>WMS: apply transaction
WMS->>IS: update InsetsState with<br/>caption source
IS->>APP: dispatchInsetsChanged()
APP->>APP: View.onApplyWindowInsets()
Note over APP: Content offset by<br/>caption height
WindowDecorationInsets (frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorationInsets.kt:31-150):
- Registers two inset sources per caption:
WindowInsets.Type.captionBar()— pushes app content below the captionWindowInsets.Type.mandatorySystemGestures()— prevents gesture conflicts with caption buttons
- Supports two frame modes:
Frame.Absolute: Fixed rectangle coordinates (legacy)Frame.Relative: Height-based, adapts to container size (modern, whenFlags.relativeInsets()enabled)
- Bounding rects: Fine-grained occlusion areas specifying non-customizable button regions (back button area, right-side buttons area)
- App bounds exclusion (line 128-130): Optionally excludes caption from the app’s layout bounds
49.7 Drag-to-Move and Drag-to-Resize
The caption bar provides the primary interaction point for moving and resizing freeform windows:
flowchart TD
TOUCH[Touch/Mouse Event] --> REGION{Event location?}
REGION -->|"On caption bar"| DET[DragDetector<br/>slop threshold check]
REGION -->|"On resize edge/corner"| DRIL[DragResizeInputListener<br/>TaskResizeInputEventReceiver]
DET -->|"Drag detected"| DPC[DragPositioningCallback]
DRIL -->|"Resize detected"| CTRL[calculateCtrlType<br/>LEFT/RIGHT/TOP/BOTTOM]
CTRL --> DPC
DPC --> START[onDragPositioningStart<br/>record initial bounds]
START --> MOVE[onDragPositioningMove<br/>update position/size]
MOVE --> END_DRAG[onDragPositioningEnd<br/>finalize bounds]
MOVE -->|"Move (CTRL_TYPE_UNDEFINED)"| REPOSITION[setPositionOnDrag<br/>direct SurfaceControl.Transaction<br/>t.setPosition, t.apply]
MOVE -->|"Resize"| RESIZE_MODE{Resize strategy?}
RESIZE_MODE -->|"Fluid"| FLUID[FluidResizeTaskPositioner<br/>setBounds via WCT<br/>real-time content resize]
RESIZE_MODE -->|"Veiled"| VEIL[VeiledResizeTaskPositioner<br/>show ResizeVeil overlay<br/>apply final bounds on end]
END_DRAG --> TRANSIT[Shell Transition<br/>TRANSIT_CHANGE<br/>finalize with animation]
DragDetector (DragDetector.java, 223 lines): Filters touch events for slop threshold before initiating drag. Supports configurable hold-to-drag timing and filters out touchpad-synthesized gestures.
DragPositioningCallback (DragPositioningCallback.java): Interface with control type flags:
CTRL_TYPE_LEFT = 1,CTRL_TYPE_RIGHT = 2,CTRL_TYPE_TOP = 4,CTRL_TYPE_BOTTOM = 8CTRL_TYPE_UNDEFINED= drag-to-move (no resize direction)- Input method types:
TOUCH,STYLUS,MOUSE,TOUCHPAD
DragResizeWindowGeometry (DragResizeWindowGeometry.java): Computes resize regions:
mResizeHandleEdgeOutset— handle size outside the window boundsmResizeHandleEdgeInset— handle size inside the window boundsmLargeTaskCorners— larger hit targets for touch input (~48dp)mFineTaskCorners— smaller hit targets for stylus/cursor (~24dp)
FluidResizeTaskPositioner (FluidResizeTaskPositioner.java, 238 lines): Real-time resize — applies setBounds() via WindowContainerTransaction on each drag move, causing the app to relayout continuously. Sets FLAG_DRAG_RESIZING on the window token during the operation.
ResizeVeil (ResizeVeil.kt, 492 lines): Alternative resize strategy showing an overlay during resize:
- Container surface with background color layer and app icon surface
- Fade-in: 50ms, fade-out: 200ms
- Icon loaded asynchronously via coroutine
- Final bounds applied as Shell transition (
TRANSIT_CHANGE) on drag end
49.8 Transparent and Customizable Caption Bars
Apps can customize the caption bar appearance using appearance flags:
flowchart TD
APP[App sets<br/>APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND] --> CHECK{Shell checks<br/>transparency flag}
CHECK -->|"Flag set"| TRANSPARENT[Transparent caption mode]
TRANSPARENT --> SPY[Caption surface gets<br/>INPUT_FEATURE_SPY flag]
SPY --> PASSTHROUGH[Touch events pass through<br/>to customizable regions]
TRANSPARENT --> CUSTOM_REGION[CaptionRegionHelper calculates<br/>customizable vs non-customizable areas]
CUSTOM_REGION --> BUTTONS[Non-customizable: button areas<br/>back, minimize, maximize, close]
CUSTOM_REGION --> DRAGGABLE[Customizable: remaining caption area<br/>app draws its own content]
CHECK -->|"Flag not set"| OPAQUE[Opaque caption mode]
OPAQUE --> SHELL_RENDER[Shell renders full caption<br/>with theme-based colors]
SHELL_RENDER --> COLOR[setCaptionColor from<br/>app's status bar color]
CaptionRegionHelper (frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/CaptionRegionHelper.kt):
calculateCustomizableRegion()— determines which caption areas apps can draw intocalculateLimitedTouchableRegion()— restricts Shell’s touchable region to non-customized buttons only- When transparent, the caption surface uses
INPUT_FEATURE_SPYto observe touches without consuming them, allowing the app to handle them
Color theming (CaptionWindowDecoration.java:389-416): The caption automatically adapts its color scheme based on the app’s status bar color, computing button tint from luminance analysis.
49.9 Performance Optimization — View Host Pooling
Creating SurfaceControlViewHost instances is expensive (involves surface allocation, window manager setup, input channel creation). The Shell uses a pooling mechanism to amortize this cost:
sequenceDiagram
participant INIT as ShellInit
participant POOL as PooledViewHostSupplier
participant CACHE as SynchronizedPool per-display
participant WD as WindowDecoration
Note over INIT: Shell initialization
INIT->>POOL: onShellInit()
POOL->>POOL: preWarmViewHosts(preWarmSize)
loop preWarmSize times
POOL->>CACHE: create ReusableWindowDecorViewHost
POOL->>CACHE: warmUp() + release to pool
end
Note over WD: Task appearing
WD->>POOL: acquire(context, display)
POOL->>CACHE: pool.acquire()
alt Pooled host available
CACHE-->>POOL: recycled ViewHost
else Pool empty
POOL->>POOL: newInstance() [expensive]
end
POOL-->>WD: WindowDecorViewHost
Note over WD: Task vanishing
WD->>POOL: release(viewHost, transaction)
POOL->>CACHE: pool.release(viewHost)
Note over CACHE: Host returned to pool<br/>for next task
PooledWindowDecorViewHostSupplier (frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/PooledWindowDecorViewHostSupplier.kt, 162 lines):
- Pre-warming (lines 103-120): On Shell init, pre-creates
preWarmSizeview hosts and puts them in the pool. Each iswarmUp()-ed — allocating internal surfaces and input channels ahead of time - Per-display pools (lines 56-57, 73-100): When
ENABLE_PER_DISPLAY_WINDOW_DECOR_VIEW_HOST_POOLflag is enabled, maintains separate pools per display. Pools are created ononDisplayAdded()and cleaned up ononDisplayRemoved() - Desktop mode aware (line 82): Pre-warming only occurs on displays that support desktop mode (
isDesktopModeSupportedOnDisplay()) - Pool sizing: Configurable
maxPoolSizeandpreWarmSizevia constructor injection - Reusable hosts (
ReusableWindowDecorViewHost): SupportswarmUp()for pre-initialization andreset()for state cleanup between uses - Async pre-warming: Uses
mainScope.launchcoroutine to avoid blocking Shell initialization
Additional performance optimizations in WindowDecoration:
| Optimization | Mechanism | Benefit |
|---|---|---|
| Async view updates | updateViewAsync() via coroutine |
Main thread not blocked by view draw |
| Transaction batching | applyStartTransactionOnDraw |
Surface crop synced with buffer draw — no flicker |
| Lazy display lookup | On-demand with listener fallback | Avoids unnecessary IPC |
| Input channel on bg thread | Background executor for DragResizeInputListener |
Main thread not blocked |
| Configuration context caching | Only recreate on density/theme/night mode change (lines 426-449) | Avoid repeated view inflation |
| Snapshot exclusion | setExcludeLayersFromTaskSnapshot() on bg thread |
Caption not in recents thumbnails |
49.10 Shell ↔ WindowManager Integration
The caption bar lifecycle is coordinated between Shell’s WindowDecorViewModel and WMS via ShellTaskOrganizer:
sequenceDiagram
participant WMS as WindowManagerService
participant STO as ShellTaskOrganizer
participant VM as CaptionWindowDecorViewModel
participant CWD as CaptionWindowDecoration
participant SF as SurfaceFlinger
WMS->>STO: onTaskAppeared(taskInfo, leash)
STO->>VM: onTaskOpening(taskInfo, leash, startT, finishT)
VM->>CWD: new CaptionWindowDecoration(taskInfo)
CWD->>CWD: relayout(taskInfo)
Note over CWD: Create decoration container<br/>Create caption surface<br/>Inflate caption view<br/>Register caption insets<br/>Setup drag listeners
CWD->>SF: SurfaceControl transactions<br/>(reparent, setLayer, setCrop)
WMS->>STO: onTaskInfoChanged(taskInfo)
STO->>VM: onTaskInfoChanged(taskInfo)
VM->>CWD: relayout(taskInfo)
Note over CWD: Update bounds, visibility,<br/>caption color, button state
WMS->>STO: onTaskVanished(taskInfo)
STO->>VM: onTaskVanished(taskInfo)
VM->>CWD: close()
Note over CWD: Remove insets<br/>Release view host to pool<br/>Remove surfaces
Reference files:
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java— Abstract base class (~937 lines, surface management, insets, relayout)frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java— Freeform caption implementation (473 lines)frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java— Desktop mode decoration with app headerframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorLinearLayout.java— Caption root view with task focus state (72 lines)frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorationInsets.kt— Caption inset registration (lines 31-150)frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/PooledWindowDecorViewHostSupplier.kt— View host pooling and pre-warming (162 lines)frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHost.kt— View hosting interfaceframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/SurfaceControlViewHostAdapter.kt— Low-level SCVH wrapperframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/CaptionRegionHelper.kt— Customizable region calculationframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/caption/CaptionController.kt— Abstract caption controllerframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/caption/AppHeaderController.kt— Desktop mode app headerframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java— Touch slop filtering (223 lines)frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java— Drag interface with control typesframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java— Resize region computationframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java— Real-time resize (238 lines)frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.kt— Resize overlay (492 lines)frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java— Caption lifecycle via ShellTaskOrganizerframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/BoxShadowHelper.java— Shadow settings helperframeworks/base/libs/WindowManager/Shell/res/layout/caption_window_decor.xml— Freeform caption layoutframeworks/base/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml— Desktop mode header layoutframeworks/base/core/java/com/android/internal/policy/DecorView.java— Legacy caption inset consumption (lines 1255-1287)frameworks/base/core/java/android/view/WindowInsets.java—CAPTION_BARtype (line 1895)frameworks/base/core/java/android/view/InsetsSource.java— Caption inset source flags
50. Drag and Drop Architecture
Android’s drag and drop system spans three architectural layers: the View API (app-side initiation), WindowManagerService (system-wide event routing and security), and WM Shell (policy-driven drop target handling). The architecture supports same-window, cross-window, cross-app, and cross-display drag operations with a permission model based on URI grants and drag flags. This section covers data drag-and-drop; for window drag-to-move/resize mechanics, see §49.7. For input dispatch fundamentals, see §44. For split-screen drop targets in context, see §26-§28. For cross-display topology, see §34-§35.
50.1 Architecture Overview
graph TB
subgraph "Application Layer"
VIEW["View.startDragAndDrop()"]
DSB["DragShadowBuilder"]
CD["ClipData + ClipDescription"]
DE["DragEvent callbacks"]
end
subgraph "Framework Layer - Session"
SESSION["Session.performDrag()<br/>Binder security boundary"]
end
subgraph "WMS Layer - DragDropController + DragState"
DDC["DragDropController<br/>673 lines"]
DS["DragState<br/>948 lines - per-drag lifecycle"]
DIER["DragInputEventReceiver<br/>115 lines - motion tracking"]
II["InputInterceptor<br/>Input channel + window handle"]
end
subgraph "Shell Layer - Global Drag Handling"
GDL["GlobalDragListener<br/>131 lines"]
SDDC["DragAndDropController<br/>646 lines - Shell policy"]
DL["DragLayout<br/>845 lines - drop zone UI"]
SDP["SplitDragPolicy<br/>672 lines - target calculation"]
DSS["DragSession<br/>143 lines - per-drag state"]
end
VIEW --> DSB
VIEW --> CD
VIEW --> SESSION
SESSION --> DDC
DDC --> DS
DS --> II
II --> DIER
DS -->|"ACTION_DRAG_*"| DE
DDC -->|"GlobalDragListener"| GDL
GDL --> SDDC
SDDC --> DL
DL --> SDP
SDDC --> DSS
Source files (paths relative to frameworks/base/):
core/java/android/view/View.java—startDragAndDrop()(lines 29567-29732, ~34,918 lines total)core/java/android/view/DragEvent.java— Event class with 6 action types (664 lines)services/core/.../server/wm/Session.java—performDrag()binder entry (lines 341-357, 1,021 lines total)services/core/.../server/wm/DragDropController.java— WMS drag controller (673 lines)services/core/.../server/wm/DragState.java— Per-drag state machine (948 lines)services/core/.../server/wm/DragInputEventReceiver.java— Motion event routing (115 lines)libs/WindowManager/Shell/src/.../draganddrop/DragAndDropController.java— Shell controller (646 lines)libs/WindowManager/Shell/src/.../draganddrop/DragLayout.java— Drop zone visualization (845 lines)libs/WindowManager/Shell/src/.../draganddrop/SplitDragPolicy.java— Drop target policy (672 lines)libs/WindowManager/Shell/src/.../draganddrop/GlobalDragListener.kt— WMS-Shell bridge (131 lines)
50.2 DragEvent Lifecycle
The DragEvent class defines six action types that form the complete drag lifecycle:
| Action | Value | Valid Fields | Purpose |
|---|---|---|---|
ACTION_DRAG_STARTED |
1 | description, localState, x, y | Broadcast to all windows; return true to accept |
ACTION_DRAG_ENTERED |
5 | description, localState | Shadow enters view bounds |
ACTION_DRAG_LOCATION |
2 | description, localState, x, y | Position update within view |
ACTION_DRAG_EXITED |
6 | description, localState | Shadow exits view bounds |
ACTION_DROP |
3 | description, localState, x, y, clipData | User releases; deliver data |
ACTION_DRAG_ENDED |
4 | localState, result | Operation complete; check success |
DragEvent object pooling: MAX_RECYCLED = 10 instances cached via a custom linked-list recycler with gRecyclerLock (lines 175-178). Factory method obtain() (lines 323-361) reuses pooled instances; recycle() (lines 508-534) returns them.
sequenceDiagram
participant App as Source App
participant View as View
participant Session as Session (Binder)
participant DDC as DragDropController
participant DS as DragState
participant AllWin as All Windows
participant Target as Drop Target Window
App->>View: startDragAndDrop(clipData, shadowBuilder, flags)
View->>View: Create SurfaceControl for shadow
View->>View: DragShadowBuilder.onDrawShadow(canvas)
View->>Session: performDrag(window, flags, surface, touchX, touchY, clipData)
Session->>DDC: performDrag(callerPid, callerUid, ...)
DDC->>DS: new DragState(token, surface, flags, data)
DDC->>DS: Register InputInterceptor
DDC->>DDC: Wait for touch focus transfer (5s max)
DDC->>DS: broadcastDragStartedLocked()
DS->>AllWin: ACTION_DRAG_STARTED (clipDescription only)
Note over DS: Motion tracking via DragInputEventReceiver
loop Each pointer move
DS->>Target: ACTION_DRAG_LOCATION (x, y)
DS->>Target: ACTION_DRAG_ENTERED / ACTION_DRAG_EXITED
end
Note over DS: User lifts finger
DS->>DDC: reportDropWindowLock(token, x, y)
DDC->>DS: Create DragEvent with ClipData + permissions
DS->>Target: ACTION_DROP (clipData, permissions)
Target->>DDC: reportDropResult(consumed)
alt Drop consumed
DDC->>DS: endDragLocked(consumed=true)
else Drop not consumed
DDC->>DS: endDragLocked(consumed=false)
DS->>DS: createReturnAnimationLocked()
end
DS->>AllWin: ACTION_DRAG_ENDED (result)
DS->>DS: closeLocked() - cleanup
50.3 Drag Initiation: View.startDragAndDrop()
The View.startDragAndDrop() method (line 29567) handles two distinct paths:
Standard drag path (lines 29635-29720):
- Get shadow metrics from
DragShadowBuilder.onProvideShadowMetrics() - Validate shadow dimensions (non-zero, within limits)
- Create
SurfaceControlfor drag shadow withcallSiteDebugInfo = "View.startDragAndDrop" - Lock canvas, call
DragShadowBuilder.onDrawShadow(canvas)to render - Call
Session.performDrag()with surface, touch coordinates, and ClipData - Return drag token (IBinder) for operation tracking
Accessibility drag path (lines 29601-29634):
- No surface rendering — passes null surface
- Uses extended 60-second timeout (vs standard 5-second)
- Skips animations on completion
- Sets
DRAG_FLAG_ACCESSIBILITY_ACTION
Key drag flags (defined in View.java):
| Flag | Purpose |
|---|---|
DRAG_FLAG_GLOBAL |
Any app can receive the drop |
DRAG_FLAG_GLOBAL_SAME_APPLICATION |
Same UID only |
DRAG_FLAG_GLOBAL_URI_READ |
Grant URI read to receiver |
DRAG_FLAG_GLOBAL_URI_WRITE |
Grant URI write to receiver |
DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION |
Persistable URI grant |
DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION |
Prefix-matching URI grant |
DRAG_FLAG_OPAQUE |
Opaque drag shadow |
DRAG_FLAG_ACCESSIBILITY_ACTION |
Accessibility-initiated drag |
DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION |
Request surface back for animation |
DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START |
Hide source task during drag |
DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG |
Route unhandled to IntentSender |
50.4 WMS DragDropController and DragState
DragDropController (673 lines)
The WMS-level controller manages the system-wide drag lifecycle:
Timeout constants:
DRAG_TIMEOUT_MS= 5,000 ms — standard drop result timeoutA11Y_DRAG_TIMEOUT_DEFAULT_MS= 60,000 ms — accessibility drag timeout
Handler messages:
MSG_DRAG_END_TIMEOUT(0) — Drop result not receivedMSG_TEAR_DOWN_DRAG_AND_DROP_INPUT(1) — Input channel cleanupMSG_ANIMATION_END(2) — Return/cancel animation completeMSG_REMOVE_DRAG_SURFACE_TIMEOUT(3) — Surface cleanupMSG_UNHANDLED_DROP_LISTENER_TIMEOUT(4) — Unhandled drop listener timeout
performDrag() (lines 165-313) — The main entry point:
- Validate via
IDragDropCallback.prePerformDrag()(vendor hook) - Verify no drag already in progress
- Verify calling window is valid
- Get
DisplayContentfrom calling window - Create
DragStatewith unique token - Register input channel via callback (or skip for accessibility)
- Wait for touch focus transfer (up to 5 seconds)
- Show drag surface with alpha, set overlay position
- Return drag token
reportDropResult() (lines 319-381) — Drop completion:
- Validate drop window holds correct token
- Clear drag end timeout
- If not consumed: route to
GlobalDragListenerfor unhandled drop - Determine if cross-window drag occurred
- Call
endDragLocked()with consumption status - Notify global listener of cross-window drops
Multi-display handling:
handleDisplayTopologyChange()(lines 494-504) — cancels active drag when display topology changesENABLE_CONNECTED_DISPLAYS_DNDfeature flag (line 117) — gates cross-display drag support- Registers
DisplayTopology.Listenerwhen flag is enabled
DragState (948 lines)
Manages per-drag state including surface, input, animation, and permission handling.
Key fields:
| Field | Type | Purpose |
|---|---|---|
mToken |
IBinder | Unique drag operation ID |
mSurfaceControl |
SurfaceControl | Drag shadow surface |
mFlags |
int | DRAG_FLAG_* flags |
mData |
ClipData | Transferred payload |
mCurrentDisplayContent |
DisplayContent | Display being dragged on |
mStartDragDisplayContent |
DisplayContent | Display where drag started |
mInputInterceptor |
InputInterceptor | Input event capture |
mNotifiedWindows |
ArrayList | Windows that received DRAG_STARTED |
mDragInProgress |
boolean | Lifecycle tracking |
mUnhandledDropEvent |
DragEvent | Stored for global listener |
InputInterceptor inner class (lines 423-471):
- Creates input channel via
InputManager - Sets up
DragInputEventReceiverfor motion tracking - Creates
InputWindowHandlewithTYPE_DRAGlayout type - Z-order set to
Integer.MAX_VALUEfor highest priority DISPLAY_TOPOLOGY_AWAREconfig for cross-display events- Pauses display rotations during drag
stateDiagram-v2
[*] --> Idle: No active drag
Idle --> Initiated: performDrag() called
Initiated --> InputRegistered: InputInterceptor created
InputRegistered --> FocusTransfer: Waiting for touch focus
FocusTransfer --> Broadcasting: broadcastDragStartedLocked()
Broadcasting --> Tracking: ACTION_DRAG_STARTED sent to all windows
Tracking --> Tracking: ACTION_DRAG_LOCATION - pointer move
Tracking --> DropReported: reportDropWindowLock()
DropReported --> WaitingResult: ACTION_DROP sent - 5s timeout
WaitingResult --> Ending: reportDropResult()
WaitingResult --> Ending: Timeout - MSG_DRAG_END_TIMEOUT
Ending --> Animating: Return animation - drop not consumed
Ending --> Closing: No animation needed
Animating --> Closing: MSG_ANIMATION_END
Closing --> Idle: closeLocked() - cleanup
Tracking --> Cancelling: cancelDragAndDrop()
Cancelling --> Animating: createCancelAnimationLocked()
50.5 ClipData Transfer and Permission Model
The drag and drop system uses a layered permission model to control cross-app data access:
graph TB
subgraph "ClipData Delivery Rules"
LOCAL["Local Drag - same window"]
GLOBAL_SAME["DRAG_FLAG_GLOBAL_SAME_APPLICATION"]
GLOBAL["DRAG_FLAG_GLOBAL"]
INTERCEPT["PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP"]
end
subgraph "Data Received"
FULL["Full ClipData"]
MODIFIED["Modified ClipData - activity extras"]
DESC_ONLY["ClipDescription only - no data"]
end
subgraph "Permission Grants"
URI_R["URI_READ grant"]
URI_W["URI_WRITE grant"]
PERSIST["PERSISTABLE_URI_PERMISSION"]
PREFIX["PREFIX_URI_PERMISSION"]
end
LOCAL -->|"Always"| FULL
GLOBAL_SAME -->|"Same UID"| FULL
INTERCEPT -->|"System windows"| MODIFIED
GLOBAL -->|"Different app"| DESC_ONLY
GLOBAL -->|"On ACTION_DROP"| FULL
GLOBAL --> URI_R
GLOBAL --> URI_W
URI_R --> PERSIST
URI_W --> PERSIST
URI_R --> PREFIX
Drop target validation (isValidDropTarget(), DragState lines 605-640):
- App drag restriction: Application-type drags (MIMETYPE_APPLICATION_*) only to local windows or global-intercepting windows
- Global flag check: Non-global drag limited to originating window
- Same-app check:
DRAG_FLAG_GLOBAL_SAME_APPLICATIONlimited to same UID - Cross-profile check: Honors
DISALLOW_CROSS_PROFILE_COPY_PASTEuser restriction - Global drag interception: Windows with
PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROPcan intercept (requires system privilege or targetSdk N+)
DragAndDropPermissionsHandler (separate file: DragAndDropPermissionsHandler.java, 167 lines — not defined within DragState):
- Grants URI access based on
DRAG_FLAGS_URI_ACCESS=GLOBAL_URI_READ | GLOBAL_URI_WRITE - Validates source/target user IDs for cross-profile scenarios
- Creates
IDragAndDropPermissionsbinder for the drop target
50.6 Shell-Level Drag and Drop Handling
The Shell layer provides policy-driven drop target management, including split-screen drop zones and unhandled drop routing.
DragAndDropController (Shell, 646 lines)
Initialization (onInit(), lines 175-188):
- Registers as
DisplayController.OnDisplaysChangedListener - Sets
GlobalDragListenercallback for WMS bridge - Registers display lifecycle callbacks
Drop target window creation (onDisplayAdded(), lines 236-278):
- Creates
FrameLayoutroot view per display - Window params:
TYPE_APPLICATION_OVERLAYwith:FLAG_NOT_FOCUSABLE | FLAG_HARDWARE_ACCELERATEDPRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP— intercepts all global drag eventsSYSTEM_FLAG_SHOW_FOR_ALL_USERS
- Window initially
INVISIBLE; shown on drag start DragLayoutadded as child for drop zone visualizationDragToBubbleControlleradded on primary display only
Main event handler (onDrag(), lines 338-449):
flowchart TD
START["onDrag(view, event)"]
START --> CHECK_ACTION{"event.getAction()"}
CHECK_ACTION -->|"DRAG_STARTED"| STARTED["canHandleDrag()?"]
STARTED -->|"Yes"| PREPARE["Create DragSession<br/>Prepare DragLayout<br/>Show drop zones"]
STARTED -->|"No"| IGNORE["Return false"]
CHECK_ACTION -->|"DRAG_LOCATION"| LOCATION["update(event)<br/>Move drop zone highlights"]
CHECK_ACTION -->|"DROP"| DROP["handleDrop()<br/>Route to DragLayout"]
DROP --> DROP_RESULT{"DragLayout.drop()"}
DROP_RESULT -->|"Handled"| HIDE_DROP["Hide layout"]
DROP_RESULT -->|"Not handled"| PASS["Let WMS handle"]
CHECK_ACTION -->|"DRAG_EXITED"| EXITED["update(-1, -1)<br/>Clear highlights"]
CHECK_ACTION -->|"DRAG_ENDED"| ENDED["Hide layout<br/>Clear session<br/>Release resources"]
Per-display state (PerDisplay inner class, lines 590-645):
displayId— display identifiercontext— display-specific contextwm— per-display WindowManagerrootView— drop target window rootdragLayout— DragLayout for drop zone UIhasDrawn— first-draw trackingisHandlingDrag— active drag stateactiveDragCount— concurrent drag counter
DragLayout (845 lines)
Manages visual drop target zones for split-screen and fullscreen drop scenarios:
Drop zone configuration:
- Two
DropZoneViewinstances for left/right or top/bottom split SplitDragPolicyprovides target calculation based on current task state- Flexible split support via
enableFlexibleSplit()flag for variable target counts
Key flow:
prepare(session)— initialize with drag session dataupdateSession(session)— calculate targets based on running taskshow()— make layout visible, recompute drop targetsupdate(event)— highlight active drop zone as pointer movesdrop(event, dragSurface, hideTaskToken, callback)— animate drop and launchhide(event, callback)— animate out and clean up
GlobalDragListener (131 lines)
Bridges WMS drag events to Shell:
sequenceDiagram
participant WMS as DragDropController (WMS)
participant GL as GlobalDragListener
participant SDC as DragAndDropController (Shell)
participant Listener as DragAndDropListener
WMS->>GL: IGlobalDragListener.onCrossWindowDrop(taskInfo)
GL->>SDC: callback.onCrossWindowDrop(taskInfo)
SDC->>SDC: Bring task to front
WMS->>GL: IGlobalDragListener.onUnhandledDrop(dragEvent, wmCallback)
GL->>SDC: callback.onUnhandledDrop(dragEvent, consumer)
SDC->>Listener: listener.onUnhandledDrop(dragEvent, consumer)
Listener-->>WMS: wmCallback.notifyUnhandledDropComplete(consumed)
50.7 Drag-to-Move and Drag-to-Resize (Desktop Mode)
Desktop mode uses a separate drag mechanism for window repositioning and resizing, distinct from the data drag-and-drop system. For detailed coverage of the caption bar integration, DragDetector touch slop filtering, ResizeVeil, and fluid vs veiled resize strategies, see §49.7. For multi-window resize context, see §28.1. This subsection provides a summary of the interaction flow.
Key components (detailed in §49.7):
DragDetector(223 lines) — Detects drag gestures from caption bar touch eventsDragPositioningCallback(89 lines) — Interface for move/resize positionersFluidResizeTaskPositioner(238 lines) — Real-time position/resize updatesDragPositioningCallbackUtility(~370 lines) — Bounds calculation helpers
sequenceDiagram
participant User as User Touch
participant DD as DragDetector
participant DPC as DragPositioningCallback
participant FRTP as FluidResizeTaskPositioner
participant WCT as WindowContainerTransaction
participant SCT as SurfaceControl.Transaction
User->>DD: ACTION_DOWN on caption
DD->>DD: Record touch position
DD->>DD: Check hold-to-drag minimum duration
User->>DD: ACTION_MOVE (past touch slop)
DD->>DD: mIsDragEvent = true
DD->>DPC: onDragPositioningStart(ctrlType, startX, startY)
DPC->>FRTP: Record CTRL_TYPE, snapshot bounds
loop Each subsequent ACTION_MOVE
DD->>DPC: onDragPositioningMove(x, y)
DPC->>FRTP: calculateDelta() + changeBounds()
FRTP->>WCT: setBounds(newBounds), setDragResizing(true)
FRTP->>WCT: apply() via Shell
FRTP->>SCT: setPosition() for immediate feedback
end
User->>DD: ACTION_UP
DD->>DPC: onDragPositioningEnd(x, y)
DPC->>FRTP: Start TRANSIT_CHANGE shell transition
CTRL_TYPE values determine move vs resize:
CTRL_TYPE_UNDEFINED— Window move (drag from caption area)CTRL_TYPE_LEFT,CTRL_TYPE_RIGHT,CTRL_TYPE_TOP,CTRL_TYPE_BOTTOM— Edge resizeCTRL_TYPE_LEFT | CTRL_TYPE_TOP, etc. — Corner resize
50.8 Multi-Display Drag and Drop
Cross-display drag is gated by the ENABLE_CONNECTED_DISPLAYS_DND feature flag (DesktopExperienceFlags). When enabled, the system supports dragging content and windows across physical display boundaries.
Cross-Display Data Drag
DragState coordinate handling (updateDragSurfaceLocked(), lines 726-779):
- Detect display change via
displayIdcomparison - Retrieve new
DisplayContentand updatemCurrentDisplayContent - Scale drag shadow by density ratio between displays
- Reparent shadow
SurfaceControlto new display’s overlay layer - Update
InputWindowHandle.displayIdfor input routing
Cross-display window notification (sendDragStartedLocked(), lines 536-591):
- Windows on other displays receive negative coordinates (-1, -1) indicating drag started outside
- All eligible windows across all displays receive
ACTION_DRAG_STARTED
Return animation scaling (lines 827-838):
DIFFERENT_DISPLAY_RETURN_ANIMATION_SCALE= 0.75f- When drag returns to a different display, animation uses reduced scale for visual distinction
sequenceDiagram
participant D1 as Display 1 (Source)
participant DS as DragState
participant SF as SurfaceControl
participant D2 as Display 2 (Target)
participant Win as Target Window (D2)
Note over D1,DS: Drag starts on Display 1
DS->>D1: broadcastDragStartedLocked()
DS->>D2: sendDragStartedLocked() - coords (-1,-1)
Note over DS: User moves to Display 2
DS->>DS: updateDragSurfaceLocked(displayId=2)
DS->>DS: Update mCurrentDisplayContent
DS->>SF: Scale shadow by density ratio
DS->>SF: Reparent to Display 2 overlay
DS->>DS: Update InputWindowHandle.displayId
DS->>Win: ACTION_DRAG_LOCATION (local coords)
DS->>Win: ACTION_DROP
alt Return to Display 1
DS->>DS: createReturnAnimationLocked()
DS->>SF: Animate with 0.75x scale factor
end
Multi-Display Window Drag (Desktop Mode)
For window repositioning across displays, dedicated calculator and indicator classes handle the coordinate transformation:
MultiDisplayDragMoveBoundsCalculator (213 lines):
calculateGlobalDpBoundsForDrag()(lines 48-72) — Converts display-local pixel coordinates to global DP space usingDisplayLayouttransformations, enabling DPI-agnostic positioningconstrainBoundsForDisplay()(lines 90-179) — Applies 96 DP overhang margin (allowing partial window off-screen), scales non-resizable windows to fit while preserving aspect ratioconvertGlobalDpToLocalPxForRect()(lines 203-212) — Inverse transformation from global DP back to display-local pixels for final positioning
MultiDisplayDragMoveIndicatorController (151 lines):
onDragMove()(lines 45-121) — Iterates over all active displays; createsSurfaceControl.mirrorSurface()for visual preview; shows VISIBLE on cursor display, TRANSLUCENT on othersonDragEnd()(lines 127-138) — Cleans up indicator surfaces per taskId- Scale factor calculated as
targetDpi / startDpifor consistent visual sizing
MultiDisplayDragMoveIndicatorSurface (144 lines):
- Creates mirrored surface via
SurfaceControl.mirrorSurface()for the dragged task - Reparents to
RootTaskDisplayAreaOrganizerfor target display rendering - Updates position and visibility based on cursor location
graph TB
subgraph "Coordinate Transformation"
LP1["Display 1: Local PX<br/>(e.g., 480 DPI)"]
GDP["Global DP Space<br/>(DPI-agnostic)"]
LP2["Display 2: Local PX<br/>(e.g., 320 DPI)"]
end
subgraph "Visual Feedback"
MIRROR["SurfaceControl.mirrorSurface()"]
IND1["Indicator on Display 1<br/>TRANSLUCENT"]
IND2["Indicator on Display 2<br/>VISIBLE (cursor here)"]
end
subgraph "Constraints"
OVERHANG["96 DP overhang margin"]
ASPECT["Aspect ratio preservation"]
SCALE["DPI-based scale factor"]
end
LP1 -->|"localPxToGlobalDp()"| GDP
GDP -->|"globalDpToLocalPx()"| LP2
MIRROR --> IND1
MIRROR --> IND2
LP2 --> OVERHANG
LP2 --> ASPECT
LP2 --> SCALE
50.9 Vendor Extension: IDragDropCallback
The IDragDropCallback interface (defined in WindowManagerInternal, lines 388+) provides vendor hooks for customizing drag behavior at the WMS level:
| Method | Purpose |
|---|---|
registerInputChannel() |
Custom input channel registration |
prePerformDrag() / postPerformDrag() |
Lifecycle hooks around drag initiation |
preReportDropResult() / postReportDropResult() |
Hooks around drop result processing |
dragRecipientEntered() / dragRecipientExited() |
Cross-window drag state tracking |
This allows vendor overlays (e.g., Samsung DeX, Motorola Ready For) to inject custom drag behavior without modifying core WMS code.
50.10 Key Source Files
| File | Lines | Purpose |
|---|---|---|
core/java/android/view/View.java |
~34,918 | startDragAndDrop() API (lines 29567-29732) |
core/java/android/view/DragEvent.java |
664 | Event class, 6 action types, object pooling |
services/core/.../wm/Session.java |
1,021 | Binder entry point performDrag() (lines 341-357) |
services/core/.../wm/DragDropController.java |
673 | WMS system-wide drag management |
services/core/.../wm/DragState.java |
948 | Per-drag state, animation, permissions |
services/core/.../wm/DragInputEventReceiver.java |
115 | Motion event capture and routing |
libs/WindowManager/Shell/.../draganddrop/DragAndDropController.java |
646 | Shell policy-driven drag handler |
libs/WindowManager/Shell/.../draganddrop/DragLayout.java |
845 | Drop zone visualization |
libs/WindowManager/Shell/.../draganddrop/SplitDragPolicy.java |
672 | Drop target calculation |
libs/WindowManager/Shell/.../draganddrop/GlobalDragListener.kt |
131 | WMS-to-Shell event bridge |
libs/WindowManager/Shell/.../draganddrop/DragSession.java |
143 | Per-drag session data |
libs/WindowManager/Shell/.../draganddrop/DragUtils.java |
163 | Drag validation utilities |
libs/WindowManager/Shell/.../draganddrop/DropTarget.kt |
55 | Drop target interface |
libs/WindowManager/Shell/.../common/MultiDisplayDragMoveBoundsCalculator.kt |
213 | Cross-display coordinate transformation |
libs/WindowManager/Shell/.../common/MultiDisplayDragMoveIndicatorController.kt |
151 | Multi-display drag visual indicators |
libs/WindowManager/Shell/.../common/MultiDisplayDragMoveIndicatorSurface.kt |
144 | Mirrored surface for drag preview |
Part X: Buffer Management and Virtual Display Internals
51. BufferQueue and BLASTBufferQueue Architecture
51.1 BufferQueue Core Architecture
BufferQueue is the foundational producer-consumer mechanism for passing graphic buffers between components in the Android graphics pipeline. The BufferQueue class itself (99 lines) is a thin factory; the real state lives in BufferQueueCore (400 lines), which manages a pool of BufferSlot entries. The factory method BufferQueue::createBufferQueue() produces a matched pair of IGraphicBufferProducer and IGraphicBufferConsumer interfaces that share a single BufferQueueCore instance.
graph TB
subgraph "BufferQueue Factory (99 lines)"
BQ["BufferQueue::createBufferQueue()"]
end
subgraph "Shared Core (400 lines)"
BQC["BufferQueueCore"]
SLOTS["mSlots[NUM_BUFFER_SLOTS]"]
QUEUE["mQueue (FIFO)"]
MUTEX["mMutex + mDequeueCondition"]
end
subgraph "Producer Side"
BQP["BufferQueueProducer"]
IGBP["IGraphicBufferProducer"]
end
subgraph "Consumer Side"
BQCo["BufferQueueConsumer"]
IGBC["IGraphicBufferConsumer"]
end
BQ --> BQC
BQ --> BQP
BQ --> BQCo
BQP --> BQC
BQCo --> BQC
BQP -.->|implements| IGBP
BQCo -.->|implements| IGBC
BQC --> SLOTS
BQC --> QUEUE
BQC --> MUTEX
Key design constants:
| Constant | Value | Purpose |
|---|---|---|
NUM_BUFFER_SLOTS |
Defined in BufferQueueDefs |
Maximum buffer slots tracked |
MAX_MAX_ACQUIRED_BUFFERS |
NUM_BUFFER_SLOTS - 2 |
Reserve two slots for async producer/consumer |
INVALID_BUFFER_SLOT |
From BufferItem |
Sentinel for unbound slots |
The ProxyConsumerListener inner class prevents circular references between the BufferQueue and its consumer by holding a wp<ConsumerListener> weak reference and forwarding callbacks including onFrameAvailable, onFrameReplaced, onBuffersReleased, onFrameDequeued, onFrameCancelled, onFrameDetached, and onSetFrameRate.
51.2 Buffer Slot State Machine
Each BufferSlot (250 lines) contains a GraphicBuffer, a Fence, and a BufferState that tracks ownership through reference counting rather than a simple enum. The state is determined by three counters (mDequeueCount, mQueueCount, mAcquireCount) plus a mShared flag:
| State | mShared | mDequeueCount | mQueueCount | mAcquireCount | Owner |
|---|---|---|---|---|---|
| FREE | false | 0 | 0 | 0 | BufferQueue |
| DEQUEUED | false | 1 | 0 | 0 | Producer |
| QUEUED | false | 0 | 1 | 0 | BufferQueue |
| ACQUIRED | false | 0 | 0 | 1 | Consumer |
| SHARED | true | any | any | any | Multiple |
stateDiagram-v2
[*] --> FREE
FREE --> DEQUEUED : dequeueBuffer()
DEQUEUED --> QUEUED : queueBuffer()
DEQUEUED --> FREE : cancelBuffer() or detachProducer()
QUEUED --> ACQUIRED : acquireBuffer()
QUEUED --> FREE : replaced in async mode
ACQUIRED --> FREE : releaseBuffer() or detachConsumer()
state SHARED {
[*] --> SharedActive
note right of SharedActive : mShared=true, any combination of dequeue/queue/acquire counts
}
State transitions are performed by increment/decrement methods on BufferState: dequeue() increments mDequeueCount, queue() decrements mDequeueCount and increments mQueueCount, acquire() decrements mQueueCount and increments mAcquireCount, and release() decrements mAcquireCount.
51.3 Triple Buffering
Android uses a three-buffer system to maximize pipeline throughput. The three active slots distribute work across three pipeline stages:
sequenceDiagram
participant App as App (Producer)
participant BQ as BufferQueue
participant SF as SurfaceFlinger (Consumer)
participant HWC as Display
Note over App,HWC: Frame N pipeline
App->>BQ: dequeueBuffer(slot A)
Note over App: Render Frame N to slot A
App->>BQ: queueBuffer(slot A)
Note over App,HWC: Frame N+1 overlaps
App->>BQ: dequeueBuffer(slot B)
BQ->>SF: onFrameAvailable(slot A)
SF->>BQ: acquireBuffer(slot A)
Note over App: Render Frame N+1 to slot B
Note over SF: Composite Frame N from slot A
App->>BQ: queueBuffer(slot B)
App->>BQ: dequeueBuffer(slot C)
SF->>HWC: Present Frame N
SF->>BQ: releaseBuffer(slot A)
BQ->>SF: onFrameAvailable(slot B)
SF->>BQ: acquireBuffer(slot B)
Note over App: Render Frame N+2 to slot C
Note over SF: Composite Frame N+1 from slot B
The buffer count is configured via:
mMaxDequeuedBufferCount(default 1, set by producer) — controls how many buffers the producer can holdmMaxAcquiredBufferCount(default 1, set by consumer) — controls how many buffers the consumer can holdmAsyncMode— adds one extra buffer for non-blocking enqueue- Total active slots =
mMaxDequeuedBufferCount + mMaxAcquiredBufferCount + asyncExtra
51.4 BLASTBufferQueue
BLASTBufferQueue (367 lines header, ~1510 lines implementation) is the modern replacement for the legacy BufferQueue-to-SurfaceFlinger path. Instead of SurfaceFlinger pulling buffers from a consumer, BLASTBufferQueue acquires buffers on the client side and submits them via SurfaceComposerClient::Transaction, enabling atomic updates with other surface properties.
sequenceDiagram
participant App as Application
participant BBQ as BLASTBufferQueue
participant BQ as Internal BufferQueue
participant SF as SurfaceFlinger
App->>BQ: dequeueBuffer()
Note over BBQ: onFrameDequeued - record timestamp
App->>BQ: queueBuffer()
BQ->>BBQ: onFrameAvailable(BufferItem)
BBQ->>BBQ: acquireNextBufferLocked()
BBQ->>BBQ: Build Transaction with buffer, crop, transform
BBQ->>SF: Transaction.apply() via mApplyToken
SF-->>BBQ: transactionCallback(latchTime, presentFence)
SF-->>BBQ: releaseBufferCallback(id, releaseFence)
BBQ->>BQ: releaseBuffer()
Key internal mechanisms:
- Buffer tracking:
mSubmitted(ftl::SmallMap<ReleaseCallbackId, BufferItem>) holds buffers sent to SF;mPendingReleasequeues buffers awaiting return to the BQ - Transaction synchronization:
syncNextTransaction()allows callers to intercept the next transaction for synchronization;mSyncTransactionandmTransactionReadyCallbackcoordinate this - Frame timeline:
mPendingFrameTimelinesqueue pairs frame numbers withFrameTimelineInfofor Choreographer integration - Buffer release channel:
BufferReleaseChannel::ProducerEndpointprovides a dedicated channel for SF-to-client buffer release notifications
Reference files:
frameworks/native/libs/gui/include/gui/BufferQueue.h— Factory class withcreateBufferQueue()(99 lines)frameworks/native/libs/gui/include/gui/BufferQueueCore.h— Central state: slots, counts, synchronization (400 lines)frameworks/native/libs/gui/include/gui/BufferSlot.h— Slot structure withBufferStatestate machine (250 lines)frameworks/native/libs/gui/BufferQueueProducer.cpp— Producer-side operations (dequeue, queue, cancel, detach)frameworks/native/libs/gui/BufferQueueConsumer.cpp— Consumer-side operations (acquire, release, attach)frameworks/native/libs/gui/include/gui/BLASTBufferQueue.h— Transaction-based buffer delivery (367 lines)frameworks/native/libs/gui/BLASTBufferQueue.cpp— Full implementation (~1510 lines)
52. Buffer Sharing Architecture and Lifecycle
52.1 GraphicBuffer
GraphicBuffer (307 lines) is the core cross-process buffer representation, extending ANativeWindowBuffer (the NDK-level native window buffer) with reference counting (RefBase) and serialization (Flattenable). It wraps a gralloc-allocated buffer handle and provides the interface for buffer lifecycle management across process boundaries.
Key properties:
| Property | Method | Description |
|---|---|---|
| Width | getWidth() |
Buffer width in pixels |
| Height | getHeight() |
Buffer height in pixels |
| Stride | getStride() |
Row stride in pixels |
| Format | getPixelFormat() |
HAL pixel format |
| Usage | getUsage() |
64-bit usage flags |
| Layer count | getLayerCount() |
For array textures |
| ID | getId() |
Unique 64-bit buffer identifier |
| Generation | getGenerationNumber() |
BQ generation matching |
Buffer handle ownership is managed through HandleWrapMethod:
| Method | Behavior |
|---|---|
WRAP_HANDLE |
Use directly, no ownership transfer |
TAKE_HANDLE |
Take ownership of a registered handle |
TAKE_UNREGISTERED_HANDLE |
Take ownership, register on construction |
CLONE_HANDLE |
Clone the handle, register the clone |
GraphicBuffer supports Flattenable serialization for Binder IPC — flatten() serializes the buffer’s native handle (file descriptors and integers) plus metadata, and unflatten() reconstructs the buffer on the receiving side.
52.2 Fence Synchronization
Fence (161 lines) wraps a Linux sync fence file descriptor, providing the synchronization primitive for GPU and display pipeline coordination.
sequenceDiagram
participant App as App (GPU)
participant BQ as BufferQueue
participant SF as SurfaceFlinger
participant HWC as HWC/Display
App->>App: GPU render to buffer
App->>BQ: queueBuffer(acquireFence)
Note over BQ: acquireFence signals when GPU done writing
BQ->>SF: acquireBuffer()
SF->>SF: Wait on acquireFence
SF->>HWC: setLayerBuffer(buffer, acquireFence)
HWC->>HWC: Composite and present
HWC-->>SF: presentFence (on-screen signal)
SF->>BQ: releaseBuffer(releaseFence)
Note over BQ: releaseFence signals when compositor done reading
BQ->>App: dequeueBuffer() returns releaseFence
App->>App: Wait on releaseFence before writing
Core Fence API:
| Method | Description |
|---|---|
Fence(int fenceFd) |
Construct from sync fence fd (takes ownership) |
isValid() |
Check if fence fd is open |
wait(int timeout) |
Block up to timeout ms |
waitForever(const char* logname) |
Block indefinitely with warning |
merge(name, f1, f2) |
Create fence signaling when both inputs signal |
getSignalTime() |
Monotonic timestamp or SIGNAL_TIME_PENDING |
getStatus() |
Signaled, Unsignaled, or Invalid |
Sentinel values:
NO_FENCE— Staticsp<Fence>meaning “no synchronization needed”SIGNAL_TIME_PENDING(INT64_MAX) — Not yet signaledSIGNAL_TIME_INVALID(-1) — Invalid fence
52.3 End-to-End Buffer Lifecycle
sequenceDiagram
participant App as Application
participant Gralloc as Gralloc HAL
participant BQ as BufferQueue
participant BBQ as BLASTBufferQueue
participant SF as SurfaceFlinger
participant HWC as HWC HAL
participant Display as Display Panel
Note over App,Gralloc: Allocation
App->>BQ: dequeueBuffer(w, h, format, usage)
BQ->>Gralloc: allocate(descriptor)
Gralloc-->>BQ: buffer_handle_t
BQ-->>App: slot + GraphicBuffer
Note over App: Rendering
App->>App: lock(USAGE_SW_WRITE) or EGL/Vulkan bind
App->>App: Draw content
App->>App: unlock() or glFinish()
Note over App,SF: Submission
App->>BQ: queueBuffer(slot, acquireFence)
BQ->>BBQ: onFrameAvailable()
BBQ->>BBQ: acquireNextBufferLocked()
BBQ->>SF: Transaction.setBuffer(sc, buffer, acquireFence)
Note over SF,Display: Composition
SF->>HWC: validateDisplay()
SF->>HWC: presentDisplay()
HWC->>Display: Scanout
HWC-->>SF: presentFence
Note over SF,App: Release
SF-->>BBQ: releaseBufferCallback(releaseFence)
BBQ->>BQ: releaseBuffer(slot, releaseFence)
Reference files:
frameworks/native/libs/ui/include/ui/GraphicBuffer.h— Cross-process buffer wrapper (307 lines)frameworks/native/libs/ui/include/ui/Fence.h— Sync fence wrapper (161 lines)frameworks/native/libs/ui/include/ui/GraphicBufferAllocator.h— Gralloc HAL allocation interfaceframeworks/native/libs/ui/include/ui/GraphicBufferMapper.h— Gralloc HAL mapping interface
53. Virtual Display Composition Pipeline
When SurfaceFlinger creates a virtual display, it constructs a DisplaySurface implementation that manages the complex buffer flow between the GPU, HWC, and the consuming application. AOSP now provides two implementations: the modern VirtualDisplaySurface (introduced 2025) and the LegacyVirtualDisplaySurface (dating back to 2013).
53.1 Display Addition Pipeline
graph TD
A["DisplayManagerService<br/>createVirtualDisplay()"] --> B["SurfaceFlinger<br/>createDisplay()"]
B --> C{wb_virtualdisplay2<br/>flag enabled?}
C -- Yes --> D["VirtualDisplaySurface<br/>(modern path)"]
C -- No --> E["LegacyVirtualDisplaySurface<br/>(legacy path)"]
D --> F["SinkSurfaceHelper<br/>connectSinkSurface()"]
F --> G["VirtualDisplayThread<br/>task queue"]
E --> H["ConsumerBase +<br/>BnGraphicBufferProducer"]
D --> I["CompositionEngine<br/>OutputLayer setup"]
E --> I
53.2 Three-BufferQueue Architecture
Both implementations manage buffer flow through three logical BufferQueues:
| BufferQueue | Owner (Consumer) | Owner (Producer) | Role |
|---|---|---|---|
| Sink BQ | Application | VDS | Delivers composed frames to the consuming app (e.g., MediaCodec for screen recording) |
| Render BQ | VDS | CompositionEngine/GPU | Receives GPU-composed frames; VDS acquires buffers from here |
| Output BQ | VDS | VDS | Provides output buffers to HWC for Hwc/Mixed composition |
53.3 Per-Frame Composition Sequence
sequenceDiagram
participant CE as CompositionEngine
participant VDS as VirtualDisplaySurface
participant Sink as SinkSurfaceHelper
participant HWC as HWComposer
CE->>VDS: beginFrame(mustRecompose)
VDS->>VDS: Check frozen state, apply pending resize
CE->>VDS: prepareFrame(compositionType)
alt GPU composition
VDS->>Sink: getDequeuedBuffer() for reuse
Sink-->>VDS: buffer or nullopt
VDS->>VDS: Attach recycled buffer to Render BQ
end
CE->>CE: GPU renders to Render BQ surface
Note over CE,VDS: RenderConsumerListener fires onFrameAvailable
VDS->>VDS: onRenderFrameAvailable() - acquire from Render BQ
CE->>VDS: advanceFrame(hdrSdrRatio)
alt HWC or Mixed
VDS->>Sink: getDequeuedBuffer() for output
VDS->>HWC: setOutputBuffer(outputBuffer)
end
alt Mixed
VDS->>HWC: setClientTarget(clientComposedBuffer)
end
CE->>VDS: onFrameCommitted()
alt GPU
Note over VDS: Already sent in onRenderFrameAvailable
else HWC or Mixed
VDS->>HWC: getPresentFence()
VDS->>Sink: sendBuffer(outputBuffer, fence)
end
53.4 VirtualDisplaySurface State Machine
The legacy LegacyVirtualDisplaySurface maintains an explicit debug state machine. The modern VirtualDisplaySurface uses an optional<FrameInfo> instead — if mCurrentFrame is nullopt, no frame is in progress.
Legacy DebugState transitions:
stateDiagram-v2
[*] --> Idle
Idle --> Begun : beginFrame()
Begun --> Prepared : prepareFrame()
Prepared --> Gpu : dequeueBuffer() for GPU/Mixed
Prepared --> Hwc : advanceFrame() for HWC-only
Gpu --> GpuDone : queueBuffer()
GpuDone --> Hwc : advanceFrame()
Hwc --> Idle : onFrameCommitted()
| State | Meaning |
|---|---|
Idle |
No buffer dequeued, no frame in progress |
Begun |
Output buffer dequeued, composition type unknown |
Prepared |
Composition type known but GPU buffer not yet provided |
Gpu |
GPU driver has a buffer dequeued for rendering |
GpuDone |
GPU has queued its buffer, awaiting HWC submission |
Hwc |
HWC has the buffer; frame committed to hardware |
53.5 Buffer Slot Tracking and LRU Eviction
VirtualDisplayBufferSlotTracker maps GraphicBuffer IDs to HWC slot numbers, enabling buffer reuse without re-sending the full buffer handle across IPC on every frame.
- Backed by
LruCache<uint64_t, uint32_t>mapping buffer IDs to HWC slots - Maximum capacity defaults to
BufferQueue::NUM_BUFFER_SLOTS(64) - Maintains an
std::set<uint32_t> mOpenSlotsof available slot numbers - Implements
OnEntryRemovedto recycle evicted slot numbers back tomOpenSlots
When getSlot() returns requiresRefresh = true, the full GraphicBuffer must be sent to HWC. When false, only the slot number is passed (HWC already has the buffer handle cached).
53.6 CompositionType and GPU/HWC/Mixed Selection
The CompositionType enum is defined as a bitmask in DisplaySurface.h:
| Value | Binary | Meaning |
|---|---|---|
Unknown |
0b00 |
Frame not yet prepared |
Gpu |
0b01 |
All layers composited by GPU/RenderEngine |
Hwc |
0b10 |
All layers composited by HWC hardware |
Mixed |
0b11 |
GPU composites some layers; HWC composites the rest |
Buffer flow per type in the modern VirtualDisplaySurface:
| Step | GPU | HWC | Mixed |
|---|---|---|---|
prepareFrame |
Recycle sink buffer into Render BQ | No-op | No-op |
| GPU renders | To Render BQ surface | N/A | To Render BQ surface |
onRenderFrameAvailable |
Acquire, detach, send to sink | N/A | Acquire, detach, store as clientComposedBuffer |
advanceFrame |
No-op | Dequeue output, set on HWC | Dequeue output, set clientTarget + outputBuffer on HWC |
onFrameCommitted |
No-op (already sent) | Send output to sink | Reattach render buffer; send output to sink |
53.7 Legacy vs Modern Path Selection
| Aspect | LegacyVirtualDisplaySurface |
VirtualDisplaySurface (modern) |
|---|---|---|
| Copyright year | 2013 | 2025 |
| Inheritance | BnGraphicBufferProducer + ConsumerBase |
compositionengine::DisplaySurface only |
| GPU interface | Acts as IGraphicBufferProducer proxy (slot muxing) |
Separate Surface/BufferItemConsumer pair |
| Sink interaction | Direct IGraphicBufferProducer calls on SF thread |
Offloaded to VirtualDisplayThread via SinkSurfaceHelper |
| Thread safety | Single-threaded (SF main thread) | SinkSurfaceHelper isolates sink IPC to dedicated thread |
| Freeze detection | None | VirtualDisplayThread::isFrozen() detects deadlocked apps |
| Buffer recycling | Slot-based muxing between SOURCE_SINK and SOURCE_SCRATCH | SinkSurfaceHelper pre-dequeues up to 4 buffers for reuse |
Reference files:
frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp—processDisplayAdded()at lines 4265-4391,processDisplayRemoved()at line 4393frameworks/native/services/surfaceflinger/DisplayHardware/VirtualDisplay/VirtualDisplaySurface.h— Modern three-BQ architecture (167 lines)frameworks/native/services/surfaceflinger/DisplayHardware/VirtualDisplay/SinkSurfaceHelper.h— Thread-isolated sink management (144 lines)frameworks/native/services/surfaceflinger/DisplayHardware/VirtualDisplay/VirtualDisplayBufferSlotTracker.h— LRU buffer slot tracking
54. CompanionDeviceManager and Virtual Device Framework
The Virtual Device Manager (VDM) framework allows companion apps to create full virtual devices with their own displays, input devices, sensors, cameras, and audio routing. Built on top of CompanionDeviceManager associations, it provides a controlled mechanism for cross-device experiences.
54.1 VirtualDeviceImpl Display Creation
sequenceDiagram
participant App as Companion App
participant VDI as VirtualDeviceImpl
participant GWPC as GenericWindowPolicyController
participant DMS as DisplayManagerService
participant SF as SurfaceFlinger
App->>VDI: createVirtualDisplay(config, callback)
VDI->>VDI: checkCallerIsDeviceOwner()
VDI->>VDI: Validate clipboard policy requires trusted display
VDI->>GWPC: createWindowPolicyController(displayCategories)
VDI->>DMS: createVirtualDisplay(config, callback, gwpc, ownerPkg, ownerUid)
DMS->>SF: createDisplay()
SF-->>DMS: displayId
DMS->>VDI: onVirtualDisplayCreated(displayId)
VDI->>VDI: acquireWakeLock()
VDI->>VDI: setMouseScalingEnabled(false)
VDI->>VDI: setDisplayEligibilityForPointerCapture(false)
alt Trusted display
VDI->>VDI: setShowPointerIcon(default)
VDI->>VDI: setDisplayImePolicy(LOCAL)
else Untrusted
VDI->>GWPC: setShowInHostDeviceRecents(true)
end
VDI-->>App: displayId
54.2 GenericWindowPolicyController Callbacks
GenericWindowPolicyController extends DisplayWindowPolicyController to enforce policies on what can run on virtual displays:
| Callback | Purpose | Key Logic |
|---|---|---|
canActivityBeLaunched() |
Gate for all activity launches | Checks intent interception, then delegates to canContainActivity() |
canContainActivity() |
Core policy enforcement | Rejects mirror displays, checks canDisplayOnRemoteDevices, user allowlist, display categories |
keepActivityOnWindowFlagsChanged() |
Handles FLAG_SECURE changes | Fires onSecureWindowShown/onSecureWindowHidden callbacks |
onTopActivityChanged() |
Notifies when foreground activity changes | Forwards to app’s ActivityListener on main thread |
onRunningAppsChanged() |
Tracks all running UIDs/packages | Updates internal set; fires onDisplayEmpty when empty |
canShowTasksInHostDeviceRecents() |
Controls recents visibility | Configurable per display |
getCustomHomeComponent() |
Custom home activity for display | Returns component set in VirtualDeviceParams |
54.3 Device Profiles
Companion device associations are typed by device profile, which determines the permissions granted and the capabilities available:
| Profile Constant | Role String | Required Permission | Use Case |
|---|---|---|---|
DEVICE_PROFILE_WATCH |
COMPANION_DEVICE_WATCH |
REQUEST_COMPANION_PROFILE_WATCH |
Smartwatches |
DEVICE_PROFILE_FITNESS_TRACKER |
COMPANION_DEVICE_FITNESS_TRACKER |
REQUEST_COMPANION_PROFILE_WATCH |
Fitness bands |
DEVICE_PROFILE_GLASSES |
COMPANION_DEVICE_GLASSES |
REQUEST_COMPANION_PROFILE_GLASSES |
AR/smart glasses |
DEVICE_PROFILE_MEDICAL |
COMPANION_DEVICE_MEDICAL |
REQUEST_COMPANION_PROFILE_MEDICAL |
Medical devices |
DEVICE_PROFILE_COMPUTER |
COMPANION_DEVICE_COMPUTER |
REQUEST_COMPANION_PROFILE_COMPUTER |
Cross-device notification/media |
DEVICE_PROFILE_APP_STREAMING |
COMPANION_DEVICE_APP_STREAMING |
REQUEST_COMPANION_PROFILE_APP_STREAMING |
Virtual display app rendering |
DEVICE_PROFILE_NEARBY_DEVICE_STREAMING |
COMPANION_DEVICE_NEARBY_DEVICE_STREAMING |
REQUEST_COMPANION_PROFILE_NEARBY_DEVICE_STREAMING |
Content rendering to nearby device |
DEVICE_PROFILE_VIRTUAL_DEVICE |
COMPANION_DEVICE_VIRTUAL_DEVICE |
REQUEST_COMPANION_PROFILE_VIRTUAL_DEVICE |
Sensor data streaming |
DEVICE_PROFILE_AUTOMOTIVE_PROJECTION |
SYSTEM_AUTOMOTIVE_PROJECTION |
REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION |
Android Auto projection |
DEVICE_PROFILE_WEARABLE_SENSING |
(non-role) | (hidden) | Environmental sensing wearables |
Only DEVICE_PROFILE_APP_STREAMING is currently allowed to create mirror displays.
54.4 Virtual Input Devices
The InputController manages the lifecycle of virtual input devices through InputManagerInternal:
| Device Type | Config Class | Creation Method |
|---|---|---|
VirtualKeyboard |
VirtualKeyboardConfig |
createVirtualKeyboard() |
VirtualMouse |
VirtualMouseConfig |
createVirtualMouse() |
VirtualTouchscreen |
VirtualTouchscreenConfig |
createVirtualTouchscreen() |
VirtualDpad |
VirtualDpadConfig |
createVirtualDpad() |
VirtualNavigationTouchpad |
VirtualNavigationTouchpadConfig |
createVirtualNavigationTouchpad() |
VirtualStylus |
VirtualStylusConfig |
createVirtualStylus() |
VirtualRotaryEncoder |
VirtualRotaryEncoderConfig |
createVirtualRotaryEncoder() |
graph TD
A["VirtualDeviceImpl<br/>createVirtualKeyboard(token, config)"] --> B["InputController<br/>createKeyboard(token, config)"]
B --> C["InputManagerInternal<br/>createVirtualKeyboard(token, config)"]
C --> D["IVirtualInputDevice returned"]
D --> E["InputController stores<br/>token to device mapping"]
E --> F["InputDeviceListener monitors<br/>onInputDeviceRemoved"]
F --> G["Auto-cleanup on<br/>device removal"]
All devices are tracked in an ArrayMap<IBinder, IVirtualInputDevice> keyed by token. When the InputController is closed, all registered devices are closed via InputManagerInternal.closeVirtualInputDevice().
54.5 Window Policy Enforcement Flow
Activity launches on virtual displays pass through a multi-stage enforcement pipeline:
graph TD
A["Activity launch requested<br/>on virtual display"] --> B{Intent intercepted<br/>by VDM client?}
B -- Yes --> Z["Launch blocked"]
B -- No --> C{Mirror display?}
C -- Yes --> Z
C -- No --> D{Secure display OR<br/>canDisplayOnRemoteDevices?}
D -- No --> Z
D -- Yes --> E{User in<br/>allowedUsers set?}
E -- No --> Z
E -- Yes --> F{Activity matches<br/>display category?}
F -- No --> Z
F -- Yes --> G{Allowed by<br/>activity policy?}
G -- No --> Z
G -- Yes --> H{Cross-task navigation<br/>from non-default display?}
H -- Yes, blocked --> Z
H -- No/Allowed --> I["Activity launched<br/>on virtual display"]
Z --> J["BlockedAppStreamingActivity<br/>shown if configured"]
Z --> K["onActivityLaunchBlocked<br/>callback fired"]
The policy system supports two modes configured via VirtualDeviceParams:
- Default allowed (
mActivityLaunchAllowedByDefault = true): All activities can launch except those explicitly in the exemption set (blocklist) - Default blocked (
mActivityLaunchAllowedByDefault = false): Only activities explicitly in the exemption set can launch (allowlist)
54.6 Sensor, Audio, and Camera Virtualization
Beyond displays and input, VDM supports three additional virtualization subsystems:
| Subsystem | Controller | Mechanism |
|---|---|---|
| Sensors | SensorController |
Creates virtual sensors via SensorManagerInternal; companion app injects VirtualSensorEvent data |
| Audio | VirtualAudioController |
Routes audio via AudioMix policies; microphone input injectable from companion device |
| Camera | VirtualCameraController |
Creates virtual cameras via VirtualCameraConfig; frames provided by companion app |
54.7 VirtualDevice Lifecycle
graph TD
A["CompanionDeviceManager<br/>associate(request)"] --> B["AssociationInfo created<br/>with DeviceProfile"]
B --> C["VirtualDeviceManager<br/>createVirtualDevice(associationId, params)"]
C --> D["VirtualDeviceImpl constructed<br/>InputController, SensorController,<br/>CameraAccessController initialized"]
D --> E["createVirtualDisplay(config)<br/>per display needed"]
E --> F["GenericWindowPolicyController<br/>created per display"]
F --> G["DisplayManagerService<br/>creates virtual display"]
G --> H["createVirtualKeyboard /<br/>createVirtualMouse /<br/>createVirtualTouchscreen"]
H --> I["App streams content<br/>to/from virtual device"]
I --> J["close() called"]
J --> K["InputController.close()<br/>removes all input devices"]
K --> L["VirtualAudioController.close()<br/>removes audio routing"]
L --> M["SensorController.close()<br/>removes virtual sensors"]
M --> N["All virtual displays<br/>released and removed"]
N --> O["VirtualDeviceImpl<br/>binderDied cleanup"]
Reference files:
frameworks/base/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java— Virtual device implementation with display, input, sensor managementframeworks/base/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java— Window policy enforcement for virtual displaysframeworks/base/services/companion/java/com/android/server/companion/virtual/InputController.java— Virtual input device lifecycle managementframeworks/base/core/java/android/companion/virtual/VirtualDeviceManager.java— Public API for virtual device creation
Part XI: Display Refresh and Frame Scheduling
57. Display Refresh Architecture
Android’s display refresh system is a multi-stage pipeline that coordinates hardware VSYNC interrupts through SurfaceFlinger’s scheduler to application-level frame callbacks. The architecture includes VSYNC prediction, phase-offset scheduling, per-layer refresh rate voting, and dynamic mode switching. This section also compares the Android approach with Linux’s DRM/KMS model.
57.1 VSYNC Pipeline Overview
graph TB
subgraph "Hardware Layer"
HWC_VSYNC["HWC VSYNC Interrupt"]
HWC2["HWC2::ComposerCallback<br/>onComposerHalVsync()"]
end
subgraph "VSYNC Prediction (per display)"
VS["VsyncSchedule<br/>170 lines"]
VSR["VSyncReactor<br/>114 lines - period transitions"]
VSP["VSyncPredictor<br/>186 lines - linear model"]
end
subgraph "Dispatch & Scheduling"
VSD["VSyncDispatch<br/>197 lines - callback scheduling"]
VSDTQ["VSyncDispatchTimerQueue<br/>175 lines - timer-based"]
MQ["MessageQueue<br/>160 lines - SF frame scheduling"]
ET["EventThread<br/>289 lines - app VSYNC delivery"]
end
subgraph "Orchestration"
SCHED["Scheduler<br/>696 lines - central coordinator"]
RRS["RefreshRateSelector<br/>596 lines - rate selection"]
LH["LayerHistory<br/>167 lines - per-layer tracking"]
DMC["DisplayModeController<br/>210 lines - mode switching"]
end
subgraph "Application Layer"
DER["DisplayEventReceiver<br/>219 lines"]
CHOREO["Choreographer<br/>1,714 lines"]
VRI["ViewRootImpl<br/>scheduleTraversals()"]
end
HWC_VSYNC --> HWC2
HWC2 --> VS
VS --> VSR
VS --> VSP
VSP --> VSD
VSD --> VSDTQ
VSDTQ --> MQ
VSDTQ --> ET
MQ -->|"SF commit + composite"| SCHED
ET -->|"VSYNC events"| DER
DER --> CHOREO
CHOREO --> VRI
SCHED --> RRS
SCHED --> LH
RRS --> DMC
The pipeline has nine stages, each with a distinct responsibility:
| Stage | Component | Responsibility |
|---|---|---|
| 1 | HWC2 ComposerCallback | Receive hardware VSYNC interrupts from display hardware |
| 2 | VsyncSchedule + VSyncPredictor | Predict future VSYNC times using linear regression model |
| 3 | VSyncReactor | Manage VSYNC period transitions during refresh rate changes |
| 4 | VSyncDispatch | Schedule callbacks at precise offsets relative to predicted VSYNC |
| 5 | MessageQueue | Schedule SurfaceFlinger frame composition |
| 6 | EventThread | Deliver phase-offset VSYNC events to app processes |
| 7 | Choreographer | Coordinate app frame callbacks (input, animation, traversal) |
| 8 | RefreshRateSelector + LayerHistory | Choose optimal refresh rate based on content |
| 9 | DisplayModeController | Execute hardware display mode switches |
57.2 Hardware VSYNC Reception
The HWC HAL delivers hardware VSYNC interrupts to SurfaceFlinger through the ComposerCallback interface:
HWC2::ComposerCallback (HWC2.h, 489 lines, lines 81-95):
onComposerHalVsync(HWDisplayId, timestamp, optional<VsyncPeriodNanos>)— hardware VSYNC entry pointonComposerHalVsyncPeriodTimingChanged()— refresh rate change notificationonComposerHalVsyncIdle()— idle power state VSYNC
HWComposer (HWComposer.h, 622 lines):
onVsync(HWDisplayId, timestamp)(line 251) — maps hardware display ID to logicalPhysicalDisplayIdsetVsyncEnabled(PhysicalDisplayId, Vsync)(line 253) — controls hardware VSYNC deliverygetDisplayVsyncPeriod(PhysicalDisplayId)(line 270) — queries current VSYNC period
SurfaceFlinger receives the callback at onComposerHalVsync() (SurfaceFlinger.cpp line 2452), routes it through HWComposer, and feeds the sample into the per-display VsyncSchedule.
57.3 VSYNC Prediction and Tracking
Each physical display maintains its own VsyncSchedule (VsyncSchedule.h, 170 lines) that owns three components:
- VSyncTracker (interface, 141 lines) / VSyncPredictor (implementation, 186 lines) — Predicts future VSYNC times using a linear regression model:
Model { nsecs_t slope; nsecs_t intercept; }addVsyncTimestamp(timestamp)— feed hardware VSYNC samplenextAnticipatedVSyncTimeFrom(timePoint)— predict next VSYNC >= timePointcurrentPeriod()— current VSYNC period in nanosecondssetRenderRate(Fps, applyImmediately)— VRR support: different render vs display rateVsyncTimelineinner class manages per-render-rate timeline (e.g., 120 Hz display rendering at 60 Hz)
- VSyncReactor (
VSyncReactor.h, 114 lines) — Manages VSYNC period transitions during refresh rate changes:addHwVsyncTimestamp(timestamp, hwcVsyncPeriod, periodFlushed)— react to hardware samplesaddPresentFence(FenceTime)— validate predictions against actual present timesonDisplayModeChanged(DisplayModePtr)— handle mode transitions- State machine:
mMoreSamplesNeeded→mPeriodConfirmationInProgress→ confirmed
- Hardware VSYNC state (
VsyncSchedule, lines 139-157):enum HwVsyncState { Enabled, Disabled, Disallowed }Hardware VSYNC is reference-counted: enabled when prediction model needs calibration, disabled when model is confident, disallowed when display is off.
57.4 VSYNC Dispatch and Phase Offsets
VSyncDispatch (VSyncDispatch.h, 197 lines) schedules callbacks at precise offsets before predicted VSYNC times:
sequenceDiagram
participant Client as Client (SF or App)
participant VSD as VSyncDispatch
participant VSDTQ as VSyncDispatchTimerQueue
participant VSP as VSyncPredictor
participant Timer as System Timer
Client->>VSD: schedule(token, ScheduleTiming)
Note over VSD: ScheduleTiming contains:<br/>workDuration, readyDuration, lastVsync
VSD->>VSP: nextAnticipatedVSyncTimeFrom(now)
VSP-->>VSD: predictedVsync
VSD->>VSD: wakeup = predictedVsync - workDuration - readyDuration
VSD->>VSDTQ: Arm timer for earliest wakeup
VSDTQ->>Timer: Set alarm
Timer-->>VSDTQ: Timer fires at wakeup time
VSDTQ->>Client: callback(vsyncTime, wakeupTime, readyTime)
ScheduleTiming (lines 102-114):
workDuration— time the client needs to complete its workreadyDuration— additional buffer for SurfaceFlinger processing (app clients only)- Wakeup time = predicted VSYNC - workDuration - readyDuration
The VSyncDispatchTimerQueue (VSyncDispatchTimerQueue.h, 175 lines) implements a single-timer multiplexer: one system timer serves all registered callbacks. Each callback is a VSyncDispatchTimerQueueEntry with states: disarmed → armed → running → disarmed.
57.5 SurfaceFlinger Frame Scheduling
MessageQueue (MessageQueue.h, 160 lines; .cpp, 217 lines) bridges VSYNC dispatch to SurfaceFlinger’s composition loop:
vsyncCallback(vsyncTime, targetWakeupTime, readyTime)— received from VSyncDispatchHandler::dispatchFrame(VsyncId, expectedVsyncTime)— triggers SF frame processing- SF executes
commit()(line 683 in SurfaceFlinger.h) thencomposite()(line 685):commit()— process pending transactions, update layer statecomposite()— build composition engine refresh args, submit to HWC
Pacesetter display model (Scheduler.h lines 99-114): In multi-display setups, the Scheduler designates one display as the “pacesetter” — typically the powered-on display with the highest refresh rate. The pacesetter’s VSYNC drives the main composition loop; other displays synchronize to it.
57.6 App-Side VSYNC: EventThread and Choreographer
sequenceDiagram
participant VSD as VSyncDispatch
participant ET as EventThread
participant ETC as EventThreadConnection
participant DER as DisplayEventReceiver
participant C as Choreographer
participant VRI as ViewRootImpl
VSD->>ET: onVsync(vsyncTime, wakeupTime, readyTime)
ET->>ET: generateFrameTimeline()
ET->>ETC: postEvent(VSync event + VsyncEventData)
ETC->>DER: BitTube write
DER->>C: onVsync(timestamp, displayId, frame, vsyncEventData)
C->>C: doFrame(frameTimeNanos)
C->>C: CALLBACK_INPUT
C->>C: CALLBACK_ANIMATION
C->>C: CALLBACK_INSETS_ANIMATION
C->>C: CALLBACK_TRAVERSAL
C->>VRI: doTraversal()
VRI->>VRI: performMeasure() + performLayout() + performDraw()
C->>C: CALLBACK_COMMIT
EventThread (EventThread.h, 289 lines; .cpp, 907 lines):
- Two instances: one for app VSYNC (
Rendercycle), one for SF internal timing (LastCompositecycle) requestNextVsync(connection)— one-shot VSYNC deliverysetVsyncRate(rate, connection)— periodic delivery (rate=1 every VSYNC, rate=2 every other)- Generates
VsyncEventDatawith frame timeline predictions for jank analysis
Choreographer (Choreographer.java, 1,714 lines):
- One instance per
Looperthread; apps useVSYNC_SOURCE_APP doFrame()(line 1021) executes five callback phases in order:CALLBACK_INPUT(0) — process input eventsCALLBACK_ANIMATION(1) — update animationsCALLBACK_INSETS_ANIMATION(2) — inset transitionsCALLBACK_TRAVERSAL(3) — View measure/layout/drawCALLBACK_COMMIT(4) — finalize transactions
scheduleVsyncLocked()(lines 1270-1273) requests the next VSYNC from EventThread
ViewRootImpl (ViewRootImpl.java, 13,827 lines):
scheduleTraversals()(line 3085) — posts sync barrier + Choreographer TRAVERSAL callbackdoTraversal()(line 3123) — removes sync barrier, callsperformTraversals()performTraversals()— the core measure → layout → draw pipeline
57.7 Content-Driven Refresh Rate Selection
Android dynamically selects the display refresh rate based on content requirements:
flowchart TD
subgraph "Per-Layer Signals"
APP["App: Surface.setFrameRate()<br/>e.g., video at 24fps"]
ANIM["Animation frame history<br/>LayerHistory.record()"]
TOUCH["Touch event<br/>GlobalSignals.touch"]
IDLE["Idle timer<br/>GlobalSignals.idle"]
end
subgraph "LayerHistory (167 lines)"
LH["record() per frame"]
SUM["summarize() returns<br/>vector of LayerRequirement"]
end
subgraph "RefreshRateSelector (596 lines)"
SCORE["Score each available mode<br/>against all layer votes"]
RANK["getRankedFrameRates()<br/>returns scored list"]
OVERRIDE["getFrameRateOverrides()<br/>per-UID divisor rates"]
end
subgraph "DisplayModeController (210 lines)"
DESIRED["setDesiredMode()"]
INITIATE["initiateModeChange()<br/>HWC.setActiveModeWithConstraints()"]
FINALIZE["finalizeModeChange()"]
end
APP --> LH
ANIM --> LH
LH --> SUM
SUM --> SCORE
TOUCH --> SCORE
IDLE --> SCORE
SCORE --> RANK
RANK --> DESIRED
DESIRED --> INITIATE
INITIATE --> FINALIZE
RANK --> OVERRIDE
LayerHistory (LayerHistory.h, 167 lines; .cpp, 498 lines):
record(LayerUpdateType)— records frame timing with type: Buffer, AnimationTX, or SetFrameRatesummarize()— returnsvector<LayerRequirement>for all active layers- Activity window: 1 second; inactive layers excluded from voting
RefreshRateSelector (RefreshRateSelector.h, 596 lines; .cpp, 1,885 lines):
LayerVoteTypeenum: NoVote, Min, Max, Heuristic, ExplicitDefault, ExplicitExactOrMultiple, ExplicitExact, ExplicitGte, ExplicitCategorygetRankedFrameRates()(impl line 578) — scores each available refresh rate against all layer requirementsgetFrameRateOverrides()(impl line 1082) — computes per-UID frame rate divisors (e.g., 120 Hz display running an app at 60 Hz)- Policy includes
primaryRanges,appRequestRanges,idleScreenConfig
Surface.setFrameRate() (Surface.java, 1,494 lines):
- Per-surface frame rate hints with compatibility modes:
FRAME_RATE_COMPATIBILITY_DEFAULT(0) — allows downscalingFRAME_RATE_COMPATIBILITY_FIXED_SOURCE(1) — video at exact rateFRAME_RATE_COMPATIBILITY_AT_LEAST(2) — greater than or equal to specified rateFRAME_RATE_COMPATIBILITY_EXACT(100) — exact rate required
- Mode switch strategy:
CHANGE_FRAME_RATE_ONLY_IF_SEAMLESSvsCHANGE_FRAME_RATE_ALWAYS
57.8 Frame Timeline and Jank Detection
FrameTimeline (FrameTimeline.h, 568 lines; .cpp, 1,681 lines) tracks expected vs actual frame timing:
TimelineItem (lines 86-105):
startTime— when render/composition startedendTime— when render/composition finishedpresentTime— actual on-screen timedesiredPresentTime— target presentation time
Jank classification (lines 49-81):
| Metadata | Values |
|---|---|
| FramePresentMetadata | OnTimePresent, LatePresent, EarlyPresent |
| FrameReadyMetadata | OnTimeFinish, LateFinish |
| FrameStartMetadata | OnTimeStart, LateStart, EarlyStart |
TokenManager (lines 123-133): Generates prediction tokens for apps; predictions expire after 120 ms. Apps receive tokens via VsyncEventData and report back actual timing for comparison.
57.9 Android vs Linux Display Refresh Comparison
graph LR
subgraph "Android"
direction TB
A_HWC["HWC HAL<br/>Vendor-specific"]
A_SF["SurfaceFlinger<br/>Scheduler + VSyncPredictor"]
A_ET["EventThread<br/>Phase-offset VSYNC"]
A_CH["Choreographer<br/>Per-app frame callbacks"]
A_HWC --> A_SF --> A_ET --> A_CH
end
subgraph "Linux"
direction TB
L_DRM["DRM/KMS<br/>Kernel standard API"]
L_COMP["Compositor<br/>Weston/Mutter/KWin"]
L_WL["wl_surface.frame<br/>Compositor-driven callback"]
L_APP["Application<br/>Renders on callback"]
L_DRM --> L_COMP --> L_WL --> L_APP
end
| Aspect | Android | Linux (DRM/KMS + Wayland) |
|---|---|---|
| VSYNC source | HWC HAL onComposerHalVsync() — vendor-specific implementation |
DRM page_flip_handler / vblank_handler — kernel-standard via drmHandleEvent() |
| VSYNC prediction | VSyncPredictor linear model extrapolates future VSYNC times from hardware samples |
No prediction layer — compositors react to page-flip-complete events directly |
| Frame scheduling signal | Choreographer.doFrame() provides per-app VSYNC callbacks with precise timestamps |
wl_surface.frame callback — compositor decides when each surface should render; no global VSYNC signal to apps |
| Phase offsets | Apps wake workDuration + readyDuration before VSYNC; SF wakes sfWorkDuration before VSYNC |
Weston uses output_repaint_timer (frame_period - repaint_window); Mutter uses per-monitor ClutterFrameClock |
| Display abstraction | HWC HAL (IComposerClient): validateDisplay(), presentDisplay(), setVsyncEnabled() — vendor binary |
DRM/KMS: drmModeAtomicCommit(), CRTC/plane/connector — open kernel API, no vendor HAL |
| Buffer allocation | Gralloc HAL (IAllocator/IMapper) — vendor-specific, buffer_handle_t with opaque data |
GBM (Generic Buffer Manager) via Mesa — gbm_bo_create(), open standard; or Vulkan WSI |
| Fence synchronization | Android sync framework: sync_file fds via ANativeWindow and HWC |
DRM IN_FENCE_FD (per-plane acquire) / OUT_FENCE_PTR (per-CRTC release) — same struct dma_fence kernel primitive |
| Refresh rate selection | RefreshRateSelector scores modes against per-layer LayerRequirement votes; Surface.setFrameRate() API for app hints |
No per-app frame rate API in Wayland protocol; compositor policy decides. KDE/GNOME expose settings UI |
| VRR | RefreshRateSelector.isVrrDisplay(), per-surface frame rate hints, render rate decoupled from display rate via VSyncPredictor.setRenderRate() |
DRM property VRR_ENABLED on CRTC; compositor sets it and controls flip timing |
| Multi-display sync | Pacesetter display model: highest-rate display drives composition; per-display VsyncSchedule with independent prediction |
Per-monitor frame clocks (Mutter ClutterFrameClock); Weston per-output repaint scheduling; no global pacesetter |
| Composition model | SurfaceFlinger collects all layers; HWC decides hardware vs GPU composition per-frame; RenderEngine for GPU fallback |
Compositor renders all surfaces; DRM planes for direct scanout of fullscreen apps; GPU composition via EGL/Vulkan for overlapping windows |
| Page flip | HWC::presentDisplay() → HWC driver handles atomic commit internally |
drmModeAtomicCommit() with DRM_MODE_PAGE_FLIP_EVENT flag; DRM_MODE_ATOMIC_NONBLOCK for async |
| Triple buffering | BufferQueue with configurable mMaxDequeuedBufferCount + mMaxAcquiredBufferCount; slot-based state machine (§51) |
GBM surface or Vulkan swapchain; Mesa typically allocates 3 EGL buffers; no standardized producer-consumer queue |
57.10 Linux DRM/KMS Frame Scheduling Detail
For comparison, the Linux display refresh pipeline operates as follows:
Kernel VBLANK mechanism:
- Each CRTC generates VBLANK interrupts at the display’s refresh rate
drm_crtc_vblank_get()/drm_crtc_vblank_put()— reference-counted interrupt enable/disabledrm_crtc_send_vblank_event()— delivers page-flip-complete events to userspace- Events read via the DRM fd using
poll()/epoll()+drmHandleEvent()
Atomic commit flow:
- Compositor constructs atomic request (framebuffer IDs, plane properties, CRTC state)
DRM_IOCTL_MODE_ATOMICwithDRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK- Kernel validates (
atomic_check()), commits (atomic_commit()) - Commit completion tracked via
struct drm_crtc_commit:flip_done— hardware flipped to new buffer (VBLANK); userspace event senthw_done— all register writes completecleanup_done— old state freed
Wayland compositor scheduling (Weston example):
- Page-flip-complete event arrives from DRM
- Compositor starts repaint timer: delay = frame_period - repaint_window
- Timer fires → compositor samples all client surfaces, composes, submits atomic commit
- Composed frame hits the next VBLANK
Wayland client frame protocol:
- Client calls
wl_surface_frame(surface)→ receiveswl_callback - Client renders, attaches buffer, commits
- Compositor fires
wl_callback.done(timestamp)when ready for next frame - Client repeats — no rendering without compositor permission
This is fundamentally different from Android’s model where Choreographer provides a predictable per-VSYNC signal to every app. In Wayland, the compositor decides when each surface should render, enabling per-surface throttling (hidden/minimized windows get no callbacks).
Variable Refresh Rate on Linux:
vrr_capable(connector property, read-only) — display supports VRRVRR_ENABLED(CRTC property, writable) — compositor enables VRR- Supported by amdgpu (FreeSync), i915 (Gen12+), and NVIDIA proprietary
- Compositors: KWin auto-enables for fullscreen; Mutter experimental since v46; Sway via
adaptive_sync on
57.11 Key Source Files
| File | Lines | Purpose |
|---|---|---|
frameworks/native/services/surfaceflinger/DisplayHardware/HWC2.h |
489 | ComposerCallback VSYNC interface |
frameworks/native/services/surfaceflinger/DisplayHardware/HWComposer.h |
622 | HWC abstraction, VSYNC routing |
frameworks/native/services/surfaceflinger/Scheduler/VsyncSchedule.h |
170 | Per-display VSYNC schedule |
frameworks/native/services/surfaceflinger/Scheduler/VSyncTracker.h |
141 | VSYNC prediction interface |
frameworks/native/services/surfaceflinger/Scheduler/VSyncPredictor.h |
186 | Linear model VSYNC predictor |
frameworks/native/services/surfaceflinger/Scheduler/VSyncReactor.h |
114 | Period transition management |
frameworks/native/services/surfaceflinger/Scheduler/VSyncDispatch.h |
197 | Callback scheduling interface |
frameworks/native/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h |
175 | Timer-based dispatch implementation |
frameworks/native/services/surfaceflinger/Scheduler/EventThread.h |
289 | App VSYNC event delivery |
frameworks/native/services/surfaceflinger/Scheduler/EventThread.cpp |
907 | EventThread implementation |
frameworks/native/services/surfaceflinger/Scheduler/MessageQueue.h |
160 | SF frame scheduling |
frameworks/native/services/surfaceflinger/Scheduler/Scheduler.h |
696 | Central scheduling orchestrator |
frameworks/native/services/surfaceflinger/Scheduler/Scheduler.cpp |
1,820 | Scheduler implementation |
frameworks/native/services/surfaceflinger/Scheduler/RefreshRateSelector.h |
596 | Refresh rate selection logic |
frameworks/native/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp |
1,885 | Scoring algorithm implementation |
frameworks/native/services/surfaceflinger/Scheduler/LayerHistory.h |
167 | Per-layer frame rate tracking |
frameworks/native/services/surfaceflinger/Scheduler/LayerHistory.cpp |
498 | Frame recording and activity detection |
frameworks/native/services/surfaceflinger/Scheduler/FrameTimeline.h |
568 | Expected vs actual frame timing |
frameworks/native/services/surfaceflinger/Scheduler/FrameTimeline.cpp |
1,681 | Jank detection implementation |
frameworks/native/services/surfaceflinger/Display/DisplayModeController.h |
210 | Display mode switch orchestration |
frameworks/native/libs/gui/include/gui/DisplayEventReceiver.h |
219 | VSYNC event delivery to apps |
frameworks/base/core/java/android/view/Choreographer.java |
1,714 | App frame callback coordinator |
frameworks/base/core/java/android/view/ViewRootImpl.java |
13,827 | View traversal + VSYNC integration |
frameworks/base/core/java/android/view/Surface.java |
1,494 | Per-surface frame rate API |
Part XII: Rotation, Foldables, Keyguard, Starting Windows, and App Compatibility
58. Screen Rotation and Orientation
58.1 Overview
Screen rotation is one of the most complex visual transitions in the Android window system. When a device rotates, every window on the display must be repositioned, resized, and redrawn to match the new orientation — all while maintaining visual continuity for the user. The rotation subsystem spans sensor hardware, orientation policy, matrix transforms, and transition animation, coordinating tightly with the display pipeline (see Section 21), insets framework (see Section 23), and shell transitions (see Section 15).
Android defines four discrete rotation values:
| Constant | Value | Description |
|---|---|---|
ROTATION_0 |
0 | Natural portrait (phone) or landscape (tablet) orientation |
ROTATION_90 |
1 | Rotated 90 degrees counter-clockwise |
ROTATION_180 |
2 | Inverted from natural orientation |
ROTATION_270 |
3 | Rotated 270 degrees counter-clockwise (90 clockwise) |
Rotation can be triggered by multiple sources: the accelerometer sensor, an application requesting a specific orientation, a lid switch event, a dock connection, a display overlay change, or a fold state transition on foldable devices. The DisplayRotation class serves as the central arbitrator, collecting inputs from all sources and resolving them into a single rotation value that is then applied to the display and all its windows.
Two fundamentally different strategies exist for applying rotation:
- Non-seamless rotation: The screen blacks out, all windows are relaid out at the new size, and the screen is unblacked. This is the traditional approach and remains the default for most scenarios.
- Seamless rotation: A counter-rotation matrix is applied to each window so it appears to stay in place while the display rotates underneath. Windows individually redraw at the new orientation and shed their counter-rotation transform. This avoids the blackout but requires careful coordination.
The subsections below trace the full rotation pipeline from sensor input through final frame rendering.
58.2 DisplayRotation — Core Engine
DisplayRotation (2,255 lines) is the central rotation management class, one instance per DisplayContent. It owns the rotation policy state, receives input from sensors and system events, and drives the rotation update through the window manager.
classDiagram
class DisplayRotation {
-int mCurrentAppOrientation
-int mRotation
-int mLandscapeRotation
-int mPortraitRotation
-int mDeferredRotationPauseCount
-boolean mUserRotationMode
-int mUserRotation
-int mFixedToUserRotation
-OrientationListener mOrientationListener
-FoldController mFoldController
+updateRotationUnchecked(boolean forceUpdate) boolean
+rotationForOrientation(int orientation, int lastRotation) int
+setCurrentOrientation(int newOrientation) void
+freezeRotation(int rotation) void
+thawRotation() void
+pauseRotationLocked() void
+resumeRotationLocked() void
+isRotatingSeamlessly() boolean
+isFixedToUserRotation() boolean
}
class FoldController {
-boolean mIsHalfFolded
-int mHalfFoldedRotation
+shouldRevertOverride() boolean
+adjustRotation(int proposedRotation) int
}
class WindowOrientationListener {
-SensorEventListener mSensorJudge
-int mCurrentRotation
-int mProposedRotation
+enable(boolean clearProposal) void
+disable() void
+getProposedRotation() int
}
class DisplayRotationCoordinator {
-int mDefaultDisplayCurrentRotation
-Runnable mDefaultDisplayRotationChangedCallback
+getDefaultDisplayCurrentRotation() int
+setDefaultDisplayRotationChangedCallback(Runnable) void
}
class DisplayRotationReversionController {
-SparseIntArray mSlotValues
+beforeOverrideApplied(int slot, int rotation) void
+revertOverride(int slot) void
}
DisplayRotation --> FoldController : inner class
DisplayRotation --> WindowOrientationListener : sensor input
DisplayRotation --> DisplayRotationCoordinator : cross-display sync
DisplayRotation --> DisplayRotationReversionController : reversion mgmt
DisplayRotation --> DisplayContent : updates rotation
The key entry point is updateRotationUnchecked(). When called, it:
- Queries
rotationForOrientation()to compute the desired rotation. - Compares the result to the current rotation.
- If changed, initiates the rotation update through
DisplayContent.updateOrientation(), which triggersapplyRotationAndFinishFixedRotation(). - Returns
trueif rotation actually changed.
The mDeferredRotationPauseCount mechanism allows callers to temporarily suppress rotation updates during critical transitions. Each call to pauseRotationLocked() increments the counter; resumeRotationLocked() decrements it. Rotation updates are blocked while the count is greater than zero, ensuring that animations or multi-step layout operations are not disrupted by an interleaved rotation.
58.3 Rotation Resolution Algorithm
The rotationForOrientation() method is the policy core of the rotation subsystem. It takes the requested orientation (from the topmost activity or system policy) and the last applied rotation, then resolves the final rotation by considering sensor data, user preferences, forced overrides, lid state, dock mode, and display overlays.
flowchart TD
A[rotationForOrientation called] --> B{Is fixed to user rotation?}
B -->|Yes| C[Return mUserRotation]
B -->|No| D{Is rotation forced by override?}
D -->|Yes| E[Return forced rotation]
D -->|No| F{Check lid switch state}
F -->|Lid closed| G[Return lid-closed rotation]
F -->|Lid open or N/A| H{Check dock mode}
H -->|Docked| I[Return dock rotation]
H -->|Not docked| J{Evaluate requested orientation}
J --> K{SCREEN_ORIENTATION_LANDSCAPE}
J --> L{SCREEN_ORIENTATION_PORTRAIT}
J --> M{SCREEN_ORIENTATION_SENSOR}
J --> N{SCREEN_ORIENTATION_UNSPECIFIED}
J --> O{SCREEN_ORIENTATION_LOCKED}
K --> P[Return mLandscapeRotation]
L --> Q[Return mPortraitRotation]
M --> R{Sensor has proposal?}
R -->|Yes| S[Return sensor proposed rotation]
R -->|No| T[Return lastRotation]
N --> U{User rotation locked?}
U -->|Yes| V[Return mUserRotation]
U -->|No| W{Sensor has proposal?}
W -->|Yes| X[Return sensor proposed rotation]
W -->|No| Y[Return ROTATION_0]
O --> Z[Return lastRotation]
The resolution follows a strict priority order:
- Fixed-to-user rotation — highest priority, system-level lock set via
setFixedToUserRotation(). - Forced rotation — set by system overlay or policy override such as camera compatibility.
- Lid switch — physical lid state on clamshell devices.
- Dock mode — car or desk dock orientations defined by config.
- App-requested orientation — the
screenOrientationattribute from the top activity. - Sensor proposal — accelerometer or game rotation vector data filtered through
WindowOrientationListener. - User preference — the auto-rotate toggle and user-set rotation in Settings.
- System default —
ROTATION_0when no other source provides a value.
Within app-requested orientations, the following key values are handled:
| Orientation | Behavior |
|---|---|
SCREEN_ORIENTATION_UNSPECIFIED |
Follow sensor if auto-rotate on, else user rotation |
SCREEN_ORIENTATION_LANDSCAPE |
Lock to device landscape rotation |
SCREEN_ORIENTATION_PORTRAIT |
Lock to device portrait rotation |
SCREEN_ORIENTATION_SENSOR |
Follow sensor freely among all four rotations |
SCREEN_ORIENTATION_SENSOR_LANDSCAPE |
Follow sensor but only landscape orientations |
SCREEN_ORIENTATION_SENSOR_PORTRAIT |
Follow sensor but only portrait orientations |
SCREEN_ORIENTATION_LOCKED |
Remain at whatever rotation is currently applied |
SCREEN_ORIENTATION_NOSENSOR |
Ignore sensor, use preferred orientation |
SCREEN_ORIENTATION_FULL_SENSOR |
Follow sensor including 180-degree rotation |
The FoldController may further adjust the resolved rotation on foldable devices in half-folded posture (see Section 58.9).
58.4 Sensor-Based Orientation Detection
WindowOrientationListener (1,371 lines) bridges the hardware sensor subsystem and the rotation policy engine. It listens for sensor events, filters noisy data through a multi-stage judge, and produces a proposed rotation value that DisplayRotation consumes.
Two judge implementations exist:
| Judge | Sensor Type | Approach |
|---|---|---|
AccelSensorJudge |
TYPE_ACCELEROMETER | Computes tilt angle from raw acceleration vectors, applies threshold bands and stability timers |
OrientationSensorJudge |
TYPE_GAME_ROTATION_VECTOR | Uses quaternion-based orientation from sensor fusion, simpler decision logic |
The AccelSensorJudge is the more complex and historically default implementation. It operates as a state machine with the following proposal states:
stateDiagram-v2
[*] --> Evaluating : sensor event received
Evaluating --> Evaluating : tilt outside threshold or unstable
Evaluating --> Pending : tilt within threshold and stable
Pending --> Pending : stability timer not expired
Pending --> Settled : stability timer expired
Settled --> Evaluating : new sensor event differs
Settled --> Settled : sensor confirms current proposal
Evaluating: The judge is processing sensor data but has not yet committed to a rotation proposal. The tilt angle must fall within the acceptance range (approximately plus or minus 40 degrees from the target orientation axis) and the device must be sufficiently stable (not being shaken or in free fall).
Pending: The sensor data consistently points to a new rotation, but the stability timer has not yet expired. This timer prevents spurious rotations from brief tilts. The timer duration varies based on whether the user is currently touching the screen (touch-active state shortens the timer, as intentional rotation during interaction is more likely).
Settled: The proposal is committed and delivered to DisplayRotation via mProposedRotation. The value remains settled until sensor data indicates a different orientation.
Key thresholds in AccelSensorJudge:
| Parameter | Value | Purpose |
|---|---|---|
| Tilt tolerance | approximately 40 degrees | Angular range around each orientation axis |
| Stability timer (normal) | 200-500 ms | Debounce time when screen not touched |
| Stability timer (touch) | 100-300 ms | Shorter debounce during active touch |
| Flat threshold | Near-zero gravity vector | Suppresses rotation when device is flat |
| Swing threshold | Excessive acceleration delta | Suppresses rotation during rapid movement |
The OrientationSensorJudge delegates most filtering to the sensor HAL and applies simpler thresholds on the quaternion output, making it more responsive but also more dependent on hardware quality.
58.5 Seamless vs Non-Seamless Rotation
Rotation can be applied in two fundamentally different ways. The choice between them affects visual quality, performance cost, and application compatibility.
| Aspect | Non-Seamless | Seamless |
|---|---|---|
| Visual effect | Screen blacks out briefly during transition | Windows counter-rotate smoothly, no blackout |
| Window relayout | All windows relaid out simultaneously | Windows relayout individually as they redraw |
| Duration | One full frame of blackout minimum | Varies per window redraw speed |
| App requirement | None, works with all apps | App must opt in via ROTATION_ANIMATION_SEAMLESS or system policy |
| Rotation pairs | Any rotation change | Default: only 0 to 180 or 90 to 270 (180-degree flips) |
| Wallpaper | Redrawn at new rotation | Counter-rotated with display |
| IME | Redrawn at new rotation | Faded out and back in via ACTION_TOGGLE_IME |
| System bars | Redrawn at new rotation | Faded via AsyncRotationController |
| SurfaceFlinger | Full display recomposition | Per-window transform update |
The rotation pair restriction exists because 90-degree seamless rotations require complex geometry transforms that are visually jarring when windows have asymmetric content. A 180-degree rotation is a simple point reflection that preserves aspect ratio.
sequenceDiagram
participant DR as DisplayRotation
participant DC as DisplayContent
participant SF as SurfaceFlinger
participant ARC as AsyncRotationController
participant SR as SeamlessRotator
participant App as Application
Note over DR: Rotation change detected
DR->>DC: updateOrientation()
alt Non-Seamless Path
DC->>SF: setDisplayProjection(newRotation)
DC->>DC: Freeze screen - black overlay
DC->>DC: Relayout all windows at new size
App->>DC: Redraw complete
DC->>DC: Unfreeze screen - remove overlay
else Seamless Path
DC->>ARC: Create AsyncRotationController
DC->>SF: setDisplayProjection(newRotation)
loop For each visible window
ARC->>ARC: Classify window action
alt ACTION_SEAMLESS
ARC->>SR: Create SeamlessRotator for window
SR->>SF: Apply counter-rotation matrix
else ACTION_FADE
ARC->>SF: Set window alpha to 0
else ACTION_TOGGLE_IME
ARC->>SF: Hide IME window
end
end
App->>DC: Window redrawn at new rotation
DC->>ARC: handleFinishDrawing(window)
ARC->>SR: finish() - remove counter-rotation
Note over ARC: Repeat until all windows redrawn
ARC->>ARC: completeAll() - cleanup
end
The seamless path involves AsyncRotationController classifying every visible window into an action type and SeamlessRotator applying the appropriate counter-rotation matrix. Windows that cannot be seamlessly rotated (such as system bars) are faded out instead, then faded back in once they have redrawn at the new orientation.
58.6 SeamlessRotator — Matrix Transform
SeamlessRotator (134 lines) is a lightweight utility that computes and applies a counter-rotation transform to a single window’s SurfaceControl. The goal is to make the window appear to remain at its old rotation while the underlying display has already rotated.
The core mechanism uses CoordinateTransforms.transformLogicalToPhysicalCoordinates() and transformPhysicalToLogicalCoordinates() to produce the affine transform encoding the difference between old and new rotations. This transform is applied to the window’s surface transaction so that the pixel content, rendered at the old orientation, appears correctly on the newly rotated display.
flowchart LR
subgraph Before Rotation
A[Display at ROTATION_0]
B[Window at ROTATION_0]
end
subgraph Display Rotates
C[Display changes to ROTATION_90]
D[Window still has ROTATION_0 content]
end
subgraph Counter-Rotation Applied
E[SeamlessRotator computes delta matrix]
F[Matrix: rotate -90 degrees plus translate]
G[Window appears upright on rotated display]
end
subgraph Window Redraws
H[App draws at ROTATION_90]
I[SeamlessRotator.finish removes transform]
J[Window native at new rotation]
end
A --> C
B --> D
D --> E
E --> F
F --> G
G --> H
H --> I
I --> J
Key methods:
unrotate()— Applies the counter-rotation to aSurfaceControl.Transaction. It sets the matrix once and the window remains visually stable as the display rotates.finish()— Called when the window has redrawn at the new rotation. Removes the counter-rotation transform from theSurfaceControl, allowing the window to display its freshly rendered content natively.
The rotation matrix for a 90-degree counter-clockwise counter-rotation on a display of width w and height h:
| From / To | ROTATION_0 | ROTATION_90 | ROTATION_180 | ROTATION_270 |
|---|---|---|---|---|
| ROTATION_0 | Identity | Rotate(-90) + Translate(0, w) | Rotate(180) + Translate(w, h) | Rotate(90) + Translate(h, 0) |
| ROTATION_90 | Rotate(90) + Translate(h, 0) | Identity | Rotate(-90) + Translate(0, w) | Rotate(180) + Translate(w, h) |
The matrix accounts for both the angular rotation and the translation required because rotation around the origin shifts the window outside the visible display area. The translation component repositions the window back into the display bounds after rotation.
58.7 AsyncRotationController — Transition Management
AsyncRotationController (765 lines) manages the visual gap between the moment a rotation is applied to the display and the moment all windows have redrawn at the new orientation. During this gap, windows still showing old-rotation content must be handled to avoid visual artifacts.
The controller is created when a shell transition involves rotation. It classifies each visible window into one of three action types:
| Action | Constant | Behavior | Typical Targets |
|---|---|---|---|
| Seamless | ACTION_SEAMLESS |
Counter-rotation matrix applied via SeamlessRotator |
App windows that opt in |
| Fade | ACTION_FADE |
Window faded to transparent, faded back after redraw | Status bar, navigation bar, non-seamless windows |
| Toggle IME | ACTION_TOGGLE_IME |
IME hidden during rotation, reshown after | Input method windows |
Window targets are tracked in mTargetWindowTokens, a HashMap<WindowToken, Integer> mapping each window token to its assigned action.
The controller operates within four operation type contexts:
| Operation Type | Trigger | Description |
|---|---|---|
LEGACY |
Pre-shell-transition rotation | Compatibility path for legacy rotation handling |
APP_SWITCH |
Activity switch with rotation | App-to-app transition where new app has different orientation |
CHANGE |
Display change with rotation | Configuration change triggers rotation |
CHANGE_MAY_SEAMLESS |
Potential seamless rotation | Display change where seamless rotation is possible |
flowchart TD
A[Rotation transition starts] --> B[Create AsyncRotationController]
B --> C[Enumerate visible windows]
C --> D{For each window}
D --> E{Can rotate seamlessly?}
E -->|Yes| F[Assign ACTION_SEAMLESS]
E -->|No| G{Is IME window?}
G -->|Yes| H[Assign ACTION_TOGGLE_IME]
G -->|No| I[Assign ACTION_FADE]
F --> J[Apply SeamlessRotator transform]
H --> K[Hide IME]
I --> L[Fade window to alpha 0]
J --> M{Window redraws?}
K --> M
L --> M
M -->|Yes| N[handleFinishDrawing]
N --> O{Action type?}
O -->|SEAMLESS| P[SeamlessRotator.finish - remove transform]
O -->|FADE| Q[Restore alpha to 1]
O -->|TOGGLE_IME| R[Restore IME visibility]
P --> S{All windows done?}
Q --> S
R --> S
S -->|No| M
S -->|Yes| T[completeAll - cleanup controller]
The handleFinishDrawing() method is the per-window completion callback. When a window reports that it has finished drawing at the new rotation, the controller removes its transform (seamless) or restores its visibility (fade/IME). Once all tracked windows have completed, completeAll() tears down the controller and finalizes the transition.
A timeout mechanism ensures that the controller does not block indefinitely if a window fails to redraw. After the timeout expires, any remaining windows are force-completed.
58.8 FixedRotationTransformState — Orientation Launch
When an activity launches with a different orientation than the current display rotation, the system faces a chicken-and-egg problem: the activity needs to draw at its target rotation, but the display has not rotated yet (and may not rotate until the activity is visible). FixedRotationTransformState, an inner class of WindowToken, solves this by creating a simulated rotated display environment for the launching activity.
sequenceDiagram
participant Launcher as Current Activity ROTATION_0
participant WMS as WindowManagerService
participant FRT as FixedRotationTransformState
participant NewApp as New Activity wants ROTATION_90
participant DC as DisplayContent
Launcher->>WMS: startActivity with landscape orientation
WMS->>FRT: Create FixedRotationTransformState for ROTATION_90
FRT->>FRT: Build rotated DisplayInfo
FRT->>FRT: Build rotated DisplayFrames
FRT->>FRT: Build rotated InsetsState
FRT->>FRT: Build rotated Configuration
WMS->>NewApp: Provide rotated configuration
NewApp->>NewApp: Layout and draw at ROTATION_90
Note over NewApp: Activity renders in landscape while display is still portrait
NewApp->>WMS: First frame drawn
WMS->>DC: applyRotationAndFinishFixedRotation()
DC->>DC: Rotate display to ROTATION_90
FRT->>FRT: disassociate() - remove fixed rotation
Note over DC: Display now matches activity rotation
The FixedRotationTransformState holds independent copies of:
- DisplayInfo — with width/height swapped and rotation set to the target value
- DisplayFrames — computed for the target rotation dimensions
- InsetsState — insets recalculated for the target orientation (see Section 23)
- Configuration — screen width/height dp, orientation, and layout direction adjusted
The transform() method applies a rotation transform to window frames, converting coordinates from the fixed rotation space back to the current display space. This allows the window to be correctly positioned on the still-unrotated display while its content is rendered at the target rotation.
When the activity’s first frame is drawn and the transition is ready, applyRotationAndFinishFixedRotation() on DisplayContent rotates the display to match, and disassociate() removes the fixed rotation state. The activity’s windows are now natively at the correct rotation without ever having been visible at the wrong one.
This mechanism is essential for avoiding orientation flicker during app launches, especially when transitioning from a portrait app to a landscape app or vice versa.
58.9 Foldable Rotation — FoldController
The FoldController inner class within DisplayRotation handles rotation adjustments specific to foldable devices in half-folded (tabletop or laptop) posture. When a foldable device is partially folded, the usable display area changes and certain rotations become inappropriate.
stateDiagram-v2
[*] --> Flat : device fully open
Flat --> HalfFolded : hinge angle enters half-fold range
HalfFolded --> Flat : hinge angle exits half-fold range
HalfFolded --> FullyFolded : hinge angle below threshold
FullyFolded --> Flat : device opened
Flat --> Flat : rotation follows normal policy
state HalfFolded {
[*] --> AdjustRotation
AdjustRotation --> LockToPortrait : content on top half
AdjustRotation --> LockToLandscape : content spans both halves
}
In half-folded posture, the FoldController:
- Sets
mIsHalfFoldedto true. - Records the preferred half-folded rotation in
mHalfFoldedRotation. - Calls
adjustRotation()on proposals from the sensor or app to ensure they are compatible with the half-folded state. - Reports via
shouldRevertOverride()whether a previously applied rotation override should be reverted when exiting the half-folded state.
The half-folded rotation preference typically favors orientations that keep content visible on the top half of the display, avoiding orientations where the primary content area would be occluded by the fold. The exact behavior is device-specific and configured through display policy overlays.
Integration with DisplayRotationReversionController ensures that rotation overrides applied during half-fold are cleanly reverted when the device returns to a flat posture.
58.10 Rotation Reversion System
DisplayRotationReversionController (148 lines) manages a slot-based system for saving and restoring rotation overrides. When a subsystem temporarily forces a specific rotation (for example, camera compatibility forcing landscape), the reversion controller records the original rotation so it can be restored later.
| Reversion Slot | Purpose | Typical User |
|---|---|---|
| Camera compatibility | Revert forced rotation when camera session ends | Camera compat policy in DisplayRotation |
| Half-fold override | Revert fold-related rotation when posture changes | FoldController |
| Display overlay | Revert overlay-forced rotation on overlay removal | Display overlay system |
Key methods:
beforeOverrideApplied(int slot, int rotation)— Saves the current rotation into the specified slot before the override takes effect.revertOverride(int slot)— Restores the saved rotation from the slot and clears it.
The slot-based design allows multiple independent subsystems to save and revert rotation without interfering with each other. Each slot operates independently; reverting one slot does not affect another.
58.11 Cross-References
- Section 15 — Shell Transitions:
AsyncRotationControlleris created within the shell transition framework. Rotation transitions are a specific transition type that the transition system orchestrates. - Section 21 — DisplayManagerService: Display rotation values are propagated through
DisplayManagerServiceto all display consumers.DisplayRotationCoordinator(101 lines) synchronizes the default display rotation to secondary displays viagetDefaultDisplayCurrentRotation(). - Section 23 — Insets System:
FixedRotationTransformStatecreates its ownInsetsStatefor the target rotation. Inset sources (status bar, navigation bar) must be recalculated when rotation changes. - Section 25 — Window-Display Relationship:
DisplayContent.updateOrientation()is the bridge between rotation decision and window layout. All windows in the display are relaid out when rotation changes. - Section 32 — Configuration Changes (if applicable): Rotation changes trigger configuration changes for all activities on the affected display, potentially causing activity recreation.
58.12 Key Source Files
| File | Path | Lines | Role |
|---|---|---|---|
| DisplayRotation | frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java |
2,255 | Core rotation engine, policy resolution, rotation update orchestration |
| AsyncRotationController | frameworks/base/services/core/java/com/android/server/wm/AsyncRotationController.java |
765 | Manages window visibility and transforms during rotation transitions |
| SeamlessRotator | frameworks/base/services/core/java/com/android/server/wm/SeamlessRotator.java |
134 | Computes and applies counter-rotation matrix transforms to surfaces |
| FixedRotationTransformState | frameworks/base/services/core/java/com/android/server/wm/WindowToken.java (inner class) |
N/A | Simulated rotated display environment for cross-orientation app launch |
| WindowOrientationListener | frameworks/base/services/core/java/com/android/server/wm/WindowOrientationListener.java |
1,371 | Sensor-based orientation detection with accelerometer and game rotation vector judges |
| DisplayRotationCoordinator | frameworks/base/services/core/java/com/android/server/wm/DisplayRotationCoordinator.java |
101 | Synchronizes default display rotation to secondary displays |
| DisplayRotationReversionController | frameworks/base/services/core/java/com/android/server/wm/DisplayRotationReversionController.java |
148 | Slot-based rotation override save and restore |
| DisplayContent | frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java |
N/A | Owns DisplayRotation, calls updateOrientation() and applyRotationAndFinishFixedRotation() |
59. Foldable Display Support
59.1 Overview
Foldable display support in AOSP is built on a Device State abstraction that decouples physical hardware events (hinge angle sensors, hall sensors) from the display and window management layers. The architecture spans four principal layers:
- Hardware Detection —
FoldableDeviceStateProviderreads hinge angle and lid switch sensors, translating raw readings into candidate device states. - State Management —
DeviceStateManagerServicemaintains a three-stage pipeline (Requested → Pending → Committed) that serialises state transitions, enforces policy, and resolves conflicts between provider-reported base states and app-initiated overrides. - Display Mapping —
LogicalDisplayMapperperforms the physical-to-logical display swap on fold/unfold, enabling or disabling the inner and outer panels with a 500 ms transition timeout. - Window Manager Integration —
DeviceStateControllerbridges committed state changes into WM policy decisions, notifying listeners that govern task positioning, rotation, and split-screen behaviour.
The design deliberately avoids hard-coding any particular form factor. A DeviceState
carries an integer identifier (0–10 000) and a property set, while a policy class such as
BookStyleDeviceStatePolicy maps those identifiers to physical postures with angle
thresholds and transition rules. OEMs replace the policy without touching the pipeline.
Cross-references: Display enumeration (§21 DisplayManagerService), multi-display topology (§32 Multi-Display), rotation arbitration (§58 Rotation), window-display binding (§25 Window-Display).
59.2 Device State Data Model
DeviceState.java (534 lines) defines the immutable value object that flows through the
entire pipeline.
State Identifier Range
| Constant | Value | Purpose |
|---|---|---|
MIN_IDENTIFIER |
0 | Lowest legal state id |
MAX_IDENTIFIER |
10 000 | Highest legal state id |
Identifiers are opaque integers — the system imposes no semantic meaning. Policy classes assign meaning by mapping identifiers to named postures (see §59.5).
Device State Properties
Properties are bit-flags queried via hasProperty(int property).
| Property Constant | Meaning |
|---|---|
PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER |
State uses the inner (foldable) display as primary |
PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER |
State uses the outer (cover) display as primary |
PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE |
Entering this state wakes the device |
PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP |
Entering this state puts the device to sleep |
These properties drive downstream behaviour in LogicalDisplayMapper (which display to
enable) and in power management (wake/sleep on posture change).
59.3 DeviceStateManagerService — State Pipeline
DeviceStateManagerService.java (1 634 lines) implements a three-stage pipeline that
prevents race conditions between hardware events and app overrides.
stateDiagram-v2
[*] --> Requested : requestState() or\nProvider callback
state "Requested" as Requested
state "Pending" as Pending
state "Committed" as Committed
Requested --> Pending : Validation passed,\npolicy allows transition
Requested --> [*] : Rejected / cancelled\n(cancelStateRequest)
Pending --> Committed : All conditions met\n(commitPendingState)
Pending --> [*] : Superseded by\nnewer request
Committed --> [*] : State active,\ncallbacks dispatched
Stage details:
| Stage | Entry Condition | Key Logic |
|---|---|---|
| Requested | requestState() called by provider listener or app override |
Validates identifier range; checks caller permissions; records in mProcessRecords |
| Pending | Policy approves the transition | Waits for thermal clearance, display readiness, animation completion |
| Committed | commitPendingState() invoked |
Updates current state; dispatches onDeviceStateChanged to all registered callbacks; notifies DisplayManagerService |
The service distinguishes two state sources:
- Base state — reported by
DeviceStateProviderListenerfrom hardware sensors. This is the ground truth of the physical posture. - Override state — requested by an application via
DeviceStateManager.requestState(). Overrides take precedence while active and are tracked per-process inmProcessRecords.
When an override is active, the committed state reflects the override. When the override
is cancelled (explicitly via cancelStateRequest() or implicitly when the requesting
process dies), the committed state reverts to the current base state.
59.4 FoldableDeviceStateProvider — Hardware Detection
FoldableDeviceStateProvider.java (721 lines) fuses two sensor inputs into a candidate
device state.
flowchart TB
subgraph Sensors
HA[Hinge Angle Sensor\nTYPE_HINGE_ANGLE]
HS[Hall Sensor\nMagnetic Lid Switch]
end
HA -->|onSensorChanged| AP[Angle Processing]
HS -->|onSwitchStateChanged| LP[Lid Processing]
AP --> SM{State\nMapping}
LP --> SM
SM -->|Tent mode?\nhinge > 90 deg\nscreen facing down| TM[Tent Mode State]
SM -->|Normal posture| NP[Mapped Posture State]
TM --> TC{Thermal\nCheck}
NP --> TC
TC -->|Throttling active| BL[Transition Blocked]
TC -->|Clear| PC{Power State\nAwareness}
PC --> CB[DeviceStateProviderListener\ncallback to DMS]
Sensor fusion rules:
| Input | Signal | Effect |
|---|---|---|
| Hinge angle sensor | Continuous angle in degrees | Primary posture discriminator (see §59.5 thresholds) |
| Hall sensor | Binary open/closed | Overrides angle-based detection when lid is magnetically latched (CLOSED state) |
| Tent mode | Hinge > 90 deg AND accelerometer indicates screen-down | Triggers tent/tabletop posture |
| Thermal state | System thermal status | Can block state transitions during thermal throttling to prevent display damage |
| Power state | Charger connect/disconnect | Adjusts behaviour — e.g., may prevent sleep on fold while charging |
The provider reports candidate states to DeviceStateManagerService through
DeviceStateProviderListener. It never commits states directly — the service pipeline
controls commitment.
59.5 BookStyleDeviceStatePolicy — State Definitions
BookStyleDeviceStatePolicy.java (299 lines) defines six discrete postures for
book-style foldable devices.
Posture Table
| State Name | Identifier | Hinge Angle Range | Primary Display | Notes |
|---|---|---|---|---|
| CLOSED | 0 | < 5 deg | Outer | Device folded shut; outer cover display active |
| HALF_OPENED | 1 | 5 deg – 125 deg | Inner | Laptop/tent posture; inner display active |
| OPENED | 2 | > 125 deg | Inner | Fully flat or nearly flat |
| REAR_DISPLAY | 3 | App override | Outer | Inner display folded back; outer display used as viewfinder |
| CONCURRENT | 4 | App override | Both | Dual-screen mode; both displays active simultaneously |
| RDM_V2 | 5 | App override | Outer | Rear Display Mode v2 with enhanced camera features |
State Transition Diagram
stateDiagram-v2
CLOSED : CLOSED (0)\n< 5 deg
HALF_OPENED : HALF_OPENED (1)\n5-125 deg
OPENED : OPENED (2)\n> 125 deg
REAR_DISPLAY : REAR_DISPLAY (3)\nApp override
CONCURRENT : CONCURRENT (4)\nApp override
RDM_V2 : RDM_V2 (5)\nApp override
CLOSED --> HALF_OPENED : Hinge opens past 5 deg
HALF_OPENED --> CLOSED : Hinge closes below 5 deg
HALF_OPENED --> OPENED : Hinge opens past 125 deg
OPENED --> HALF_OPENED : Hinge closes below 125 deg
OPENED --> REAR_DISPLAY : App requests override
OPENED --> CONCURRENT : App requests override
HALF_OPENED --> CONCURRENT : App requests override
OPENED --> RDM_V2 : App requests override
REAR_DISPLAY --> OPENED : Override cancelled
CONCURRENT --> OPENED : Override cancelled
RDM_V2 --> OPENED : Override cancelled
REAR_DISPLAY --> CLOSED : Physical fold
CONCURRENT --> CLOSED : Physical fold
State availability is gated by power state, thermal conditions, and whether an app has requested an override. The policy class exposes per-state availability to the service so that unavailable states are rejected at the Requested stage.
59.6 State Transition Rules
BookStyleStateTransitions.java (722 lines) codifies the legal transitions and applies
hysteresis and debounce to prevent rapid state flipping.
Transition Matrix
The following matrix shows permitted direct transitions (Y = allowed, - = blocked).
| From \ To | CLOSED | HALF_OPENED | OPENED | REAR_DISPLAY | CONCURRENT | RDM_V2 |
|---|---|---|---|---|---|---|
| CLOSED | - | Y | - | - | - | - |
| HALF_OPENED | Y | - | Y | - | Y | - |
| OPENED | - | Y | - | Y | Y | Y |
| REAR_DISPLAY | Y | - | Y | - | - | - |
| CONCURRENT | Y | - | Y | - | - | - |
| RDM_V2 | Y | - | Y | - | - | - |
Key rules:
- CLOSED can only transition to HALF_OPENED — a direct jump to OPENED is disallowed because the hinge must physically pass through intermediate angles.
- Override states (REAR_DISPLAY, CONCURRENT, RDM_V2) can always return to CLOSED on physical fold, ensuring the user can always close the device.
- Override states cannot transition to other override states directly; the current override must be cancelled first.
Hysteresis and Debounce
| Mechanism | Purpose | Typical Value |
|---|---|---|
| Angle hysteresis | Prevents oscillation at threshold boundaries | ~3–5 deg deadband around each threshold |
| Debounce timer | Suppresses transient angle spikes during rapid movement | Per-transition type; prevents sub-second flips |
| Thermal hold | Blocks transitions when thermal state exceeds threshold | Duration of thermal event |
59.7 LogicalDisplayMapper — Display Swapping
LogicalDisplayMapper.java (1 610 lines) is responsible for enabling/disabling physical
displays in response to device state changes, maintaining the mapping from physical panel
IDs to logical display IDs.
Fold/Unfold Sequence
sequenceDiagram
participant DMS as DeviceStateManager<br/>Service
participant LDM as LogicalDisplay<br/>Mapper
participant DD as DisplayDevice<br/>(Inner / Outer)
participant WM as WindowManager<br/>Service
participant App as Application
DMS->>LDM: setDeviceStateLocked(newState)
LDM->>LDM: Look up mDisplayIdToLayoutMap<br/>for new state
alt Fold (OPENED to CLOSED)
LDM->>DD: Disable inner display
LDM->>DD: Enable outer display
LDM->>LDM: Remap DEFAULT_DISPLAY_GROUP<br/>to outer panel
else Unfold (CLOSED to HALF_OPENED/OPENED)
LDM->>DD: Disable outer display
LDM->>DD: Enable inner display
LDM->>LDM: Remap DEFAULT_DISPLAY_GROUP<br/>to inner panel
end
LDM->>LDM: performLayoutLocked()
LDM->>WM: Notify display changed
alt State has TRIGGER_WAKE property
LDM->>WM: Wake device
else State has TRIGGER_SLEEP property
LDM->>WM: Sleep device
end
Note over LDM: 500ms transition timeout<br/>guards against stuck swaps
WM->>App: onConfigurationChanged()
App->>App: Handle display metrics change
Key implementation details:
| Aspect | Detail |
|---|---|
| Display group | Fold/unfold always targets DEFAULT_DISPLAY_GROUP — the primary user-facing display |
| Transition timeout | 500 ms maximum for the display swap; if exceeded, the swap is forced to complete |
| Wake states | Tracked in mDeviceStatesOnWhichToWakeUp; typically HALF_OPENED and OPENED |
| Sleep states | Tracked in mDeviceStatesOnWhichToSleep; typically CLOSED |
| Layout recomputation | performLayoutLocked() recalculates all logical display positions and sizes after the swap |
| Physical-to-logical map | mDisplayIdToLayoutMap persists the mapping so that display IDs remain stable across fold cycles |
59.8 DeviceStateController — WM Integration
DeviceStateController.java (273 lines) bridges the device state system into the Window
Manager, translating numeric state identifiers into WM-specific enums and dispatching
notifications to WM components.
WM Device State Enum
| Enum Value | Integer | Mapping |
|---|---|---|
UNKNOWN |
0 | Initial / error state |
OPEN |
1 | Maps to OPENED (2) or HALF_OPENED (1) |
FOLDED |
2 | Maps to CLOSED (0) |
HALF_FOLDED |
3 | Maps to HALF_OPENED (1) when distinguished |
REAR |
4 | Maps to REAR_DISPLAY (3) |
CONCURRENT |
5 | Maps to CONCURRENT (4) |
Callback Flow
flowchart TB
DMS[DeviceStateManagerService] -->|commitPendingState| DM[DisplayManager]
DM -->|onDeviceStateReceivedByDisplayManager| DSC[DeviceStateController]
DSC -->|Update mCurrentDeviceState| DSC
DSC -->|notify| L1[DisplayRotationController\nRotation policy per state]
DSC -->|notify| L2[TaskPositioningController\nSplit-screen constraints]
DSC -->|notify| L3[DesktopModeController\nFreeform window policy]
DSC -->|notify| L4[LetterboxUiController\nLetterbox adjustments]
L1 -->|§58| ROT[Rotation arbitration]
L2 -->|§32| MD[Multi-display layout]
L3 -->|§25| WD[Window-display binding]
The controller maintains mDeviceStateChangedListeners, a list of WM-internal consumers.
Each listener receives the new DeviceStateEnum and adjusts its policy accordingly. For
example, DisplayRotationController may lock rotation to landscape when HALF_FOLDED (to
support tabletop video playback), while TaskPositioningController may enforce side-by-side
split when in CONCURRENT mode.
59.9 Override and App-Initiated State Changes
Applications interact with the foldable state system through DeviceStateManager.java
(306 lines), the public SDK API.
Requesting an override:
DeviceStateManager dsm = context.getSystemService(DeviceStateManager.class);
DeviceStateRequest request = DeviceStateRequest.newBuilder(REAR_DISPLAY_STATE_ID).build();
dsm.requestState(request, executor, callback);
The request enters the pipeline at the Requested stage. If the policy approves, the
override takes precedence over the base state from the hardware provider. The service
tracks the override against the calling process in mProcessRecords so that it is
automatically cancelled if the process dies.
Cancellation:
dsm.cancelStateRequest();
On cancellation (or process death), the committed state reverts to the current base state
reported by FoldableDeviceStateProvider.
Listening for state changes:
dsm.registerCallback(executor, new DeviceStateManager.DeviceStateCallback() {
@Override
public void onDeviceStateChanged(DeviceState state) { /* committed state */ }
@Override
public void onBaseStateChanged(DeviceState state) { /* physical posture */ }
});
The dual-callback design lets applications distinguish between the physical posture
(onBaseStateChanged) and the effective state after overrides (onDeviceStateChanged).
59.10 Cross-References
| Section | Relationship |
|---|---|
| §21 DisplayManagerService | LogicalDisplayMapper is owned by DMS; state changes flow through DMS dispatch |
| §25 Window-Display Binding | Display swap on fold/unfold triggers rebinding of windows to the new primary display |
| §32 Multi-Display | CONCURRENT state enables dual-display mode handled by multi-display topology |
| §58 Rotation | DeviceStateController feeds posture into rotation policy; tabletop mode may lock rotation |
59.11 Key Source Files
| File | Path | Lines | Role |
|---|---|---|---|
| DeviceStateManagerService.java | frameworks/base/services/core/java/com/android/server/devicestate/ |
1 634 | Three-stage state pipeline; override management |
| DeviceState.java | frameworks/base/core/java/android/hardware/devicestate/ |
534 | Immutable state data model with properties |
| DeviceStateManager.java | frameworks/base/core/java/android/hardware/devicestate/ |
306 | Public SDK API for state requests and callbacks |
| FoldableDeviceStateProvider.java | frameworks/base/services/foldables/devicestateprovider/src/com/android/server/policy/ |
721 | Hinge angle and hall sensor fusion |
| BookStyleDeviceStatePolicy.java | frameworks/base/services/foldables/devicestateprovider/src/com/android/server/policy/ |
299 | Six-state posture definitions and availability |
| BookStyleStateTransitions.java | frameworks/base/services/foldables/devicestateprovider/src/com/android/server/policy/ |
722 | Legal transition matrix, hysteresis, debounce |
| LogicalDisplayMapper.java | frameworks/base/services/core/java/com/android/server/display/ |
1 610 | Physical-to-logical display swap on fold/unfold |
| DeviceStateController.java | frameworks/base/services/core/java/com/android/server/wm/ |
273 | WM-side state tracking and listener dispatch |
60. Keyguard and Lock Screen Window Management
60.1 Overview
The keyguard (lock screen) is one of the most complex window management subsystems in AOSP, spanning three process boundaries: the system_server (WMS), SystemUI, and WM Shell. Its responsibilities include gating device access behind authentication, coordinating lock and unlock animations, handling activity occlusion of the lock screen, integrating with Always-On Display (AOD) and dream/screensaver modes, and maintaining independent keyguard state on each display in multi-display configurations.
The architecture follows a layered delegation model. KeyguardController in WMS owns the authoritative visibility and occlude state. It delegates UI rendering and user interaction to SystemUI’s KeyguardViewMediator via the KeyguardServiceDelegate binder bridge. Shell transitions, managed by KeyguardTransitionHandler, drive the animations for keyguard appearance, dismissal, occlusion, and unocclusion. A sleep token mechanism ensures that no activity resumes while the keyguard is showing, and a 10.5-second timeout guards against indefinite going-away states.
This section traces the full lifecycle of keyguard window management: from initial show through occlude, unlock, and AOD transitions, referencing the transition system described in Section 15 and Shell transition infrastructure in Section 19.
60.2 Keyguard Architecture Layers
The keyguard system is distributed across three AOSP process boundaries. WMS in system_server holds authoritative state. SystemUI owns the UI and authentication logic. WM Shell handles transition animations.
graph TB
subgraph system_server["system_server (WMS)"]
KC["KeyguardController"]
KSD["KeyguardServiceDelegate"]
KDS["KeyguardDisplayState\n(per display)"]
KC --> KDS
KC --> KSD
end
subgraph SystemUI["SystemUI Process"]
KVM["KeyguardViewMediator"]
KS["KeyguardService\n(IKeyguardService.Stub)"]
SBKV["StatusBarKeyguardViewManager"]
KVM --> KS
KVM --> SBKV
end
subgraph Shell["WM Shell"]
KTH["KeyguardTransitionHandler"]
KTA["KeyguardAnimationRunner"]
KTH --> KTA
end
KSD -- "IKeyguardService\n(Binder)" --> KS
KC -- "requestTransition()" --> KTH
KVM -- "IKeyguardStateCallback" --> KC
KTH -- "TransitionInfo" --> KC
subgraph WindowHierarchy["Window Hierarchy"]
SW["StatusBar Window\nTYPE_STATUS_BAR"]
NW["Navigation Bar Window"]
KW["Keyguard Window\n(part of NotificationShade)"]
end
SBKV --> KW
The critical design point is that KeyguardController never directly manipulates keyguard UI. It maintains a state model and coordinates transitions, while SystemUI renders the lock screen and WM Shell animates the transitions between keyguard states. The binder interface IKeyguardService bridges the process gap, and IKeyguardStateCallback flows state changes back to WMS.
60.3 KeyguardController — WMS Coordinator
KeyguardController is the WMS-side coordinator for all keyguard state. It lives within ActivityTaskManagerService and tracks whether the keyguard is showing, going away, or occluded on each display. It also manages the sleep token that prevents activities from resuming while the keyguard is visible.
State Machine
stateDiagram-v2
[*] --> Hidden : Device boots unlocked
[*] --> Showing : Device boots locked
Hidden --> Showing : setKeyguardShown(true, false)
Showing --> GoingAway : keyguardGoingAway(flags)
GoingAway --> Hidden : keyguardGone()
GoingAway --> Showing : timeout 10.5s
Showing --> Occluded : handleOccludedChanged\nFLAG_SHOW_WHEN_LOCKED activity
Occluded --> Showing : handleOccludedChanged\noccluding activity removed
Showing --> AODShowing : setKeyguardShown(true, true)
AODShowing --> Showing : setKeyguardShown(true, false)
AODShowing --> GoingAway : keyguardGoingAway(flags)
Hidden --> Hidden : no-op
Key Methods
| Method | Purpose |
|---|---|
setKeyguardShown(boolean showing, boolean aodShowing) |
Updates master visibility state; acquires or releases sleep token |
keyguardGoingAway(int flags) |
Initiates the going-away transition; sets mGoingAwayFlags |
keyguardGone() |
Completes the going-away transition; releases sleep token |
handleOccludedChanged(int displayId) |
Detects whether a SHOW_WHEN_LOCKED activity is on top; triggers occlude or unocclude transition |
isKeyguardOrAodShowing(int displayId) |
Returns true if keyguard or AOD is visible on the given display |
isKeyguardGoingAway() |
Returns true during the going-away window (max 10.5 seconds) |
canShowWhileLocked(ActivityRecord) |
Checks if an activity has FLAG_SHOW_WHEN_LOCKED or showWhenLocked attribute |
The going-away timeout (KEYGUARD_GOING_AWAY_TIMEOUT_MS = 10500) is a safety net. If keyguardGone() is not called within 10.5 seconds after keyguardGoingAway(), the system force-transitions back to the showing state, preventing a stuck invisible keyguard.
The sleep token is a critical correctness mechanism. When acquired (mSleepToken != null), the activity stack treats the display as if asleep — no activity is allowed to resume. This prevents a briefly visible activity from appearing behind a transparent or animating keyguard during unlock.
60.4 Per-Display Keyguard State
Each display maintains independent keyguard state through the KeyguardDisplayState inner class within KeyguardController. This enables scenarios where one display shows the keyguard while another is unlocked or occluded.
KeyguardDisplayState Fields
| Field | Type | Description |
|---|---|---|
mShowing |
boolean |
Whether the keyguard is visible on this display |
mAodShowing |
boolean |
Whether Always-On Display mode is active |
mOccluded |
boolean |
Whether a SHOW_WHEN_LOCKED activity occludes the keyguard |
mKeyguardGoingAway |
boolean |
Whether keyguard is in the going-away transition |
mGoingAwayFlags |
int |
Bitmask of going-away animation flags |
mSleepToken |
ActivityTaskManagerInternal.SleepToken |
Sleep token preventing activity resume while keyguard showing |
mRequestShowOnDisplay |
boolean |
Whether keyguard has been requested to show on this display |
mOccludedActivity |
ActivityRecord |
The activity currently occluding the keyguard (if any) |
mDisplayId |
int |
The display this state belongs to |
Going-Away Flags
| Flag Constant | Value | Effect |
|---|---|---|
GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT |
0x1 | Clears launcher snapshot to avoid stale thumbnails |
GOING_AWAY_FLAG_WITH_WALLPAPER |
0x2 | Wallpaper participates in the going-away animation |
GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS |
0x4 | Suppresses standard window animations during unlock |
GOING_AWAY_FLAG_SUBTLE_WINDOW_ANIMATIONS |
0x8 | Uses subtle (reduced motion) animations for unlock |
The per-display model is essential for automotive, desktop, and foldable form factors where multiple displays may be in different lock states simultaneously. See Section 60.11 for multi-display specifics.
60.5 Occlude and Unocclude Mechanism
An activity can display over the keyguard without unlocking the device by declaring FLAG_SHOW_WHEN_LOCKED (window flag) or the showWhenLocked activity attribute. Common use cases include incoming call screens, alarm UIs, and camera launch from the lock screen.
Occlude Sequence
sequenceDiagram
participant App as App Activity
participant ATMS as ActivityTaskManagerService
participant KC as KeyguardController
participant KSD as KeyguardServiceDelegate
participant KTH as KeyguardTransitionHandler
participant KVM as KeyguardViewMediator
App->>ATMS: startActivity() with SHOW_WHEN_LOCKED
ATMS->>ATMS: Activity becomes top of task
ATMS->>KC: evaluateOcclusionChange(displayId)
KC->>KC: canShowWhileLocked(topActivity) returns true
KC->>KC: mOccluded = true
KC->>KSD: setOccluded(true, animate)
KSD->>KVM: setOccluded(true, animate)
KC->>KTH: requestTransition(KEYGUARD_OCCLUDE)
KTH->>KTH: startAnimation() plays occlude transition
KTH-->>KC: onTransitionFinished()
Note over KC: Keyguard hidden behind activity
Note over KC: Sleep token remains held
App->>ATMS: finish() or user navigates away
ATMS->>KC: evaluateOcclusionChange(displayId)
KC->>KC: canShowWhileLocked(topActivity) returns false
KC->>KC: mOccluded = false
KC->>KSD: setOccluded(false, animate)
KSD->>KVM: setOccluded(false, animate)
KC->>KTH: requestTransition(KEYGUARD_UNOCCLUDE)
KTH->>KTH: startAnimation() plays unocclude transition
KTH-->>KC: onTransitionFinished()
Note over KC: Keyguard reappears
A critical detail: when the keyguard is occluded, the sleep token is not released. The device remains locked. The occluding activity has limited access — it cannot see notifications or launch arbitrary activities without user authentication. If the user presses the back button or the occluding activity finishes, the keyguard reappears through the KEYGUARD_UNOCCLUDE transition.
The handleOccludedChanged() method evaluates the top activity on the target display. If the top activity has the showWhenLocked attribute or FLAG_SHOW_WHEN_LOCKED window flag, and the keyguard is currently showing (not going away), the occlude transition fires.
60.6 KeyguardServiceDelegate — Cross-Process Bridge
KeyguardServiceDelegate bridges the system_server and SystemUI processes. It binds to KeyguardService (an IKeyguardService.Stub in SystemUI) and forwards all keyguard lifecycle events across the binder interface.
Cross-Process Interaction Flow
sequenceDiagram
participant WMS as WindowManagerService
participant KC as KeyguardController
participant KSD as KeyguardServiceDelegate
participant Binder as IKeyguardService Binder
participant KS as KeyguardService in SystemUI
participant KVM as KeyguardViewMediator
WMS->>KC: systemReady()
KC->>KSD: onSystemReady()
KSD->>KSD: bindService(KeyguardService)
KSD->>Binder: connect()
Binder->>KS: onConnected()
KS->>KVM: setupLocked()
Note over KSD: Service now connected
KC->>KSD: onStartedGoingToSleep(why)
KSD->>Binder: onStartedGoingToSleep(why)
Binder->>KS: onStartedGoingToSleep(why)
KS->>KVM: onStartedGoingToSleep(why)
KC->>KSD: onFinishedGoingToSleep(why, cameraGesture)
KSD->>Binder: onFinishedGoingToSleep()
Binder->>KVM: doKeyguardTimeout()
KVM->>KVM: handleShow()
InteractiveState Enum
| State | Description |
|---|---|
INTERACTIVE_STATE_SLEEP |
Device is fully asleep; screen off |
INTERACTIVE_STATE_GOING_TO_SLEEP |
Transition to sleep in progress |
INTERACTIVE_STATE_AWAKE |
Device is fully awake; screen on |
INTERACTIVE_STATE_GOING_TO_AWAKE |
Transition to awake in progress (before keyguard drawn) |
Delegated Methods
| Method | Direction | Purpose |
|---|---|---|
onStartedGoingToSleep(int why) |
WMS to SystemUI | Begin sleep sequence; prepare keyguard show |
onFinishedGoingToSleep(int why, boolean cameraGesture) |
WMS to SystemUI | Sleep complete; show keyguard |
onStartedWakingUp(int why, boolean cameraGesture) |
WMS to SystemUI | Begin wake; prepare unlock UI |
onFinishedWakingUp() |
WMS to SystemUI | Wake complete |
setOccluded(boolean occluded, boolean animate) |
WMS to SystemUI | Notify of occlude state change |
verifyUnlock() |
WMS to SystemUI | Request unlock verification (e.g., for work profile) |
dismiss(IKeyguardDismissCallback, CharSequence) |
WMS to SystemUI | Programmatic dismiss request |
The delegate buffers calls that arrive before the service is bound, replaying them once the connection is established. This handles the race condition where system_server initializes before SystemUI’s KeyguardService is available.
60.7 KeyguardViewMediator — SystemUI Controller
KeyguardViewMediator is the largest component in the keyguard stack at 4,573 lines. It runs in the SystemUI process and is the central controller for keyguard UI lifecycle, authentication response, and animation coordination.
Handler Message Table
| Message ID | Constant | Trigger | Action |
|---|---|---|---|
| 1 | SHOW |
Sleep complete or explicit show | Acquires wake lock, shows keyguard window, plays show animation |
| 2 | HIDE |
Successful authentication | Starts hide animation sequence via remote animation runner |
| 3 | RESET |
Keyguard needs refresh | Resets keyguard to initial state (e.g., after failed auth) |
| 4 | VERIFY_UNLOCK |
Work profile or similar | Verifies unlock capability without actually dismissing |
| 5 | NOTIFY_FINISHED_GOING_TO_SLEEP |
Power state | Finalizes keyguard show after screen off |
| 7 | KEYGUARD_DONE |
Unlock complete | Cleans up post-unlock state, releases wake locks |
| 8 | NOTIFY_STARTED_GOING_TO_SLEEP |
Power state | Begins pre-lock preparations |
| 9 | NOTIFY_STARTED_WAKING_UP |
Power state | Begins wake preparations, may trigger face unlock |
| 10 | SET_OCCLUDED |
Activity occlude change | Updates mediator occlude state |
| 12 | KEYGUARD_TIMEOUT |
Idle timeout | Auto-locks device after inactivity |
| 13 | DISMISS |
Programmatic dismiss | Initiates dismiss with callback |
Key State Fields
| Field | Type | Description |
|---|---|---|
mShowing |
boolean |
Whether keyguard is currently showing |
mOccluded |
boolean |
Whether keyguard is occluded by an activity |
mInputRestricted |
boolean |
Whether input is restricted (locked but possibly not visible) |
mKeyguardDonePending |
boolean |
Waiting for biometric animation to complete before finalizing unlock |
mExternallyEnabled |
boolean |
Can be disabled by DevicePolicyManager (e.g., kiosk mode) |
mGoingToSleep |
boolean |
In sleep transition |
mHiding |
boolean |
In hide animation |
mWaitingUntilKeyguardVisible |
boolean |
Blocks until keyguard is drawn (prevents flash of underlying content) |
The mKeyguardDonePending field is particularly important for biometric unlock. When the fingerprint sensor or face unlock succeeds, the system must play a visual confirmation animation before actually hiding the keyguard. During this period, mKeyguardDonePending is true — the authentication has succeeded, but the keyguard remains visible until the animation completes.
mExternallyEnabled allows enterprise management (via DevicePolicyManager) to disable the keyguard entirely, used in kiosk and dedicated device deployments.
After a successful unlock, playTrustedSound() provides audible confirmation to the user.
60.8 KeyguardTransitionHandler — Shell Transitions
KeyguardTransitionHandler is a WM Shell transition handler that manages the five keyguard-related transition types. It integrates with the Shell transition system described in Section 19.
Registered Transition Types
| Transition Type | Transit Flag | Trigger | Animation |
|---|---|---|---|
KEYGUARD_GOING_AWAY |
FLAG_KEYGUARD_GOING_AWAY |
User successfully unlocks | Keyguard fades or slides out; launcher or last activity appears |
KEYGUARD_GOING_AWAY_WITH_WALLPAPER |
FLAG_KEYGUARD_GOING_AWAY and GOING_AWAY_FLAG_WITH_WALLPAPER |
Unlock with wallpaper-based keyguard | Wallpaper cross-fades as part of unlock |
KEYGUARD_OCCLUDE |
FLAG_KEYGUARD_OCCLUDING |
SHOW_WHEN_LOCKED activity reaches top |
Activity slides or fades over keyguard |
KEYGUARD_UNOCCLUDE |
FLAG_KEYGUARD_UNOCCLUDING |
Occluding activity removed | Keyguard reappears with reverse animation |
KEYGUARD_APPEARANCE |
FLAG_KEYGUARD_LOCKED |
Keyguard becomes visible (lock) | Keyguard fades in or appears immediately |
Handler Methods
| Method | Purpose |
|---|---|
handleRequest(TransitionInfo) |
Evaluates whether this handler should claim the transition based on transit flags |
startAnimation(TransitionInfo, SurfaceControl.Transaction, SurfaceControl.Transaction, TransitionFinishCallback) |
Executes the keyguard animation using start and finish transactions |
mergeAnimation(TransitionInfo, SurfaceControl.Transaction, SurfaceControl.Transaction, TransitionInfo, TransitionFinishCallback) |
Handles cases where a new transition starts before the current one finishes (e.g., rapid lock-unlock) |
The mergeAnimation() method is critical for handling rapid state changes. Consider the scenario where a user begins unlocking (KEYGUARD_GOING_AWAY starts) but then relocks the device. The new KEYGUARD_APPEARANCE transition must merge with the in-progress going-away transition, reversing the animation smoothly rather than creating visual artifacts.
The handler uses SurfaceControl.Transaction pairs: a start transaction applied immediately when animation begins, and a finish transaction applied when animation completes. This ensures atomic visual state changes at the compositor level (see Section 15 for the general transition architecture).
60.9 Unlock Flow — End-to-End
The unlock flow is the most complex keyguard interaction, spanning all three process layers. Below is the complete sequence for a biometric unlock.
sequenceDiagram
participant Bio as Biometric HAL
participant FMS as FingerprintManagerService
participant KVM as KeyguardViewMediator
participant KSD as KeyguardServiceDelegate
participant KC as KeyguardController
participant TM as TransitionController
participant KTH as KeyguardTransitionHandler
Bio->>FMS: onAuthenticated()
FMS->>KVM: onAuthenticationSucceeded()
KVM->>KVM: mKeyguardDonePending = true
Note over KVM: Play fingerprint ripple animation
KVM->>KVM: Animation completes
KVM->>KVM: handleHide()
KVM->>KVM: mShowing = false
KVM->>KSD: keyguardDone()
KSD->>KC: keyguardGoingAway(flags)
KC->>KC: mGoingAwayFlags = flags
KC->>KC: Set going-away timeout 10.5s
KC->>TM: requestTransition(KEYGUARD_GOING_AWAY)
TM->>KTH: handleRequest(transitionInfo)
KTH->>KTH: Claims transition
KTH->>KTH: startAnimation()
Note over KTH: Keyguard surface fades out
Note over KTH: Launcher surface fades in
KTH->>KC: onTransitionFinished()
KC->>KC: keyguardGone()
KC->>KC: Release sleep token
KC->>KC: Cancel going-away timeout
Note over KC: Activities can now resume
PIN/Password Unlock Variation
For non-biometric unlock, the flow differs slightly:
- User enters PIN/password/pattern on the keyguard UI
KeyguardViewMediatorvalidates credentials viaLockPatternUtils- On success,
handleHide()is called directly (nomKeyguardDonePendingphase) - The rest of the flow is identical from
keyguardGoingAway()onward
Timing Constraints
| Phase | Timeout | Consequence of Timeout |
|---|---|---|
| Going-away transition | 10,500 ms | Force return to showing state |
| Wake lock during show | 10,000 ms | Release wake lock, may cause draw miss |
| Keyguard done pending | None (bounded by animation) | Biometric animation callback clears state |
60.10 AOD and Dream Integration
Always-On Display (AOD) and Dream (screen saver) modes interact closely with the keyguard system.
AOD is represented as a keyguard sub-state. When the screen turns off with keyguard enabled, setKeyguardShown(true, true) is called, setting both mShowing and mAodShowing to true. The AOD renders a minimal clock and notification icons. When the user lifts the device or presses power, setKeyguardShown(true, false) transitions from AOD to the full keyguard, and the AOD fades into the lock screen.
The isKeyguardOrAodShowing() method returns true in both states, ensuring the sleep token remains held and activities cannot resume during AOD.
Dream (screen saver) integration is handled through KeyguardViewMediator’s coordination with DreamManager. When a dream activates on a locked device, the keyguard must remain logically showing even though the dream window is visible. If the user interacts with the device during a dream, the dream dismisses and the keyguard appears. The dream lifecycle callbacks (onDreamingStarted, onDreamingStopped) are forwarded through the KeyguardServiceDelegate to keep WMS-side state consistent.
60.11 Multi-Display Keyguard
The per-display KeyguardDisplayState model (Section 60.4) enables independent keyguard management across displays. Key behaviors:
- Default display: Always has keyguard support. The primary
KeyguardViewMediatormanages its UI. - Secondary displays: May or may not show keyguard depending on
DisplayContentflags and policy. Each maintains its ownmShowing,mOccluded, andmSleepToken. - Occlude independence: An activity with
SHOW_WHEN_LOCKEDon display 1 does not affect the keyguard state on display 0. - Unlock coupling: Unlocking the default display typically unlocks all displays (device-wide authentication), but the per-display state is updated independently.
- Automotive and foldable: These form factors rely on per-display keyguard to allow a passenger display to remain unlocked while the driver display shows keyguard, or to manage keyguard across inner and outer foldable screens.
Each KeyguardDisplayState acquires its own sleep token, ensuring that activities on a locked display cannot resume even if another display is unlocked.
60.12 Cross-References
| Section | Relationship |
|---|---|
| Section 7 — WM Core | KeyguardController is managed by ActivityTaskManagerService within the WM core |
| Section 15 — Transitions | Keyguard transitions use the core TransitionController infrastructure |
| Section 19 — Shell Transitions | KeyguardTransitionHandler is a Shell-side transition handler |
| Section 46 — SystemUI | KeyguardViewMediator and KeyguardService live in the SystemUI process |
60.13 Key Source Files
| File | Path | Lines | Role |
|---|---|---|---|
| KeyguardController.java | frameworks/base/services/core/java/com/android/server/wm/KeyguardController.java |
924 | WMS-side keyguard state coordinator |
| KeyguardDisplayState | Inner class of KeyguardController | N/A | Per-display keyguard state holder |
| KeyguardServiceDelegate.java | frameworks/base/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java |
545 | Binder bridge from WMS to SystemUI |
| KeyguardViewMediator.java | frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java |
4,573 | Central SystemUI keyguard controller |
| KeyguardTransitionHandler.java | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java |
465 | Shell transition handler for keyguard animations |
| IKeyguardService.aidl | frameworks/base/core/java/com/android/internal/policy/IKeyguardService.aidl |
N/A | Binder interface between WMS and SystemUI |
| IKeyguardStateCallback.aidl | frameworks/base/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl |
N/A | Callback interface from SystemUI to WMS |
| KeyguardService.java | frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java |
N/A | SystemUI-side binder stub implementation |
| LockPatternUtils.java | frameworks/base/core/java/com/android/internal/widget/LockPatternUtils.java |
N/A | Credential verification utilities |
| StatusBarKeyguardViewManager.java | frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java |
N/A | Manages keyguard view within status bar window |
61. Starting Windows and Splash Screens
61.1 Overview
Starting windows are transient surfaces displayed between the moment a user taps a launcher icon and the moment the target application draws its first frame. Their purpose is twofold: eliminate perceived black-flash latency and provide visual continuity with the application’s branding. Android’s starting window subsystem spans two processes — the system server (Window Manager Service) and the Shell process (SystemUI) — connected by a two-tier decision model where WMS determines whether a starting window is needed while Shell determines what type to show.
The subsystem manages five distinct starting window types, from rich animated splash screens to minimal solid-color placeholders. A snapshot system captures and persists task screenshots so that returning to a recently-visited task can show the previous visual state instantly. Since Android 12, splash screens follow the SplashScreen API, which standardizes icon placement, branding, and exit animations across all applications. The rendering path uses windowless surfaces (SurfaceControlViewHost) rather than full WindowState objects, reducing overhead in the window manager.
61.2 Architecture — Two-Tier Decision Model
The starting window subsystem splits responsibility across process boundaries. WMS owns the policy of whether to show a starting window at all — checking theme flags, activity state, and transition type. Once WMS decides to proceed, it delegates to Shell’s StartingWindowController, which selects the type of starting window based on available data (snapshots, theme metadata, icon resources).
flowchart TD
A["ActivityStarter\nstartActivity()"] --> B["TaskDisplayArea\naddStartingWindow()"]
B --> C{"WMS: Should show\nstarting window?"}
C -- "No: theme=translucent\nor FLAG_DISABLE" --> D["STARTING_WINDOW_TYPE_NONE"]
C -- "Yes" --> E["StartingSurfaceController\ncreateSplashScreenStartingSurface()"]
E --> F["IPC to Shell process"]
F --> G["StartingWindowController\naddStartingWindow()"]
G --> H{"Shell: What type\nto show?"}
H -- "Snapshot available\nand fresh" --> I["SNAPSHOT"]
H -- "Theme has icon\nand colors" --> J["SPLASH_SCREEN"]
H -- "Fallback: only\nbackground color" --> K["SOLID_COLOR_SPLASH_SCREEN"]
H -- "Windowless\nstarting window" --> L["WINDOWLESS"]
I --> M["StartingSurfaceDrawer\nrender()"]
J --> M
K --> M
L --> M
M --> N["SurfaceControlViewHost\nattach to task surface"]
style C fill:#e8d44d,color:#000
style H fill:#4da6e8,color:#000
style D fill:#ccc,color:#000
The two-tier split is deliberate. WMS has access to theme attributes and activity flags but runs on a latency-sensitive thread. Shell has access to snapshot caches, icon resources, and a dedicated splash screen rendering thread (mSplashScreenExecutor), allowing it to perform potentially expensive bitmap operations without blocking the WM lock.
61.3 Starting Window Types
| Type Constant | Enum Value | Description | Visual Content | Use Case |
|---|---|---|---|---|
STARTING_WINDOW_TYPE_NONE |
0 | No starting window | N/A | Translucent themes, FLAG_SHOW_WALLPAPER, explicitly disabled |
STARTING_WINDOW_TYPE_SPLASH_SCREEN |
1 | Full themed splash screen | App icon (static or animated), background color, optional branding image | Cold launch of app with proper theme metadata |
STARTING_WINDOW_TYPE_SNAPSHOT |
2 | Task screenshot from previous visit | Bitmap of last visible frame captured by SnapshotController |
Returning to recent task from recents or launcher |
STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN |
3 | Minimal solid color splash screen | Single windowBackground color, no icon or branding |
App with theme but missing icon resources |
STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN |
4 | Legacy splash screen | Pre-Android 12 splash screen style | Backward compatibility for older splash screen behavior |
STARTING_WINDOW_TYPE_WINDOWLESS |
5 | Windowless starting window | Rendered without a dedicated window surface | Lightweight starting window for transition coordination |
The type selection follows a priority cascade. The Shell controller checks for a valid snapshot first (highest fidelity), then attempts a themed splash screen, falls back to solid color, and uses an empty window only when surface slot reservation is required for transition correctness.
61.4 StartingWindowController — Shell Coordinator
StartingWindowController is the Shell-side orchestrator that receives starting window requests from WMS over IPC. It coordinates type selection, delegates rendering to StartingSurfaceDrawer, and manages the lifecycle including deferred removal and exit animation transfers.
classDiagram
class StartingWindowController {
-TaskBackgroundColors mTaskBackgroundColors
-TaskLaunchingCallback mTaskLaunchingCallback
-StartingSurfaceDrawer mDrawer
+addStartingWindow(StartingWindowInfo) int
+removeStartingWindow(IBinder) void
+copySplashScreenView(IBinder) void
+onTaskLaunching(int taskId) void
}
class StartingWindowInfo {
+int taskId
+int windowingMode
+ActivityInfo activityInfo
+TaskSnapshot taskSnapshot
+int startingWindowType
}
class StartingSurfaceDrawer {
+addSplashScreenStartingWindow()
+addSnapshotStartingWindow()
+addSolidColorStartingWindow()
+addEmptyStartingWindow()
+removeStartingWindow()
}
class TaskBackgroundColors {
-SparseIntArray mColors
+get(int taskId) int
+put(int taskId, int color) void
}
StartingWindowController --> StartingSurfaceDrawer : delegates rendering
StartingWindowController --> StartingWindowInfo : receives from WMS
StartingWindowController --> TaskBackgroundColors : caches bg colors
class StartingSurfaceController {
+createSplashScreenStartingSurface()
+createTaskSnapshotStartingSurface()
+checkTheme() boolean
+needsToRemove() boolean
}
StartingSurfaceController ..> StartingWindowController : IPC call
Key behaviors of the controller:
- Background color cache (
mTaskBackgroundColors): Stores previously resolved background colors per task ID, enabling faster subsequent launches by skipping theme attribute resolution. - Task launching callback (
mTaskLaunchingCallback): A coordinator callback that notifies interested parties (e.g., launcher) when a task launch begins, enabling synchronized animations. - Copy splash screen view (
copySplashScreenView()): Transfers the splash screen’sViewhierarchy across the process boundary to the launching app, enabling custom exit animations (see Section 61.9).
61.5 StartingSurfaceDrawer — Rendering Engine
StartingSurfaceDrawer is the rendering backend that constructs and attaches starting window surfaces. It delegates to five specialized creator classes, each responsible for a specific starting window type.
| Creator Class | Starting Window Type | Rendering Strategy | Key Details |
|---|---|---|---|
SplashscreenWindowCreator |
SPLASH_SCREEN |
Inflates SplashScreenView layout with icon, branding, background color |
Resolves theme attributes; supports AnimatedVectorDrawable icons; renders on mSplashScreenExecutor thread |
SnapshotWindowCreator |
SNAPSHOT |
Applies captured HardwareBuffer as surface content |
Scales snapshot to current task bounds; handles orientation mismatch |
WindowlessSplashWindowCreator |
WINDOWLESS (splash) |
Creates a windowless splash screen without a dedicated surface | Lightweight path for splash screen rendering |
WindowlessSnapshotWindowCreator |
WINDOWLESS (snapshot) |
Creates a windowless snapshot starting window | Lightweight path for snapshot-based starting windows |
All creator classes share a common pattern: they create a SurfaceControlViewHost (rather than a real window with WindowState), attach a View hierarchy to it, and position the resulting surface as a child of the task’s surface. The dedicated mSplashScreenExecutor thread ensures that bitmap decoding, icon inflation, and layout measurement do not block either the main thread or the WM thread.
The mAnimatedSplashScreenTracker maintains a set of packages that have displayed animated icons during the current session. This tracking enables the system to decide whether to replay or skip animations on rapid re-launch.
61.6 Splash Screen Composition
SplashScreenView is a FrameLayout subclass that composes the visual elements of a themed splash screen. The layout follows a fixed structure mandated by the Android 12+ SplashScreen API.
flowchart TD
subgraph SplashScreenView["SplashScreenView (FrameLayout)"]
direction TB
BG["Background\nwindowSplashScreenBackground\n(fills entire surface)"]
subgraph Center["Center Container"]
ICON["Icon View\n288dp total / 240dp visible\nAnimatedVectorDrawable\nor static Drawable"]
end
subgraph Bottom["Bottom Container"]
BRAND["Branding Image\n(optional)\nmax 200dp height"]
end
end
style BG fill:#4a90d9,color:#fff
style ICON fill:#fff,color:#000
style BRAND fill:#e8e8e8,color:#000
style SplashScreenView fill:#2c5f8a,color:#fff
Layout dimensions and constraints:
| Element | Attribute | Default / Constraint |
|---|---|---|
| Background color | windowSplashScreenBackground |
Derived from theme colorBackground if unset |
| Icon | windowSplashScreenAnimatedIcon |
288dp container, 240dp visible area (2/3 ratio) |
| Icon animation duration | windowSplashScreenAnimationDuration |
Max 1000ms enforced by system |
| Branding image | windowSplashScreenBrandingImage |
Optional; max 200dp height; centered horizontally |
| Icon background | windowSplashScreenIconBackgroundColor |
Optional circular background behind icon |
The SplashScreenViewParcelable class enables cross-process transfer of the entire view hierarchy. It serializes the background color, icon bitmap, icon animation state, branding bitmap, and layout parameters into a Parcel. This is the mechanism behind exit animation support — the app process reconstructs an identical SplashScreenView from the parcelable data and can then run arbitrary animations on it.
For animated icons, the system uses a SurfaceControlViewHost embedded within the SplashScreenView to render AnimatedVectorDrawable content. The animation starts immediately upon surface attachment and is capped at the declared duration or the system maximum of 1000ms, whichever is shorter.
61.7 Task Snapshot System
SnapshotController orchestrates the capture, caching, persistence, and retrieval of task screenshots. These snapshots serve as the source data for STARTING_WINDOW_TYPE_SNAPSHOT starting windows.
flowchart LR
subgraph Capture["Capture Triggers"]
T1["Task going invisible\n(pause/stop)"]
T2["Recents snapshot\nrequest"]
T3["App crash / ANR"]
end
T1 --> SC["SnapshotController\nrecordTaskSnapshot()"]
T2 --> SC
T3 --> SC
SC --> HB["HardwareBuffer\ncaptureLayersToBuffer()"]
HB --> CACHE["mTaskSnapshotCache\n(LRU in-memory)"]
HB --> PERSIST["mTaskSnapshotPersister\n(disk write thread)"]
subgraph Retrieval["Retrieval Path"]
REQ["getSnapshot(taskId)"] --> CHECK{"In LRU\ncache?"}
CHECK -- "Hit" --> RET["Return cached\nHardwareBuffer"]
CHECK -- "Miss" --> DISK["Load from\ndisk persister"]
DISK --> RET
end
CACHE --> CHECK
PERSIST --> DISK
RET --> DRAW["StartingSurfaceDrawer\naddSnapshotStartingWindow()"]
style SC fill:#4da6e8,color:#fff
style CACHE fill:#6bc46b,color:#000
style PERSIST fill:#d4a64d,color:#000
Snapshot lifecycle stages:
- Capture: Triggered when a task transitions to invisible, when recents requests bulk snapshots (
snapshotTasks()), or on app crash. The capture calls through SurfaceFlinger’scaptureLayersToBuffer()to produce aHardwareBuffer. - Cache: The in-memory LRU cache (
mTaskSnapshotCache) holds recent snapshots keyed by task ID. Cache eviction recycles the underlyingHardwareBufferto prevent GPU memory leaks. - Persist: A background thread managed by
mTaskSnapshotPersisterwrites snapshots to disk as compressed bitmaps, enabling survival across process restarts. - Retrieve:
getSnapshot()checks the LRU cache first, then falls back to disk. The retrievedHardwareBufferis passed to the snapshot creator inStartingSurfaceDrawer. - Recycle: When a snapshot is no longer needed (task removed, newer snapshot captured), the
HardwareBufferis explicitly closed to release GPU memory.
Snapshot validity is checked before use — orientation changes, significant size changes, or configuration changes invalidate a cached snapshot, causing fallback to splash screen type.
61.8 Windowless Starting Windows
Starting windows do not create a WindowState object in the traditional WM sense. Instead, they use SurfaceControlViewHost to render a View hierarchy directly into a SurfaceControl that is attached as a child of the task’s container surface.
This approach provides several advantages:
- No WM lock contention: Creating a
WindowStaterequires holding the WM global lock and performing policy checks, input channel setup, and z-order calculation. ASurfaceControlViewHostbypasses all of this. - No input channel overhead: Starting windows are non-interactive; they need no input dispatch infrastructure.
- Simpler removal: Detaching a child
SurfaceControlis a single transaction operation, compared to the full window removal sequence (animation, surface destroy, input teardown). - Process isolation: The
SurfaceControlViewHostlives in the Shell process, keeping the rendering and view inflation off the system server’s main thread entirely.
The trade-off is that these windowless surfaces are invisible to the traditional window management stack — they do not appear in dumpsys window, do not participate in focus calculation, and cannot receive input events. This is acceptable because starting windows are purely visual placeholders with a brief lifespan. For further details on the SurfaceControlViewHost mechanism, see Section 14.
61.9 Exit Animation — Cross-Process Transfer
Android 12 introduced the SplashScreen.setOnExitAnimationListener() API, which allows applications to take ownership of the splash screen view and run custom exit animations. This requires transferring the view hierarchy from the Shell process to the application process.
sequenceDiagram
participant App as Application Process
participant WMS as WindowManagerService
participant Shell as Shell (StartingWindowController)
App->>WMS: setOnExitAnimationListener()
Note over App: App registers interest in<br/>controlling splash exit
App->>WMS: First frame drawn
WMS->>Shell: copySplashScreenView(appToken)
Shell->>Shell: Serialize SplashScreenView<br/>to SplashScreenViewParcelable
Shell->>Shell: Capture icon bitmap,<br/>background color, branding,<br/>animation state
Shell-->>WMS: Return SplashScreenViewParcelable
WMS-->>App: Deliver via OnExitAnimationListener
App->>App: Reconstruct SplashScreenView<br/>from parcelable
App->>App: Add SplashScreenView<br/>as overlay in window
Note over App: App runs custom<br/>exit animation<br/>(fade, scale, slide, etc.)
App->>App: Animation complete
App->>App: SplashScreenView.remove()
App->>WMS: Notify removal complete
WMS->>Shell: removeStartingWindow(appToken)
Shell->>Shell: Detach SurfaceControlViewHost
The transfer sequence works as follows:
- The application calls
SplashScreen.setOnExitAnimationListener()before its first frame, signaling that it wants to control the splash screen dismissal. - When the application draws its first frame, WMS calls
copySplashScreenView()on the Shell controller. - Shell serializes the
SplashScreenViewinto aSplashScreenViewParcelable, capturing the icon bitmap, background color, branding image, and current animation state. - The parcelable is delivered to the application through the
OnExitAnimationListenercallback. - The application reconstructs a
SplashScreenViewfrom the parcelable and overlays it on its window. - The application runs whatever exit animation it desires (fade, scale, slide, morphing).
- When the animation completes, the application calls
SplashScreenView.remove(), which triggers cleanup of both the app-side overlay and the Shell-sideSurfaceControlViewHost.
This cross-process handoff is seamless to the user because the parcelable faithfully reproduces the visual state, and both the Shell surface and the app overlay are composited at the same position by SurfaceFlinger.
61.10 Starting Window Lifecycle
The starting window progresses through a well-defined state machine, with deferred removal handling transitions involving IME and rotation.
stateDiagram-v2
[*] --> Requested : WMS calls\ncreateSplashScreenStartingSurface()
Requested --> TypeSelected : Shell selects\nstarting window type
TypeSelected --> Rendering : StartingSurfaceDrawer\ninflates view
Rendering --> Attached : SurfaceControlViewHost\nattached to task surface
Attached --> Visible : SurfaceFlinger\ncomposes frame
Visible --> RemovalPending : App draws first frame\nor needsToRemove()
RemovalPending --> DeferredRemoval : IME showing or\nrotation in progress
RemovalPending --> ExitAnimation : App registered\nOnExitAnimationListener
DeferredRemoval --> Removing : Deferred condition\nresolved
ExitAnimation --> CopiedToApp : copySplashScreenView()\ncompletes transfer
CopiedToApp --> Removing : App calls\nSplashScreenView.remove()
Removing --> Removed : SurfaceControlViewHost\ndetached
Removed --> [*]
Key lifecycle transitions:
- Requested to TypeSelected: Shell evaluates snapshot availability, theme attributes, and cached background colors to determine the type. This is the two-tier decision point.
- Visible to RemovalPending: Triggered when
needsToRemove()returns true, typically because the app has drawn its first frame (finishDrawingLocked()). - Deferred removal: If an IME transition or a display rotation is in progress, removal is delayed to prevent visual glitches. The starting window remains visible until the deferred condition resolves. See Section 15 for transition coordination details.
- Exit animation branch: When the application has registered an
OnExitAnimationListener, the starting window is not immediately removed. Instead, it enters the cross-process transfer flow described in Section 61.9.
61.11 Cross-References
| Section | Relevance |
|---|---|
| Section 8 — WM Shell Architecture | StartingWindowController and StartingSurfaceDrawer are Shell components running in the SystemUI process; their lifecycle is managed by the Shell module system |
| Section 12 — Shell Features | Starting windows are one of the core Shell features alongside bubbles, PiP, and split-screen; the feature registration and initialization pattern applies |
| Section 14 — SurfaceControlViewHost | The windowless starting window mechanism relies entirely on SurfaceControlViewHost to render View hierarchies into SurfaceControl without creating WindowState objects |
| Section 15 — Transitions | Starting window removal is coordinated with Shell transitions; deferred removal handles IME and rotation transitions to prevent visual artifacts |
61.12 Key Source Files
| File | Path | Lines | Role |
|---|---|---|---|
StartingWindowController.java |
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java |
~555 | Shell-side coordinator; type selection, lifecycle management, exit animation transfer |
StartingSurfaceDrawer.java |
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java |
~387 | Rendering engine; five creator classes; dedicated splash screen thread |
StartingSurfaceController.java |
frameworks/base/services/core/java/com/android/server/wm/StartingSurfaceController.java |
~279 | WMS-side controller; theme checks, creation initiation, removal policy |
SnapshotController.java |
frameworks/base/services/core/java/com/android/server/wm/SnapshotController.java |
~499 | Task snapshot orchestration; capture triggers, LRU cache, disk persistence |
SplashScreenView.java |
frameworks/base/core/java/android/window/SplashScreenView.java |
~857 | Splash screen FrameLayout; icon, branding, background; parcelable for cross-process transfer |
SplashScreen.java |
frameworks/base/core/java/android/window/SplashScreen.java |
– | Public API; setOnExitAnimationListener() for custom exit animations |
TaskSnapshotPersister.java |
frameworks/base/services/core/java/com/android/server/wm/TaskSnapshotPersister.java |
– | Background disk persistence for task snapshots |
TaskSnapshotCache.java |
frameworks/base/services/core/java/com/android/server/wm/TaskSnapshotCache.java |
– | In-memory LRU cache for HardwareBuffer snapshots |
62. Letterboxing and App Compatibility
62.1 Overview
When an application’s declared constraints – fixed orientation, fixed size, restricted aspect ratio – conflict with the current display geometry, the AOSP window manager does not simply stretch or crop the content. Instead, it activates a sophisticated compatibility layer that letterboxes the application, scales its coordinate space, and optionally overrides camera behavior, all while presenting a polished visual treatment to the user. This machinery spans 34 AppCompat* classes totaling over 9,300 lines of code, coordinated through a single per-activity controller.
The system addresses several distinct compatibility scenarios:
| Scenario | Root Cause | WM Response |
|---|---|---|
| Portrait app on landscape display | Fixed orientation in manifest | Letterbox with pillarbox bars |
| Landscape app on portrait display | Fixed orientation in manifest | Letterbox with horizontal bars |
| Fixed-size app on larger display | Non-resizable activity | Size compat scaling with letterbox |
| Narrow aspect ratio app on wide device | Manifest maxAspectRatio |
Letterbox to enforced aspect ratio |
| Camera preview distortion | Sensor orientation mismatch | Force rotation or pause/resume cycle |
| App in multi-window mode | Split or freeform bounds | Aspect ratio enforcement with 1.01f floor |
The entire subsystem is instantiated per ActivityRecord and lives within the com.android.server.wm package. Configuration is driven by resource overlays, allowing OEMs to customize positioning, background treatment, and aspect ratio defaults without modifying framework code.
Cross-references: Display geometry fundamentals are covered in Section 25 (Window-Display Relationship), windowing bounds in Section 27 (Windowing Modes and Bounds), inset handling in Section 23 (Insets System), and rotation mechanics in Section 58 (Rotation).
62.2 AppCompatController – Central Coordinator
AppCompatController is the single coordination point for all app compatibility logic within a given activity. It is instantiated in the ActivityRecord constructor and creates twelve specialized policy and override objects, each responsible for a distinct facet of compatibility behavior.
Class Composition
classDiagram
class ActivityRecord {
+AppCompatController mAppCompatController
+onConfigurationChanged()
}
class AppCompatController {
-AppCompatOrientationPolicy mOrientationPolicy
-AppCompatAspectRatioPolicy mAspectRatioPolicy
-AppCompatLetterboxPolicy mLetterboxPolicy
-AppCompatSizeCompatModePolicy mSizeCompatModePolicy
-AppCompatDeviceStateQuery mDeviceStateQuery
-AppCompatOverrides mAppCompatOverrides
-AppCompatReachabilityPolicy mReachabilityPolicy
-AppCompatSafeRegionPolicy mSafeRegionPolicy
-TransparentPolicy mTransparentPolicy
-AppCompatDisplayCompatModePolicy mDisplayCompatModePolicy
-DesktopAppCompatAspectRatioPolicy mDesktopAspectRatioPolicy
-AppCompatSandboxingPolicy mSandboxingPolicy
+onConfigurationChanged()
+getOrientationPolicy()
+getAspectRatioPolicy()
+getLetterboxPolicy()
+getSizeCompatPolicy()
+getDeviceStateQuery()
+getReachabilityPolicy()
}
class AppCompatOrientationPolicy {
+isLetterboxedForFixedOrientationAndAspectRatio()
+shouldEnableUserAspectRatioSettings()
}
class AppCompatAspectRatioPolicy {
+getDesiredMinAspectRatio()
+getDesiredMaxAspectRatio()
+shouldApplyUserMinAspectRatioOverride()
}
class AppCompatLetterboxPolicy {
+getLetterboxPositionForHorizontalReachability()
+getLetterboxPositionForVerticalReachability()
+isLetterboxedNotForDisplayCutout()
}
class AppCompatSizeCompatModePolicy {
+mSizeCompatScale
+mSizeCompatBounds
+resolveConfigurationInSizeCompatMode()
+updateConfigurationForSizeCompat()
}
class AppCompatDeviceStateQuery {
+isDeviceInTabletopPosture()
+isDeviceInHalfFoldedState()
}
class AppCompatReachabilityPolicy {
+handleDoubleTap()
+cyclePosition()
}
ActivityRecord --> AppCompatController
AppCompatController --> AppCompatOrientationPolicy
AppCompatController --> AppCompatAspectRatioPolicy
AppCompatController --> AppCompatLetterboxPolicy
AppCompatController --> AppCompatSizeCompatModePolicy
AppCompatController --> AppCompatDeviceStateQuery
AppCompatController --> AppCompatReachabilityPolicy
AppCompatController --> AppCompatSafeRegionPolicy
AppCompatController --> TransparentPolicy
AppCompatController --> AppCompatDisplayCompatModePolicy
AppCompatController --> DesktopAppCompatAspectRatioPolicy
AppCompatController --> AppCompatSandboxingPolicy
AppCompatController --> AppCompatOverrides
Lifecycle Integration
The controller hooks into the activity configuration change path. When ActivityRecord.onConfigurationChanged() fires, it delegates to AppCompatController.onConfigurationChanged(), which fans out the notification to all twelve policy objects. Each policy independently evaluates whether the new configuration triggers or clears its compatibility behavior.
ActivityRecord.onConfigurationChanged(newConfig)
--> AppCompatController.onConfigurationChanged()
--> AppCompatOrientationPolicy.onConfigurationChanged()
--> AppCompatAspectRatioPolicy.onConfigurationChanged()
--> AppCompatLetterboxPolicy.onConfigurationChanged()
--> AppCompatSizeCompatModePolicy.onConfigurationChanged()
--> AppCompatDeviceStateQuery.onConfigurationChanged()
--> ... (all 12 policies)
Other WM components access individual policies through the controller’s getter methods. For example, DisplayRotation consults getOrientationPolicy() to determine whether a fixed-orientation override is active, while TaskFragment queries getSizeCompatPolicy() during bounds resolution.
62.3 Letterbox Visual System
Letterbox.java (506 lines) implements the visual rendering of letterbox bars – the colored, wallpapered, or blurred regions surrounding a letterboxed application. It operates in two distinct surface modes and handles input interception for reachability gestures.
Surface Layout Modes
graph TD
subgraph FourSurfaceMode["Four-Surface Mode"]
PT["Top Surface"]
PB["Bottom Surface"]
PL["Left Surface"]
PR["Right Surface"]
IC["Inner Content Frame"]
PT --- IC
PB --- IC
PL --- IC
PR --- IC
end
subgraph OneSurfaceMode["One-Surface Mode"]
BG["Full Background Surface"]
IC2["Inner Content Frame"]
BG --- IC2
end
FourSurfaceMode -. "used when" .-> Cond1["App needs distinct bar regions"]
OneSurfaceMode -. "used when" .-> Cond2["Wallpaper background fills entire area"]
Four-surface mode creates individual SurfaceControl instances for the top, bottom, left, and right letterbox bars. Each surface is positioned as a child of the activity’s parent surface in the surface hierarchy, ensuring it renders behind sibling windows but in front of the task background.
One-surface mode uses a single background surface that fills the entire container, with the app content rendered on top. This mode is used for wallpaper-based backgrounds where seamless coverage is required.
Frame Geometry
Two rectangles define the letterbox layout:
| Frame | Description | Determines |
|---|---|---|
mOuterFrame |
The full container bounds allocated to the activity | Total available area |
mInnerFrame |
The resolved app content bounds after aspect ratio and orientation constraints | Where the app actually draws |
The letterbox bars fill the delta between these two frames. applySurfaceChanges(SurfaceControl.Transaction) computes the position and size of each bar surface on every transaction:
Top bar: (outerFrame.left, outerFrame.top) to (outerFrame.right, innerFrame.top)
Bottom bar: (outerFrame.left, innerFrame.bottom) to (outerFrame.right, outerFrame.bottom)
Left bar: (outerFrame.left, innerFrame.top) to (innerFrame.left, innerFrame.bottom)
Right bar: (innerFrame.right, innerFrame.top) to (outerFrame.right, innerFrame.bottom)
Input Interception
Each letterbox surface wraps an InputInterceptor inner class. The interceptor serves two purposes:
- Tap blocking – Prevents accidental input in the letterbox region from reaching the app or underlying windows.
- Double-tap reachability – Detects double-tap gestures in the letterbox area and forwards them to
AppCompatReachabilityPolicyto shift the app content (see Section 62.8).
Visual Treatments
Letterbox surfaces support rounded corners at the boundary between the bar and the app content. Corner radii are derived from the device’s display corner radius configuration, providing visual consistency with the physical screen shape.
62.4 Size Compat Mode
AppCompatSizeCompatModePolicy (618 lines) handles the case where an application’s resolved size does not match the available container, requiring the framework to scale the app’s coordinate space.
Trigger Conditions
| Condition | Example | Result |
|---|---|---|
| Fixed orientation on different-orientation display | Portrait-locked app on landscape tablet | App renders at portrait dimensions, scaled to fit |
| Non-resizable app on larger display | Phone app on desktop display | App renders at original size, scaled up |
| Fixed size smaller than container | App with fixed 1080x1920 on 1440x2560 display | App renders at declared size, scaled to fill |
| Multi-window with incompatible bounds | Split-screen with non-resizable app | App uses original bounds, scaled within split |
Scale Computation
The scale factor is computed as the minimum ratio that fits the app’s resolved dimensions within the container without cropping:
sizeCompatScale = min(containerWidth / appWidth, containerHeight / appHeight)
For example, a 1080x1920 app on a 2560x1440 landscape display:
scaleX = 2560 / 1920 = 1.333
scaleY = 1440 / 1080 = 1.333
sizeCompatScale = min(1.333, 1.333) = 1.333
The scale is applied as a surface transform on the activity’s main surface, meaning the app itself is unaware of the scaling – it continues to render at its native resolution while the compositor handles magnification.
Configuration Resolution
resolveConfigurationInSizeCompatMode() adjusts the Configuration object sent to the app:
- Computes
mSizeCompatBounds– the actual pixel bounds the app believes it occupies. - Adjusts
screenWidthDpandscreenHeightDpto reflect the app’s logical dimensions, not the physical container. - Applies frozen display insets from the rotation at which the activity was originally launched, preventing layout shifts when the device rotates under a fixed-orientation app.
Frozen Display Insets
The policy stores insets captured from all four rotation states at the time the activity enters size compat mode. This prevents a jarring layout recalculation when the display rotates but the app remains in its locked orientation. The frozen insets ensure the app sees consistent windowInsets values regardless of actual device orientation.
| Rotation | Stored Insets | Purpose |
|---|---|---|
| 0 (ROTATION_0) | Top, Bottom, Left, Right | Natural orientation baseline |
| 90 (ROTATION_90) | Remapped from rotation 0 | Landscape left |
| 180 (ROTATION_180) | Remapped from rotation 0 | Inverted portrait |
| 270 (ROTATION_270) | Remapped from rotation 0 | Landscape right |
See Section 23 (Insets System) for the broader inset computation pipeline and Section 58 (Rotation) for rotation transition mechanics.
62.5 Aspect Ratio Policy
AppCompatAspectRatioPolicy (466 lines) enforces minimum and maximum aspect ratio constraints through a multi-level priority cascade. The system resolves the effective aspect ratio by evaluating sources in strict priority order, with the first match winning.
Minimum Aspect Ratio Priority Cascade
| Priority | Source | Typical Value | Notes |
|---|---|---|---|
| 1 (highest) | Activity manifest android:minAspectRatio |
App-defined (e.g., 1.78) | Per-activity granularity |
| 2 | Application manifest android:minAspectRatio |
App-defined | Applies to all activities in the app |
| 3 | User override via Settings | User-selected | shouldApplyUserMinAspectRatioOverride() gate |
| 4 | System default for multi-window | 1.01f | Prevents exact-fit illusion in split screen |
| 5 (lowest) | Device config overlay | OEM-defined | config_letterboxDefaultMinAspectRatioForUnresizableApps |
Maximum Aspect Ratio Priority Cascade
| Priority | Source | Typical Value | Notes |
|---|---|---|---|
| 1 (highest) | Activity manifest android:maxAspectRatio |
App-defined (e.g., 2.4) | Per-activity granularity |
| 2 | Application manifest android:maxAspectRatio |
App-defined | Applies to all activities in the app |
| 3 | System enforcement | Display aspect ratio | Prevents app from exceeding display bounds |
The 1.01f Multi-Window Floor
In multi-window mode (split-screen or freeform), the system enforces a minimum aspect ratio of 1.01f rather than 1.0f. This seemingly minor distinction prevents a visual artifact: if an app’s aspect ratio exactly matches the split-screen slot, there would be no visible letterbox bars, yet the app would still be in compatibility mode. The 1.01f floor ensures that a minimal letterbox is always visible, signaling to the user that the app is running in a constrained mode. This also avoids edge-case layout bugs where zero-width letterbox bars cause incorrect input targeting.
Resolution Methods
getDesiredMinAspectRatio()– walks the priority cascade and returns the effective minimum.getDesiredMaxAspectRatio()– walks the maximum cascade.shouldApplyUserMinAspectRatioOverride()– checks whether the user has configured a per-app aspect ratio preference in Settings, gated byshouldEnableUserAspectRatioSettings()from the orientation policy.
The resolved aspect ratio feeds into bounds computation in TaskFragment, which constrains the activity’s bounds within its parent container (see Section 27, Windowing Modes and Bounds).
62.6 Orientation Policy and Overrides
AppCompatOrientationPolicy (199 lines) determines whether an activity should be letterboxed due to its fixed orientation conflicting with the display orientation, and provides override mechanisms for apps that incorrectly lock orientation.
Primary Decision Method
isLetterboxedForFixedOrientationAndAspectRatio() is the main entry point, returning true when:
- The activity declares a fixed orientation (PORTRAIT, LANDSCAPE, etc.) in its manifest.
- The current display orientation does not match the declared orientation.
- No orientation override is active that would force the app to UNSPECIFIED.
Orientation Override
For apps that lock orientation unnecessarily (common in older apps ported from phones), the system can override the declared orientation to UNSPECIFIED, allowing the app to rotate freely. This override is controlled through:
- Per-app compatibility flags in the framework.
- OEM configuration overlays.
- Developer options for testing.
When the override is active, the app receives the full display bounds and is not letterboxed, even if its manifest declares a fixed orientation.
User Aspect Ratio Settings Gate
shouldEnableUserAspectRatioSettings() controls whether the per-app aspect ratio override UI appears in system Settings. This allows OEMs to selectively enable user control over letterboxing behavior, typically exposed as a “Display ratio” or “Full screen apps” setting.
62.7 Camera Compatibility
AppCompatCameraOverrides (299 lines) addresses camera-specific compatibility overrides, such as forcing display rotation to match camera sensor orientation, refreshing the activity via pause/resume when the camera preview was initialized before letterboxing, and applying simulated orientations or compat aspect ratios when letterboxed apps encounter camera configuration conflicts. Note: AppCompatCameraOverrides exists as a separate class and is not a direct field of AppCompatController.
Override Types
| Override | Mechanism | When Applied | Effect |
|---|---|---|---|
| Force rotation | Temporarily rotate the display | Camera sensor orientation mismatches app orientation | Display rotates to align with camera sensor, preventing inverted or mirrored preview |
| Refresh via pause | Force activity pause then resume | Camera preview initialized before letterbox applied | Activity lifecycle restarted, camera reinitializes with correct bounds |
| Simulated orientation | Override requested orientation | App queries orientation during camera setup | App receives orientation matching camera expectations |
| Compat aspect ratio | Force 4:3 or 16:9 | Camera preview aspect ratio mismatches app bounds | App bounds adjusted to standard camera preview ratios |
Force Rotation Flow
sequenceDiagram
participant App as Activity
participant CamOv as AppCompatCameraOverrides
participant DR as DisplayRotation
participant Display as Display
App->>CamOv: Opens camera
CamOv->>CamOv: shouldForceRotateForCameraCompat()
CamOv->>DR: Request temporary rotation
DR->>Display: Apply forced rotation
Display-->>App: Configuration change with new orientation
App->>App: Camera preview renders correctly
Note over App,Display: On camera close the rotation reverts
Refresh via Pause
shouldRefreshActivityViaPause() returns true when the camera preview was initialized before the letterbox configuration was fully applied. In this case, the framework forces a pause() followed by resume() on the activity, causing the camera stack to reinitialize with the correct preview dimensions. This is less disruptive than a full activity restart and preserves the app’s in-memory state.
See Section 58 (Rotation) for the display rotation pipeline that camera force-rotation hooks into.
62.8 Letterbox Positioning and Reachability
AppCompatLetterboxPolicy (606 lines) controls where within the container the letterboxed app content is positioned, and implements the double-tap reachability gesture that lets users shift the content.
Position Options
The letterbox position is independently configurable for horizontal and vertical axes:
graph LR
subgraph Horizontal["Horizontal Positions"]
HL["LEFT"] --> HC["CENTER"] --> HR["RIGHT"]
end
subgraph Vertical["Vertical Positions"]
VT["TOP"] --> VC["CENTER"] --> VB["BOTTOM"]
end
The default position is CENTER on both axes, controlled by multiplier overlays:
config_letterboxHorizontalPositionMultiplier: 0.0 = LEFT, 0.5 = CENTER, 1.0 = RIGHTconfig_letterboxVerticalPositionMultiplier: 0.0 = TOP, 0.5 = CENTER, 1.0 = BOTTOM
Reachability – Double-Tap Cycling
On large-screen devices, a centered letterboxed app may place interactive elements out of comfortable thumb reach. The reachability feature allows users to shift the app content by double-tapping the letterbox bars.
stateDiagram-v2
[*] --> Left: Double tap right bar
Left --> Center: Double tap either bar
Center --> Right: Double tap left bar
Right --> Center: Double tap either bar
Center --> Left: Double tap right bar
state Left {
direction LR
l1: App pinned to left edge
}
state Center {
direction LR
c1: App centered in container
}
state Right {
direction LR
r1: App pinned to right edge
}
Double-tap flow:
- User double-taps within a letterbox bar surface.
- The
InputInterceptoron that surface detects the double-tap gesture. AppCompatReachabilityPolicy.handleDoubleTap()is invoked.- The policy cycles the position: LEFT to CENTER to RIGHT (for horizontal) or TOP to CENTER to BOTTOM (for vertical).
AppCompatLetterboxPolicyrecalculates the content position within the container.Letterbox.applySurfaceChanges()repositions all surfaces in the next transaction.- The app receives a configuration change with updated bounds.
The cycling direction depends on which bar was tapped – tapping the left bar shifts content rightward, tapping the right bar shifts content leftward, providing intuitive directional feedback.
Letterbox Reason Discrimination
isLetterboxedNotForDisplayCutout() distinguishes between letterboxing caused by app compatibility constraints and letterboxing caused by display cutout avoidance. This distinction matters for:
- Whether reachability gestures should be enabled (only for app compat letterboxing).
- Whether the letterbox education toast should be shown.
- Analytics and telemetry reporting.
Education
On the first occurrence of app-compat letterboxing, the system displays a toast informing the user that the app is running in compatibility mode. This one-time education is tracked per user and suppressed on subsequent encounters.
62.9 Letterbox Background Styles
The visual treatment of letterbox bars is configurable, offering three distinct background styles that OEMs and users can select.
Background Types
| Type ID | Constant | Rendering | Performance Impact |
|---|---|---|---|
| 0 | LETTERBOX_BACKGROUND_SOLID_COLOR |
Flat color fill (default black or OEM-configured) | Minimal – single color surface |
| 1 | LETTERBOX_BACKGROUND_WALLPAPER |
Current wallpaper rendered behind letterbox bars | Moderate – wallpaper texture sampling |
| 2 | LETTERBOX_BACKGROUND_WALLPAPER_WITH_BLUR |
Current wallpaper with Gaussian blur applied | Higher – blur shader on wallpaper |
Solid Color
The simplest treatment. The letterbox surfaces are filled with a single color, defaulting to black. OEMs can override the color through resource overlays. This mode has negligible GPU overhead and is the safest choice for low-end devices.
Wallpaper
The letterbox bars become transparent windows into the current wallpaper, creating a visual effect where the app appears to float over the home screen background. The wallpaper is not re-rendered – the compositor samples from the existing wallpaper layer, making this mode moderately efficient.
Wallpaper with Blur
Extends the wallpaper mode by applying a Gaussian blur to the wallpaper content visible through the letterbox bars. This creates a frosted-glass effect that reduces visual distraction while maintaining aesthetic continuity. The blur radius is system-configured and applies uniformly across all letterbox surfaces.
graph TD
Config["config_letterboxBackgroundType"] --> Decision{Background Type}
Decision -->|0| Solid["Solid Color Fill"]
Decision -->|1| Wallpaper["Wallpaper Pass-through"]
Decision -->|2| WallpaperBlur["Wallpaper with Blur Shader"]
Solid --> Surface["Letterbox SurfaceControl"]
Wallpaper --> Surface
WallpaperBlur --> BlurPass["Blur RenderEffect"] --> Surface
62.10 Configuration Resources
The letterboxing system is extensively configurable through Android resource overlays, allowing OEMs to customize behavior without modifying framework code. These overlays are typically placed in frameworks/base/core/res/res/values/config.xml and overridden in device-specific overlay packages.
Overlay Configuration Table
| Resource Name | Type | Default | Range | Description |
|---|---|---|---|---|
config_letterboxHorizontalPositionMultiplier |
float | 0.5 | 0.0 - 1.0 | Horizontal position: 0.0=left, 0.5=center, 1.0=right |
config_letterboxVerticalPositionMultiplier |
float | 0.5 | 0.0 - 1.0 | Vertical position: 0.0=top, 0.5=center, 1.0=bottom |
config_letterboxBackgroundType |
int | 0 | 0, 1, 2 | Background style: 0=solid, 1=wallpaper, 2=wallpaper+blur |
config_letterboxDefaultMinAspectRatioForUnresizableApps |
float | varies | >= 1.0 | Default min aspect ratio for non-resizable apps |
config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled |
bool | false | true/false | Whether to enforce aspect ratio in split-screen for non-resizable apps |
Overlay Mechanism
OEM overlays follow the standard Android resource overlay pattern:
device/oem/product/overlay/
frameworks/base/core/res/res/values/
config.xml
<item name="config_letterboxBackgroundType" format="integer" type="integer">2</item>
<item name="config_letterboxHorizontalPositionMultiplier" format="float" type="dimen">0.5</item>
These values are read at runtime by AppCompatLetterboxPolicy and cached per DisplayContent, meaning they apply uniformly to all activities on a given display but can theoretically differ across displays on multi-display devices.
62.11 Cross-References
| Section | Relationship to Letterboxing |
|---|---|
| Section 23 – Insets System | Frozen display insets in size compat mode rely on the inset computation pipeline; letterbox bars interact with system bar insets |
| Section 25 – Window-Display Relationship | Display properties determine when letterboxing triggers; display rotation state drives orientation conflict detection |
| Section 27 – Windowing Modes and Bounds | Aspect ratio constraints feed into bounds resolution in TaskFragment; size compat bounds override normal bounds computation |
| Section 58 – Rotation | Camera force-rotation hooks into DisplayRotation; orientation policy interacts with rotation freeze logic |
Additional connections:
- ActivityRecord – Host of
AppCompatController; configuration change propagation originates here. - TaskFragment – Consumes aspect ratio and size compat bounds during layout.
- DisplayContent – Provides display geometry that determines letterbox necessity.
- SurfaceControl – Letterbox surfaces are managed through the SurfaceControl transaction pipeline.
62.12 Key Source Files
| File | Path | Lines | Role |
|---|---|---|---|
| AppCompatController.java | frameworks/base/services/core/java/com/android/server/wm/AppCompatController.java |
194 | Central coordinator; creates and holds all 12 policy objects |
| Letterbox.java | frameworks/base/services/core/java/com/android/server/wm/Letterbox.java |
506 | Visual rendering of letterbox surfaces and input interception |
| AppCompatSizeCompatModePolicy.java | frameworks/base/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java |
618 | Size compat scaling and frozen inset management |
| AppCompatLetterboxPolicy.java | frameworks/base/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java |
606 | Letterbox positioning, reachability, and background style |
| AppCompatAspectRatioPolicy.java | frameworks/base/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java |
466 | Min/max aspect ratio priority cascade resolution |
| AppCompatDeviceStateQuery.java | frameworks/base/services/core/java/com/android/server/wm/AppCompatDeviceStateQuery.java |
64 | Device state queries for posture-dependent compat decisions |
| AppCompatOrientationPolicy.java | frameworks/base/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java |
199 | Orientation override and letterbox-for-orientation checks |
| AppCompatOverrides.java | frameworks/base/services/core/java/com/android/server/wm/AppCompatOverrides.java |
– | General compatibility overrides |
| AppCompatReachabilityPolicy.java | frameworks/base/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java |
– | Double-tap position cycling logic |
| AppCompatSafeRegionPolicy.java | frameworks/base/services/core/java/com/android/server/wm/AppCompatSafeRegionPolicy.java |
– | Safe region enforcement for compat apps |
| TransparentPolicy.java | frameworks/base/services/core/java/com/android/server/wm/TransparentPolicy.java |
– | Transparent activity letterbox handling |
| AppCompatDisplayCompatModePolicy.java | frameworks/base/services/core/java/com/android/server/wm/AppCompatDisplayCompatModePolicy.java |
– | Display compatibility mode policy |
| DesktopAppCompatAspectRatioPolicy.java | frameworks/base/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java |
– | Desktop mode aspect ratio enforcement |
| AppCompatSandboxingPolicy.java | frameworks/base/services/core/java/com/android/server/wm/AppCompatSandboxingPolicy.java |
– | Display sandboxing for compat apps |
| config.xml | frameworks/base/core/res/res/values/config.xml |
– | Overlay-configurable letterbox parameters |
Part XIII: Wallpaper, Color, Window Types, Accessibility, and IME
63. Wallpaper Window System
63.1 Overview
The AOSP Wallpaper Window System provides a layered architecture for rendering, positioning, and managing wallpaper surfaces behind application windows. Unlike ordinary windows, wallpaper windows occupy a dedicated Z-order slot at the bottom of the window hierarchy and follow a target-tracking model: the system identifies which foreground window should “show through” to the wallpaper, then positions and animates the wallpaper surface accordingly.
The system spans four major components. The public WallpaperManager API exposes wallpaper selection, offset control, and commands to application code and the launcher. WallpaperManagerService runs as a system service managing wallpaper binding, persistence, and lifecycle across users and displays. Within the Window Manager core, WallpaperController handles target detection, Z-ordering, offset calculation, and the draw state machine. Finally, WallpaperService.Engine executes inside the live wallpaper process, owning the rendering surface, processing offset callbacks, and implementing the freeze mechanism for transitions.
Key design characteristics include:
- Target tracking: wallpaper visibility is driven by whether any on-screen window has the
FLAG_SHOW_WALLPAPERattribute, determined per frame by scanning the window list. - Parallax offset model: the launcher communicates normalized scroll position (0.0 to 1.0) and step size, which the controller translates into pixel offsets for the wallpaper surface.
- Dual wallpaper support: system (home screen) and lock screen wallpapers are managed independently with separate binding connections.
- Freeze mechanism: during transitions and letterboxing, the live wallpaper engine captures a screenshot of itself, replaces the live surface with the static image, and resumes rendering when unfrozen.
- Multi-crop: wallpaper images support per-orientation crop rectangles, enabling distinct framing for portrait, landscape, and square display configurations.
63.2 Architecture Layers
The wallpaper system is organized into four layers spanning three processes: the client application process, the system server process (containing both the service and the window manager), and the wallpaper rendering process.
graph TB
subgraph "Client Process"
WM_API["WallpaperManager API<br>WallpaperManager.java"]
end
subgraph "System Server Process"
WMS["WallpaperManagerService<br>Binding and Lifecycle"]
WC["WallpaperController<br>Target Detection and Ordering"]
WWT["WallpaperWindowToken<br>Z-Order and Visibility"]
end
subgraph "Wallpaper Process"
ENGINE["WallpaperService.Engine<br>Surface Rendering"]
SC["SurfaceControl<br>BlastBufferQueue"]
end
subgraph "SurfaceFlinger"
SF["Compositor Layer"]
end
WM_API -->|"IWallpaperManager AIDL"| WMS
WMS -->|"IWallpaperConnection"| ENGINE
WMS -->|"addWindow TYPE_WALLPAPER"| WC
WC --> WWT
WWT -->|"Window State"| WC
ENGINE --> SC
SC -->|"BufferQueue"| SF
WC -->|"Offset and Commands"| ENGINE
| Layer | Component | Process | Responsibility |
|---|---|---|---|
| Public API | WallpaperManager |
Client app | Set wallpaper, send commands, query state |
| System Service | WallpaperManagerService |
system_server |
Binding, persistence, user management, LMK recovery |
| Window Manager | WallpaperController |
system_server |
Target detection, offset calculation, draw state |
| Window Manager | WallpaperWindowToken |
system_server |
Z-order positioning, visibility, per-token parallax state |
| Rendering | WallpaperService.Engine |
Wallpaper app | Surface ownership, rendering, freeze, local colors |
The IPC boundary between WallpaperManager and WallpaperManagerService uses the IWallpaperManager AIDL interface. The connection between the service and the live wallpaper engine uses IWallpaperConnection and IWallpaperEngine AIDL interfaces, with a DisplayConnector wrapping per-display engine instances.
63.3 WallpaperController — Target Detection and Ordering
WallpaperController is the central coordinator within the Window Manager, responsible for determining which window the wallpaper should appear behind (the “wallpaper target”), adjusting wallpaper window ordering, and dispatching offset and visibility updates. The core method adjustWallpaperWindows() is invoked during each window layout pass.
Target Detection Algorithm
The target detection algorithm scans the window list from top to bottom, looking for the first window that satisfies two conditions: the window has the FLAG_SHOW_WALLPAPER attribute (checked via hasWallpaper()) and the window is currently visible on screen (checked via isOnScreen()). When a target is found, all wallpaper windows are repositioned immediately below the target in Z-order.
flowchart TD
START["adjustWallpaperWindows called"] --> SCAN["Scan window list top to bottom"]
SCAN --> CHECK{"Window has<br>FLAG_SHOW_WALLPAPER<br>and isOnScreen?"}
CHECK -->|"No"| NEXT["Next window"]
NEXT --> CHECK
CHECK -->|"Yes"| FOUND["Set as wallpaper target"]
FOUND --> SAME{"Same as<br>previous target?"}
SAME -->|"Yes"| OFFSETS["Update offsets only"]
SAME -->|"No"| REORDER["Reorder wallpaper windows<br>below new target"]
REORDER --> VIS["Update wallpaper visibility"]
VIS --> OFFSETS
OFFSETS --> DRAW["Check draw state"]
DRAW --> DONE["Return"]
CHECK -->|"List exhausted"| NOTARGET["No target found"]
NOTARGET --> HIDE["Hide wallpaper windows"]
HIDE --> DONE
Target Change Behavior
When the wallpaper target changes, the controller performs several coordinated actions:
| Action | Description |
|---|---|
| Z-order adjustment | Wallpaper windows moved immediately below the new target |
| Visibility update | Wallpaper shown if target exists, hidden otherwise |
| Offset synchronization | Offsets from the new target window are applied |
| Freeze command | COMMAND_FREEZE sent if transitioning involves letterbox background |
| Draw state reset | Draw state set to WALLPAPER_DRAW_PENDING if wallpaper becoming visible |
The controller also handles the case where the wallpaper target is animating away. During app transitions, the previous target may remain as the wallpaper target until the animation completes, preventing the wallpaper from flickering during the transition.
63.4 WallpaperWindowToken — Z-Order and Visibility
WallpaperWindowToken extends WindowToken with wallpaper-specific behavior. Each live wallpaper connection creates a WallpaperWindowToken of type TYPE_WALLPAPER, which is inserted into the DisplayContent window hierarchy.
Show-When-Locked Positioning
The mShowWhenLocked flag controls whether the wallpaper token is placed at the bottom of the entire window hierarchy (below the lock screen) or at the top of the wallpaper layer (above the lock screen background). This determines whether the wallpaper is visible on the lock screen.
| mShowWhenLocked | Z-Order Position | Use Case |
|---|---|---|
false |
Bottom of hierarchy | System wallpaper behind lock screen; wallpaper not visible when locked |
true |
Top of wallpaper layer | Lock screen wallpaper; visible when device is locked |
Per-Token Parallax State
Each WallpaperWindowToken maintains its own parallax state, allowing independent offset tracking when multiple wallpaper windows exist (such as separate system and lock screen wallpapers):
| Field | Type | Description |
|---|---|---|
mWallpaperX |
float |
Horizontal offset position, 0.0 to 1.0 |
mWallpaperY |
float |
Vertical offset position, 0.0 to 1.0 |
mWallpaperXStep |
float |
Horizontal step size per page |
mWallpaperYStep |
float |
Vertical step size per page |
mCropHints |
SparseArray<Rect> |
Per-orientation crop rectangles |
Visibility Deferral
During shell transitions and activity animations, wallpaper visibility changes are deferred to prevent visual glitches. The token tracks whether a visibility change is pending and applies it only when the transition completes. This prevents the wallpaper from appearing or disappearing mid-animation.
63.5 Parallax Offset System
The parallax offset system creates the illusion of depth by scrolling the wallpaper at a different rate than the foreground content. The launcher communicates its scroll position to the wallpaper system, which translates normalized offsets into pixel displacements applied to the wallpaper surface.
Offset Parameters
The launcher sets four parameters via WallpaperManager.setWallpaperOffsets(), which are relayed through WallpaperController.setWindowWallpaperPosition():
| Parameter | Type | Description | Example |
|---|---|---|---|
xOffset |
float |
Horizontal scroll position, 0.0 (leftmost) to 1.0 (rightmost) | 0.5 at center page |
yOffset |
float |
Vertical scroll position, 0.0 to 1.0 | Usually 0.0 |
xStep |
float |
Horizontal step per page, typically 1/(N-1) for N pages | 0.25 for 5 pages |
yStep |
float |
Vertical step per page | Usually 0.0 |
Offset Calculation Flow
The method updateWallpaperOffset() (lines 287-460 in WallpaperController.java) performs a multi-stage calculation that accounts for multi-crop, zoom level, RTL layout direction, and display centering.
flowchart TD
INPUT["Receive xOffset, yOffset,<br>xStep, yStep from launcher"] --> CROP{"Multi-crop<br>hints available?"}
CROP -->|"Yes"| MULTICROP["Select crop rect for<br>current orientation"]
CROP -->|"No"| FALLBACK["Use full wallpaper dimensions"]
MULTICROP --> ZOOM["Apply zoom factor<br>scale crop region"]
FALLBACK --> ZOOM
ZOOM --> RTL{"RTL layout<br>direction?"}
RTL -->|"Yes"| MIRROR["Mirror xOffset:<br>xOffset = 1.0 - xOffset"]
RTL -->|"No"| CALC["Calculate pixel offset"]
MIRROR --> CALC
CALC --> PIXELX["pixelX = xOffset *<br>(wallpaperWidth - displayWidth)"]
PIXELX --> CENTER{"Wallpaper narrower<br>than display?"}
CENTER -->|"Yes"| CENTEROFFSET["Center wallpaper:<br>offset = (display - wallpaper) / 2"]
CENTER -->|"No"| APPLY["Apply pixel offset<br>to surface position"]
CENTEROFFSET --> APPLY
APPLY --> DISPATCH["Dispatch to<br>WallpaperService.Engine"]
Zoom Integration
The setWallpaperZoomOut() method applies a zoom factor (0.0 to 1.0) used during overview mode and notification shade pull-down. At zoom 0.0, the wallpaper is at its normal scale; at zoom 1.0, it is fully zoomed out. The zoom factor modifies the effective crop region before the offset calculation, causing the wallpaper to appear to pull back as the user enters overview.
63.6 Wallpaper Draw State Machine
The wallpaper draw state machine coordinates rendering readiness when a wallpaper becomes visible. Because live wallpapers render asynchronously in a separate process, the window manager must wait for the wallpaper to produce its first frame before showing the window, with a timeout to prevent indefinite blocking.
stateDiagram-v2
[*] --> NORMAL: Wallpaper hidden or already drawn
NORMAL --> PENDING: Wallpaper becoming visible
PENDING --> NORMAL: First frame drawn
PENDING --> TIMEOUT: 500ms elapsed without draw
TIMEOUT --> NORMAL: Force show wallpaper
State Definitions
| State | Constant | Value | Description |
|---|---|---|---|
| Normal | WALLPAPER_DRAW_NORMAL |
0 | Wallpaper is either hidden or has completed its first draw |
| Pending | WALLPAPER_DRAW_PENDING |
1 | Wallpaper is becoming visible; waiting for first frame |
| Timeout | WALLPAPER_DRAW_TIMEOUT |
2 | 500ms elapsed without receiving a draw completion; force-show |
Timeout Handling
The timeout duration is defined as WALLPAPER_DRAW_PENDING_TIMEOUT_DURATION = 500ms. When the timeout fires:
- The draw state transitions from
PENDINGtoTIMEOUT. - The wallpaper window is shown regardless of whether it has drawn its first frame.
- This prevents the system from blocking indefinitely if the wallpaper process is slow to render (for example, a complex OpenGL live wallpaper loading shader resources).
- The state then resets to
NORMALon the next layout pass.
This state machine is critical during boot, user switch, and wallpaper changes, where the wallpaper process may need significant time to initialize.
63.7 WallpaperManagerService — Lifecycle Management
WallpaperManagerService (4,203 lines) is the system service responsible for wallpaper selection, binding to live wallpaper services, persisting wallpaper state across reboots, and managing the dual wallpaper model.
Dual Wallpaper Model
The service maintains two independent wallpaper maps, one for the system (home screen) wallpaper and one for the lock screen wallpaper:
| Map | Field | Flag | Description |
|---|---|---|---|
| System wallpaper | mWallpaperMap |
FLAG_SYSTEM (1) |
Home screen wallpaper, visible behind launcher |
| Lock wallpaper | mLockWallpaperMap |
FLAG_LOCK (2) |
Lock screen wallpaper, visible on keyguard |
Each map is keyed by user ID, supporting multi-user configurations where each user has independent wallpaper selections. When FLAG_LOCK wallpaper is not explicitly set, the system wallpaper is used on the lock screen as well.
Binding Sequence
sequenceDiagram
participant Client as WallpaperManager
participant WMS as WallpaperManagerService
participant WP as WallpaperService
participant WC as WallpaperController
Client->>WMS: setWallpaperComponent(component, FLAG_SYSTEM)
WMS->>WMS: Validate component and permissions
WMS->>WMS: Persist to wallpaper_info.xml
WMS->>WP: bindService(Intent, WallpaperConnection)
WP-->>WMS: onServiceConnected(IWallpaperService)
WMS->>WP: attach(IWallpaperConnection, token, ...)
WP->>WP: onCreateEngine()
WP-->>WMS: Engine created, attachEngine callback
WMS->>WC: addWindow(TYPE_WALLPAPER, token)
WC->>WC: Create WallpaperWindowToken
WC->>WC: adjustWallpaperWindows()
WMS-->>Client: Wallpaper set successfully
LMK Recovery
Live wallpapers run in a separate process that may be killed by the Low Memory Killer. The service implements automatic recovery:
| Parameter | Value | Description |
|---|---|---|
LMK_RECONNECT_REBIND_RETRIES |
3 | Maximum rebind attempts after LMK kill |
LMK_RECONNECT_DELAY_MS |
5000 | Delay between rebind attempts (5 seconds) |
When the wallpaper process dies, the WallpaperConnection.onServiceDisconnected() callback triggers. The service waits LMK_RECONNECT_DELAY_MS before attempting to rebind, retrying up to LMK_RECONNECT_REBIND_RETRIES times. If all retries fail, the system falls back to the default wallpaper.
DisplayConnector
For multi-display support, each WallpaperConnection maintains a set of DisplayConnector objects, one per display. Each DisplayConnector holds a reference to the per-display IWallpaperEngine instance, allowing independent rendering and offset control on each display.
63.8 WallpaperService.Engine — Rendering Pipeline
WallpaperService.Engine is the abstract rendering component that live wallpaper implementations override. It manages the rendering surface, processes commands from the window manager, and implements the freeze mechanism.
Engine Lifecycle
stateDiagram-v2
[*] --> Created: onCreateEngine()
Created --> Initialized: onCreate(SurfaceHolder)
Initialized --> Visible: onVisibilityChanged(true)
Visible --> OffsetsUpdated: onOffsetsChanged(x, y, xp, yp)
OffsetsUpdated --> Visible: Continue rendering
Visible --> Hidden: onVisibilityChanged(false)
Hidden --> Visible: onVisibilityChanged(true)
Visible --> Destroyed: onDestroy()
Hidden --> Destroyed: onDestroy()
Destroyed --> [*]
Engine Surface Components
| Field | Type | Purpose |
|---|---|---|
mSurfaceControl |
SurfaceControl |
Primary surface for live rendering |
mBlastBufferQueue |
BLASTBufferQueue |
Buffer queue for frame submission |
mScreenshotSurfaceControl |
SurfaceControl |
Secondary surface for frozen screenshot |
mZoom |
float |
Current zoom-out level (0.0 to 1.0) |
mWallpaperDimAmount |
float |
Dim overlay amount for dark theme |
Command Message Table
The engine processes commands dispatched from WallpaperController and WallpaperManager via the MSG_WALLPAPER_COMMAND handler:
| Command | Constant | Source | Description |
|---|---|---|---|
| Tap | COMMAND_TAP |
WallpaperManager |
User tapped on the wallpaper area |
| Freeze | COMMAND_FREEZE |
WallpaperController |
Capture screenshot, stop live rendering |
| Unfreeze | COMMAND_UNFREEZE |
WallpaperController |
Discard screenshot, resume live rendering |
| Waking up | COMMAND_WAKING_UP |
WallpaperManager |
Device waking from sleep |
| Going to sleep | COMMAND_GOING_TO_SLEEP |
WallpaperManager |
Device entering sleep state |
| Keyguard going away | COMMAND_KEYGUARD_GOING_AWAY |
WallpaperManager |
Lock screen dismissing |
Local Color Extraction
The engine supports local color extraction through EngineWindowPage[] and mLocalColorAreas. Client code can register rectangular regions of interest, and the engine samples colors from those regions of the wallpaper, providing WallpaperColors callbacks. This is used by the launcher and system UI to adapt icon and text colors to the wallpaper content underneath them.
63.9 Freeze Mechanism and Letterbox Integration
The freeze mechanism allows the system to replace the live wallpaper with a static screenshot during transitions, preventing visual artifacts caused by the wallpaper continuing to animate while the foreground is changing.
Freeze Sequence
sequenceDiagram
participant WC as WallpaperController
participant WMS as WallpaperManagerService
participant Engine as WallpaperService.Engine
participant SF as SurfaceFlinger
WC->>Engine: COMMAND_FREEZE
Engine->>Engine: Capture current frame as Bitmap
Engine->>SF: Create mScreenshotSurfaceControl
Engine->>SF: Draw Bitmap to screenshot surface
Engine->>SF: Hide mSurfaceControl (live surface)
Engine->>SF: Show mScreenshotSurfaceControl
Note over Engine,SF: Live wallpaper is now frozen
Note over WC: Transition or letterbox completes
WC->>Engine: COMMAND_UNFREEZE
Engine->>SF: Show mSurfaceControl (live surface)
Engine->>SF: Hide and release mScreenshotSurfaceControl
Engine->>Engine: Resume rendering
Note over Engine,SF: Live wallpaper is now active
Freeze Triggers
| Trigger | Source | Reason |
|---|---|---|
| App transition | WallpaperController |
Prevent wallpaper animation during activity switch |
| Letterbox background | WallpaperController |
Wallpaper used as letterbox fill; must remain static |
| Rotation | WallpaperController |
Prevent tearing during display rotation |
| User switch | WallpaperManagerService |
Freeze current wallpaper while switching user context |
Letterbox Integration
When an activity is letterboxed (see Section 62), the system may use the wallpaper as the background fill for the letterbox bars. In this mode, WallpaperController sends COMMAND_FREEZE to ensure the wallpaper remains static while the letterboxed activity is visible. The wallpaper is positioned to align with the full display dimensions, and the letterbox bars become transparent regions that reveal the frozen wallpaper underneath. Upon exiting the letterboxed state, COMMAND_UNFREEZE restores live rendering.
The freeze mechanism uses mScreenshotSurfaceControl, a secondary SurfaceControl layered at the same Z-order as the primary wallpaper surface. The engine captures the current frame into a bitmap, draws it onto the screenshot surface, hides the live surface, and shows the screenshot surface. This swap is atomic from SurfaceFlinger’s perspective, preventing any visible flicker.
63.10 Multi-Display and Multi-Crop Support
Multi-Display
The wallpaper system supports multiple displays through the DisplayConnector mechanism in WallpaperManagerService. Each connected display receives its own IWallpaperEngine instance, allowing independent rendering, offset tracking, and visibility control. The WallpaperInfo metadata includes mSupportMultipleDisplays to indicate whether a live wallpaper can render on secondary displays. Wallpapers that do not support multiple displays fall back to a solid color on secondary screens.
Within the Window Manager, each DisplayContent has its own WallpaperController instance, maintaining independent target detection and offset calculation per display.
Multi-Crop Support
The multi-crop system enables wallpaper images to specify distinct crop rectangles for each display orientation, ensuring optimal framing regardless of device rotation:
| Orientation | Constant | Value | Description |
|---|---|---|---|
| Portrait | ORIENTATION_PORTRAIT |
0 | Standard portrait orientation |
| Landscape | ORIENTATION_LANDSCAPE |
1 | Standard landscape orientation |
| Square portrait | ORIENTATION_SQUARE_PORTRAIT |
2 | Foldable inner display, portrait |
| Square landscape | ORIENTATION_SQUARE_LANDSCAPE |
3 | Foldable inner display, landscape |
Crop hints are set via WallpaperManager.setBitmapWithCrops(Bitmap, Map<Point, Rect>), where the Point key represents display dimensions and the Rect value specifies the crop region within the source bitmap. These hints are stored in WallpaperWindowToken.mCropHints as a SparseArray<Rect> keyed by orientation constant, and are consumed by updateWallpaperOffset() during offset calculation to determine the effective wallpaper dimensions for parallax computation.
63.11 Cross-References
| Section | Topic | Relationship |
|---|---|---|
| Section 22 | DisplayArea Hierarchy | Wallpaper windows occupy a dedicated DisplayArea leaf below app windows |
| Section 23 | Insets and Cutouts | Wallpaper surface extends behind system bar insets for seamless background |
| Section 62 | Letterboxing System | Letterbox background can use frozen wallpaper as fill; COMMAND_FREEZE coordination |
| Section 15 | Shell Transitions | Wallpaper visibility deferred during transitions; freeze prevents animation artifacts |
| Section 8 | WindowToken and WindowState | WallpaperWindowToken extends WindowToken with TYPE_WALLPAPER semantics |
| Section 10 | Surface Management | Wallpaper uses SurfaceControl and BLASTBufferQueue for frame submission |
| Section 5 | Window Types and Layering | TYPE_WALLPAPER has a dedicated layer assignment below application windows |
63.12 Key Source Files
| File | Path | Lines | Role |
|---|---|---|---|
WallpaperController.java |
frameworks/base/services/core/java/com/android/server/wm/WallpaperController.java |
1,083 | WM-side target detection, offset calculation, draw state machine |
WallpaperWindowToken.java |
frameworks/base/services/core/java/com/android/server/wm/WallpaperWindowToken.java |
296 | Z-order positioning, per-token parallax state, visibility deferral |
WallpaperManagerService.java |
frameworks/base/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java |
4,203 | System service: binding, persistence, dual wallpaper, LMK recovery |
WallpaperService.java |
frameworks/base/core/java/android/service/wallpaper/WallpaperService.java |
3,054 | Live wallpaper base class: Engine lifecycle, rendering, freeze |
WallpaperManager.java |
frameworks/base/core/java/android/app/WallpaperManager.java |
3,703 | Public API: wallpaper selection, offsets, commands |
WallpaperInfo.java |
frameworks/base/core/java/android/app/WallpaperInfo.java |
474 | Wallpaper metadata: capabilities, ambient mode, multi-display |
IWallpaperManager.aidl |
frameworks/base/core/java/android/app/IWallpaperManager.aidl |
- | AIDL interface between client and service |
IWallpaperService.aidl |
frameworks/base/core/java/android/service/wallpaper/IWallpaperService.aidl |
- | AIDL interface between service and wallpaper process |
IWallpaperEngine.aidl |
frameworks/base/core/java/android/service/wallpaper/IWallpaperEngine.aidl |
- | AIDL interface for per-display engine control |
64. Display Color Management
64.1 Overview
Android’s display color management subsystem provides a layered pipeline for transforming pixel colors before they reach the physical display panel. The system handles a broad set of use cases: blue-light filtering (Night Display), ambient white-balance adaptation, global and per-app saturation control, accessibility color correction (daltonizer) and inversion, and HDR/wide-gamut color management. All of these transforms are coordinated by ColorDisplayService, a SystemService that publishes three interfaces to the rest of the platform: the binder-backed COLOR_DISPLAY_SERVICE, the local ColorDisplayServiceInternal, and DisplayTransformManager. The architecture separates policy decisions (which transform to activate and when) from matrix arithmetic (how to compose the final 4x4 color matrix) and hardware delivery (Parcel-based transactions to SurfaceFlinger).
A central design principle is the priority-ordered composition of 4x4 color matrices. Six discrete priority levels are defined, ranging from Night Display at 100 to color inversion at 300. When any individual transform changes, all active matrices are multiplied together in ascending priority order and the single resulting matrix is sent to SurfaceFlinger via a Binder transaction. This scheme ensures that lower-priority visual-comfort transforms (Night Display, white balance) compose correctly beneath higher-priority accessibility transforms (grayscale, inversion), and that accessibility transforms always take final precedence.
64.2 Architecture – ColorDisplayService and Transform Pipeline
ColorDisplayService is the central coordinator. It extends SystemService, runs on DisplayThread, and manages the full lifecycle of all color transforms. On startup it publishes three services:
COLOR_DISPLAY_SERVICE– the publicIColorDisplayManagerbinder stub, exposing color-mode selection, Night Display scheduling, and Reduce Bright Colors controls to apps and Settings.ColorDisplayServiceInternal– a local-only interface used by other system services (e.g., the display white balance sensor pipeline) to push ambient CCT updates and control DWB allowed state.DisplayTransformManager– the local service that owns the matrix compositionSparseArrayand the SurfaceFlinger transaction plumbing.
All color-transform state changes are processed on a dedicated TintHandler (a Handler bound to DisplayThread’s Looper), ensuring non-blocking, single-threaded mutation of transform state. Message types include MSG_APPLY_NIGHT_DISPLAY_ANIMATED, MSG_APPLY_DISPLAY_WHITE_BALANCE, MSG_APPLY_REDUCE_BRIGHT_COLORS, and MSG_APPLY_GLOBAL_SATURATION.
Each individual transform is encapsulated in a TintController subclass. The abstract base defines the contract: setUp() for initialization, setMatrix(int) to compute the 4x4 matrix from a scalar parameter (CCT, strength, saturation), getMatrix() to retrieve the current matrix, getLevel() to return the priority constant, and isAvailable() for capability detection. A ValueAnimator drives smooth transitions over a configurable duration (default 3000 ms).
graph TB
subgraph "System Services"
CDS["ColorDisplayService<br/>(SystemService)"]
TH["TintHandler<br/>(DisplayThread Looper)"]
end
subgraph "Published Interfaces"
ICD["IColorDisplayManager<br/>(Binder)"]
CDSI["ColorDisplayServiceInternal<br/>(Local)"]
DTM["DisplayTransformManager<br/>(Local)"]
end
subgraph "TintControllers"
NDT["NightDisplayTintController<br/>Level 100"]
DWB["DisplayWhiteBalanceTintController<br/>Level 125"]
GST["GlobalSaturationTintController<br/>Level 150"]
GS["Grayscale Matrix<br/>Level 200"]
RBC["ReduceBrightColorsTintController<br/>Level 250"]
INV["Invert Color Matrix<br/>Level 300"]
end
subgraph "Per-App"
ASC["AppSaturationController"]
end
subgraph "SurfaceFlinger"
SF_CM["Transaction 1015<br/>COLOR_MATRIX"]
SF_DT["Transaction 1014<br/>DALTONIZER"]
SF_SAT["Transaction 1022<br/>SATURATION"]
SF_DC["Transaction 1023<br/>DISPLAY_COLOR"]
end
CDS --> TH
CDS --> ICD
CDS --> CDSI
CDS --> DTM
TH --> NDT
TH --> DWB
TH --> GST
TH --> RBC
NDT --> DTM
DWB --> DTM
GST --> DTM
GS --> DTM
RBC --> DTM
INV --> DTM
DTM --> SF_CM
DTM --> SF_DT
DTM --> SF_SAT
DTM --> SF_DC
CDS --> ASC
64.3 DisplayTransformManager – Matrix Composition
DisplayTransformManager is the single point through which all 4x4 color matrices are composed and delivered to SurfaceFlinger. It maintains a SparseArray<float[]> indexed by priority level, where each entry holds a 16-element column-major 4x4 matrix.
Priority levels (ascending order of application):
| Level | Constant | Purpose |
|---|---|---|
| 100 | LEVEL_COLOR_MATRIX_NIGHT_DISPLAY |
Blue light filter tint |
| 125 | LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE |
Ambient white point adaptation |
| 150 | LEVEL_COLOR_MATRIX_SATURATION |
Global grayscale / saturation |
| 200 | LEVEL_COLOR_MATRIX_GRAYSCALE |
Accessibility monochrome mode |
| 250 | LEVEL_COLOR_MATRIX_REDUCE_BRIGHT_COLORS |
Brightness reduction for accessibility |
| 300 | LEVEL_COLOR_MATRIX_INVERT_COLOR |
Display color inversion |
The composition algorithm in computeColorMatrixLocked() operates as follows:
- Start with the 4x4 identity matrix.
- Iterate the
SparseArrayin ascending key order (level 100 first, level 300 last). - At each step, multiply the accumulated result by the current level’s matrix using
android.opengl.Matrix.multiplyMM(). - The method uses a double-buffer scheme (
mTempColorMatrix[2][16]) alternating between indicesi % 2and(i + 1) % 2to avoid allocation.
The resulting composite matrix is sent to SurfaceFlinger via applyColorMatrix(), which writes a Parcel containing the android.ui.ISurfaceComposer interface token, a presence flag (1 if non-null, 0 otherwise), and the 16 float values, then issues Binder transaction code 1015 (SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX).
Separate transaction paths exist for other types of color control:
| Transaction Code | Constant | Data Format |
|---|---|---|
| 1015 | COLOR_MATRIX |
int(present) + 16 floats |
| 1014 | DALTONIZER |
int(mode) + int(level) |
| 1022 | SATURATION |
float(saturation) |
| 1023 | DISPLAY_COLOR |
int(color_setting) + optional int(composition_mode) |
| 1030 | QUERY_COLOR_MANAGED |
(query only, returns boolean) |
The setColorMatrix() method performs a diff check (Arrays.equals) before recomputing, avoiding unnecessary SurfaceFlinger transactions when a transform is set to the same value.
64.4 Night Display – Blue Light Filter
Night Display reduces blue light emission by applying a warm color-temperature tint to the display. The NightDisplayTintController is an inner class of ColorDisplayService that generates a diagonal 4x4 matrix from a correlated color temperature (CCT) value.
CCT-to-matrix formula:
For a given color temperature T, nine polynomial coefficients c[0..8] are loaded from device resources (with separate sets for linear and native/non-linear color spaces). The diagonal RGB scale factors are computed as:
red = T^2 * c[0] + T * c[1] + c[2]
green = T^2 * c[3] + T * c[4] + c[5]
blue = T^2 * c[6] + T * c[7] + c[8]
The resulting matrix is:
| red 0 0 0 |
| 0 green 0 0 |
| 0 0 blue 0 |
| 0 0 0 1 |
Lower CCT values produce stronger red/amber tinting (smaller blue coefficient), while higher CCT values approach the identity matrix.
Auto modes:
| Value | Mode | Behavior |
|---|---|---|
| 0 | AUTO_MODE_DISABLED |
Manual on/off only |
| 1 | AUTO_MODE_CUSTOM_TIME |
Activates/deactivates at user-specified start/end times via AlarmManager |
| 2 | AUTO_MODE_TWILIGHT |
Activates at sunset, deactivates at sunrise using TwilightManager location data |
When activated, the transition from identity to the tinted matrix is animated over the default 3000 ms transition duration via ValueAnimator with a ColorMatrixEvaluator that interpolates all 16 matrix elements. Temperature changes while active are applied immediately (without animation) via MSG_APPLY_NIGHT_DISPLAY_IMMEDIATE.
Preemption behavior: When Night Display activates, it calls updateDisplayWhiteBalanceStatus(), which disables Display White Balance to avoid conflicting color temperature transforms operating simultaneously.
64.5 Display White Balance – Chromatic Adaptation
DisplayWhiteBalanceTintController implements ambient-responsive white-point adaptation using the Bradford chromatic adaptation method. Unlike Night Display which applies a fixed user-selected CCT, Display White Balance continuously adjusts the display’s white point to match ambient lighting conditions, reducing perceived color shifts under different illuminants.
Initialization and configuration:
During setUp(), the controller loads:
- Display primaries (RGB + white XYZ chromaticities) from
SurfaceControl.getDisplayNativePrimaries(), with a resource-based fallback. - Nominal white point XYZ coordinates and CCT (typically D65 at 6500K).
- Min/max color temperature bounds and a default temperature.
- Separate transition durations for increasing and decreasing CCT changes.
- CCT range minimums and step sizes for the
CctEvaluator.
Bradford adaptation flow:
- Convert the target CCT to CIE XYZ coordinates via
ColorSpace.cctToXyz(cct). - Compute the Bradford chromatic adaptation matrix using
ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.BRADFORD, nominalWhiteXYZ, targetXYZ). - Transform the adaptation matrix from XYZ space to the display’s RGB space:
result = displayRGBtoXYZ^-1 * bradfordMatrix * displayRGBtoXYZThis is computed as
mul3x3(inverseTransform, mul3x3(adaptation, transform)). - Normalize by dividing all coefficients by the maximum of the adapted R, G, B peak values, ensuring no channel exceeds 1.0.
- Pack the 3x3 result into the upper-left portion of a 4x4 identity matrix.
Validation: Each coefficient is checked with isColorMatrixCoeffValid() to reject NaN and Infinity values before applying.
The controller supports asymmetric transition durations: mTransitionDurationIncrease for warming (CCT increasing) and mTransitionDurationDecrease for cooling. The CctEvaluator provides non-uniform step sizes across different CCT ranges, enabling finer control where human perception is most sensitive.
64.6 Saturation and Brightness Controls
Global Saturation (GlobalSaturationTintController)
GlobalSaturationTintController applies a device-wide saturation adjustment at priority level 150. The saturation level ranges from 0 (full grayscale) to 100 (no adjustment). The transform matrix uses luminance coefficients:
luminanceR = 0.231, luminanceG = 0.715, luminanceB = 0.072
For a given saturation factor s (0.0 to 1.0) and desaturation d = 1 - s:
| (0.231*d + s) 0.715*d 0.072*d 0 |
| 0.231*d (0.715*d + s) 0.072*d 0 |
| 0.231*d 0.715*d (0.072*d + s) 0 |
| 0 0 0 1 |
At s = 1.0 this becomes the identity matrix and the controller deactivates itself. At s = 0.0 every pixel is mapped to its luminance value (full grayscale).
Reduce Bright Colors (ReduceBrightColorsTintController)
ReduceBrightColorsTintController (priority level 250) dims the display by scaling RGB values uniformly. It uses a quadratic formula with three device-specific coefficients loaded from resources (with separate linear and non-linear sets):
componentValue = clamp(strength^2 * c[0] + strength * c[1] + c[2])
where strength is the percentage (0-100) normalized to 0.0-1.0. The resulting scalar is applied equally to the R, G, and B diagonal elements of a 4x4 identity matrix:
| cv 0 0 0 |
| 0 cv 0 0 |
| 0 0 cv 0 |
| 0 0 0 1 |
The controller also provides getAdjustedBrightness(nits) and getOffsetFactor() for integration with the brightness system and the Even Dimmer feature. The offset factor equals the sum of all three coefficients, representing the scaling at maximum strength.
64.7 Color Mode System
Android exposes a set of color modes that control how SurfaceFlinger manages the display’s color pipeline. ColorDisplayManager defines four standard modes and a vendor-extensible range:
| Value | Mode | SF Saturation | SF DisplayColor | Description |
|---|---|---|---|---|
| 0 | COLOR_MODE_NATURAL |
1.0 | MANAGED (0) |
Accurate sRGB rendering, color-managed |
| 1 | COLOR_MODE_BOOSTED |
1.1 | MANAGED (0) |
Slightly boosted saturation, color-managed |
| 2 | COLOR_MODE_SATURATED |
1.0 | UNMANAGED (1) |
Native panel gamut, no color management |
| 3 | COLOR_MODE_AUTOMATIC |
1.0 | ENHANCED (2) |
Content-adaptive color enhancement |
| 256-511 | Vendor range | 1.0 | (vendor-defined) | OEM-specific color profiles |
When setColorMode() is called on DisplayTransformManager, it:
- Sets the global saturation via transaction 1022 (either 1.0 or 1.1).
- Sets the DisplayColor setting via transaction 1023 along with the optional composition color mode.
- Persists both values as system properties (
persist.sys.sf.color_saturation,persist.sys.sf.native_mode,persist.sys.sf.color_mode). - Immediately re-applies the Night Display and Reduce Bright Colors matrices, since the color space linearity may have changed (Saturated mode uses non-linear gamma space; managed modes use linear).
- Calls
ActivityTaskManager.updateConfiguration()to propagate the change to running activities.
The needsLinearColorMatrix() method determines whether transforms should compute matrices for linear or gamma-encoded color spaces. When DisplayColor is anything other than UNMANAGED, SurfaceFlinger operates in a linear color-managed pipeline, requiring linear-space coefficients for Night Display and Reduce Bright Colors.
64.8 Accessibility Color Transforms
Accessibility color transforms operate at the highest priority levels in the matrix composition pipeline, ensuring they override all visual-comfort transforms.
Color Inversion (Level 300)
Color inversion uses a pre-computed constant matrix (MATRIX_INVERT_COLOR) that performs a luminance-preserving color inversion through the YIQ color space:
| 0.402 -1.174 -0.228 1.0 |
| -0.598 -0.174 -0.228 1.0 |
| -0.599 -1.175 0.772 1.0 |
| 0.0 0.0 0.0 1.0 |
The last column encodes a non-multiplied additive translation. The transform converts RGB to YIQ, rotates the chrominance (I, Q) by 180 degrees while preserving luminance (Y), converts back to RGB, and subtracts from white. This approach preserves perceived brightness while inverting hue, making it more usable than a simple 1 - RGB inversion.
Grayscale (Level 200)
The accessibility grayscale transform uses a constant matrix (MATRIX_GRAYSCALE) based on Rec. 709 luminance coefficients:
| 0.2126 0.7152 0.0722 0 |
| 0.2126 0.7152 0.0722 0 |
| 0.2126 0.7152 0.0722 0 |
| 0 0 0 1 |
Every output channel receives the same luminance value, collapsing color to monochrome.
Daltonizer – Color Blindness Correction
The daltonizer operates through a separate SurfaceFlinger transaction path (code 1014) rather than the color matrix pipeline. It sends both a mode and a saturation level:
| Mode | Constant | Value | Description |
|---|---|---|---|
| Disabled | DALTONIZER_DISABLED |
-1 | No correction applied |
| Simulate monochromacy | DALTONIZER_SIMULATE_MONOCHROMACY |
0 | Full monochrome simulation |
| Correct deuteranomaly | DALTONIZER_CORRECT_DEUTERANOMALY |
12 | Red-green (green-weak) correction |
SurfaceFlinger decodes the integer as n % 10 for type (1=protanomaly, 2=deuteranomaly, 3=tritanomaly) and n >= 10 to select correction vs simulation mode. Thus 12 means “correct deuteranomaly” (12 % 10 = 2, 12 >= 10). The constants are defined in AccessibilityManager.java. SurfaceFlinger implements the actual daltonizer algorithms in its shader pipeline, separate from the composed color matrix.
64.9 Per-App Saturation
AppSaturationController provides window-level saturation control without affecting the global display transform. It maintains a Map<String, SparseArray<SaturationController>> mapping package names to per-userId saturation state, where each SaturationController holds:
- An
ArrayMap<String, Integer>of calling-package-to-saturation-level entries, allowing multiple callers to set saturation for the same target app. - A list of
WeakReference<ColorTransformController>instances, one per window belonging to the target app.
When multiple callers set different saturation levels for the same app, the minimum value wins (calculateSaturationLevel() returns the lowest). The computed saturation is applied as a 3x3 grayscale-blend matrix using the same luminance coefficients as GlobalSaturationTintController, delivered per-window via ColorTransformController.applyAppSaturation() rather than through SurfaceFlinger’s global pipeline.
Expired WeakReference entries are lazily purged during updates, preventing memory leaks from destroyed windows.
64.10 SurfaceFlinger Color Pipeline
On the native side, SurfaceFlinger implements a hardware-accelerated color management pipeline that processes the transforms received from DisplayTransformManager.
Wide color gamut support:
| Color Space | Description |
|---|---|
| sRGB | Default, standard gamut |
| Display P3 | Wide gamut, common on modern OLED panels |
| BT.2020 | Ultra-wide gamut for HDR content |
HDR capabilities:
| Mode | Description |
|---|---|
| BT2100_PQ | Perceptual Quantizer HDR (10-bit, static metadata) |
| BT2100_HLG | Hybrid Log-Gamma HDR (broadcast standard) |
| HDR10 | Static metadata HDR |
| HDR10+ | Dynamic metadata HDR |
| Dolby Vision | Proprietary dynamic HDR |
Output color settings (DisplayColorSetting):
| Value | Name | Behavior |
|---|---|---|
| 0 | kManaged |
Full color management; transforms applied in linear light; content mapped to display gamut |
| 1 | kUnmanaged |
No color management; native panel gamut; transforms in gamma space |
| 2 | kEnhanced |
Content-aware enhancement; sRGB content rendered natively, wide-gamut content color-managed |
Render intents:
SurfaceFlinger supports multiple render intents through the Hardware Composer HAL:
COLORIMETRIC– Accurate color reproduction within the target gamut.ENHANCE– Perceptually pleasing rendering that may expand colors.TONE_MAP_COLORIMETRIC/TONE_MAP_ENHANCE– Variants that include HDR-to-SDR tone mapping.
The DisplayColorProfile in SurfaceFlinger aggregates the panel’s native color capabilities (gamut, supported HDR modes, peak luminance) and selects appropriate composition strategies. The color matrix received via transaction 1015 is applied in the composition pipeline, either in linear light (managed modes) or gamma-encoded space (unmanaged mode), matching the matrix space computed by the framework.
64.11 Cross-References
- Section 39 – HDR Pipeline: HDR tone mapping, PQ/HLG EOTF handling, and dynamic metadata processing that feeds into the color profile system described here.
- Section 24 – Surface System: The SurfaceFlinger composition pipeline through which color matrices are applied to layer output.
- Section 43 – RenderEngine: GPU-based composition where color transform matrices are applied as shader uniforms.
- Section 66 – Accessibility Services: The accessibility framework that triggers daltonizer, inversion, and grayscale transforms via
Settings.Securecontent observers.
64.12 Key Source Files
| File | Path | Description |
|---|---|---|
ColorDisplayService.java |
frameworks/base/services/core/java/com/android/server/display/color/ColorDisplayService.java |
Central SystemService; coordinates all color transforms; contains NightDisplayTintController inner class |
DisplayTransformManager.java |
frameworks/base/services/core/java/com/android/server/display/color/DisplayTransformManager.java |
Matrix composition engine; SurfaceFlinger transaction delivery |
TintController.java |
frameworks/base/services/core/java/com/android/server/display/color/TintController.java |
Abstract base for all color transform controllers |
DisplayWhiteBalanceTintController.java |
frameworks/base/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java |
Bradford chromatic adaptation for ambient white balance |
GlobalSaturationTintController.java |
frameworks/base/services/core/java/com/android/server/display/color/GlobalSaturationTintController.java |
Device-wide saturation adjustment |
ReduceBrightColorsTintController.java |
frameworks/base/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java |
Quadratic brightness reduction for accessibility |
AppSaturationController.java |
frameworks/base/services/core/java/com/android/server/display/color/AppSaturationController.java |
Per-app per-window saturation via WeakReference management |
ColorDisplayManager.java |
frameworks/base/core/java/android/hardware/display/ColorDisplayManager.java |
Public API for color mode, Night Display, and RBC settings |
65. Window Types and Z-Order Policy
65.1 Overview
Every window in the Android window management system carries a type identifier that determines its position in the visual stacking order, its permission requirements, its input dispatch eligibility, and its lifecycle semantics. The window type is an integer constant defined in WindowManager.LayoutParams and is set at window creation time. Once assigned, the type drives a deterministic mapping through WindowManagerPolicy.getWindowLayerFromTypeLw() into a 36-layer Z-order model that governs how the SurfaceFlinger composition stack is assembled.
The type system partitions the integer space into three non-overlapping ranges: application windows (1–99), sub-windows (1000–1999), and system windows (2000–2999). Application windows are owned by activities and managed through the activity lifecycle. Sub-windows exist as children of a parent window and inherit visibility, focus, and display properties from that parent. System windows are created by privileged system services and the framework itself, covering everything from the status bar and navigation bar to input methods, toasts, and accessibility overlays.
The Z-order policy is not a simple function of type value magnitude. A wallpaper window (type 2013, a system window) sits at layer 1, below all application windows at layer 2. The input method (type 2011) occupies layer 13, above application overlays but below the status bar. This deliberate decoupling of type namespace from visual ordering gives the framework precise control over the composition stack while maintaining a stable, well-documented type taxonomy for third-party applications.
The layer assignment feeds into a numeric base-layer calculation: baseLayer = layer * 10000 + 1000. Within a single layer, a secondary multiplier of 5 separates individual windows, allowing fine-grained insertion without renumbering. Sub-windows use a separate sublayer scheme ranging from -2 to +3 relative to the parent, enabling media surfaces to render behind the parent while panel overlays render above it.
65.2 Window Type Taxonomy
The three type ranges define fundamentally different ownership, permission, and lifecycle models:
| Range | Name | Integer Span | Count | Owner | Permission |
|---|---|---|---|---|---|
| Application | FIRST_APPLICATION_WINDOW to LAST_APPLICATION_WINDOW |
1–99 | 4 defined | Activity/App process | None (managed by ActivityManager) |
| Sub-window | FIRST_SUB_WINDOW to LAST_SUB_WINDOW |
1000–1999 | 6 defined | App process (attached to parent) | None (inherits from parent) |
| System | FIRST_SYSTEM_WINDOW to LAST_SYSTEM_WINDOW |
2000–2999 | 18+ defined | System services / privileged apps | Varies (most require system UID or signature) |
The gaps within each range are intentional. Application types 5–99 are reserved for future use. Sub-window types 1006–1999 are similarly reserved. The system window range has the densest allocation but still contains significant gaps, with only roughly 20 of the 1000 possible values assigned.
A window’s type is validated at addition time in WindowManagerService.addWindow(). The service rejects unknown types, enforces permission checks based on type, and routes the window to the correct DisplayArea container in the hierarchy. Type misuse—such as an unprivileged application attempting to add a TYPE_STATUS_BAR—results in a BadTokenException or SecurityException.
65.3 Application Window Types
Application windows are the primary content surfaces visible to the user. They are managed by ActivityRecord and participate in the activity lifecycle, transitions, and task management.
| Constant | Value | Description |
|---|---|---|
TYPE_BASE_APPLICATION |
1 | The base window of an activity, created by PhoneWindow during Activity.onCreate(). Every activity has exactly one base application window. This is the window that receives the activity’s decor view hierarchy. |
TYPE_APPLICATION |
2 | A normal application window. Used by Dialog and other non-activity windows created by an application. These windows require a valid activity token and are stacked relative to their owning activity. |
TYPE_APPLICATION_STARTING |
3 | The starting (splash screen) window shown by the system while the application process is launching. Created by StartingSurfaceController and owned by the system, not the app process. Removed once the app draws its first frame. |
TYPE_DRAWN_APPLICATION |
4 | A variant of TYPE_BASE_APPLICATION that explicitly signals the system to wait for the application to draw before performing transition animations. Used when the app opts into the windowIsTranslucent or custom drawing scenarios where the framework cannot auto-detect first-draw readiness. |
All four types map to the same Z-order layer (layer 2) in getWindowLayerFromTypeLw(). Their ordering within that layer is determined by the activity stack order maintained by Task and ActivityRecord. The starting window (type 3) is special: it is created before the application process has drawn and is replaced in-place by the real base application window once drawing completes. This replacement is managed by ActivityRecord.removeStartingWindow() and is coordinated with WindowAnimator to produce a seamless visual transition.
65.4 Sub-Window Types and Parent-Child Ordering
Sub-windows are attached to a parent window and cannot exist independently. They are identified by the range check type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW in WindowState. When a sub-window is added, the system locates its parent via the token and establishes a parent-child relationship through mIsChildWindow and the mChildren list in the parent WindowState.
Each sub-window type has a fixed sublayer offset relative to its parent, assigned by getSubWindowLayerFromTypeLw():
| Constant | Value | Sublayer | Position Relative to Parent |
|---|---|---|---|
TYPE_APPLICATION_MEDIA |
1001 | -2 | Behind parent (video/camera surface) |
TYPE_APPLICATION_MEDIA_OVERLAY |
1004 | -1 | Behind parent, above media |
| Parent surface | — | 0 | The parent window itself |
TYPE_APPLICATION_PANEL |
1000 | +1 | Above parent (popup menus, dropdowns) |
TYPE_APPLICATION_ATTACHED_DIALOG |
1003 | +1 | Above parent (modal dialogs) |
TYPE_APPLICATION_SUB_PANEL |
1002 | +2 | Above panels (nested popups) |
TYPE_APPLICATION_ABOVE_SUB_PANEL |
1005 | +3 | Topmost child (tooltips, selection handles) |
graph TB
subgraph Parent Window Sublayer Stack
L3["Sublayer +3: ABOVE_SUB_PANEL"]
L2["Sublayer +2: SUB_PANEL"]
L1["Sublayer +1: PANEL / ATTACHED_DIALOG"]
L0["Sublayer 0: Parent Window"]
LN1["Sublayer -1: MEDIA_OVERLAY"]
LN2["Sublayer -2: MEDIA"]
end
L3 --> L2 --> L1 --> L0 --> LN1 --> LN2
The negative sublayers are essential for video playback. TYPE_APPLICATION_MEDIA at sublayer -2 holds the SurfaceView or MediaCodec output surface, rendering behind the parent’s view hierarchy. The parent window typically has a transparent “hole” punched through its surface so the media content shows through. TYPE_APPLICATION_MEDIA_OVERLAY at sublayer -1 sits between the media surface and the parent, used for subtitle rendering or semi-transparent controls that need to appear above the video but below the main UI.
All sub-windows except TYPE_APPLICATION_ATTACHED_DIALOG set mLayoutAttached = true, meaning they are laid out relative to the parent’s frame rather than the display. ATTACHED_DIALOG is the exception: while it is a sub-window in terms of Z-order and lifecycle, it is laid out independently against the display frame, which allows it to be centered on screen rather than constrained to the parent’s bounds.
Sub-windows inherit certain behavioral flags from their parent. mIsImWindow is propagated so that sub-windows of the input method are treated as part of the IME layer. Similarly, mIsWallpaper propagation ensures wallpaper sub-windows participate in wallpaper-specific parallax and visibility logic.
65.5 System Window Types — Complete Catalog
System windows occupy the 2000–2999 range and represent framework-managed UI elements. Most require system-level permissions or can only be created by processes running as the system UID. The complete catalog of defined types is as follows:
| Constant | Value | Layer | Description |
|---|---|---|---|
TYPE_STATUS_BAR |
2000 | 15 | The primary status bar at the top of the display. Created by SystemUI StatusBar service. Only one instance per display. Controls notification icons, clock, battery, and system indicators. |
TYPE_SEARCH_BAR |
2001 | 4 | The legacy search bar overlay. Rarely used in modern Android; superseded by the launcher search widget and Assistant overlay. |
TYPE_PHONE |
2002 | 3 | Phone-priority window for incoming call UI. Deprecated in favor of TYPE_APPLICATION_OVERLAY for third-party use; still used internally by the Telecom framework. |
TYPE_SYSTEM_ALERT |
2003 | 9/12 | System alert dialogs such as low-battery warnings or permission prompts. Layer depends on caller: non-system callers get layer 9, system callers get layer 12 (above application overlay). Deprecated for third-party use since API 26. |
TYPE_TOAST |
2005 | 7/27 | Toast notification windows. Non-system toasts are at layer 7 (above system dialog but below application overlay). System toasts are elevated to layer 27, above the navigation bar, ensuring system messages remain visible. |
TYPE_INPUT_METHOD |
2011 | 13 | The input method editor (soft keyboard) surface. Created by InputMethodManagerService on behalf of the current IME. Sits above application overlays to ensure the keyboard is always accessible. See Section 67 for IME architecture. |
TYPE_INPUT_METHOD_DIALOG |
2012 | 14 | Dialogs shown by the input method, such as language selection or keyboard settings. One layer above the IME itself to ensure dialog visibility. |
TYPE_WALLPAPER |
2013 | 1 | Live or static wallpaper surfaces. Despite being a system window type (2013), assigned to layer 1, the lowest in the stack. See Section 63 for wallpaper management. |
TYPE_DRAG |
2016 | 30 | Drag shadow surface during cross-window drag and drop operations. Placed at layer 30 to remain visible above nearly all other content. Managed by DragState in the window manager. |
TYPE_NAVIGATION_BAR |
2019 | 24 | The system navigation bar (back, home, recents buttons or gesture handle). Created by SystemUI NavigationBarController. One instance per display with navigation capability. |
TYPE_VOLUME_OVERLAY |
2020 | 22 | Volume adjustment panel shown when hardware volume keys are pressed. Managed by SystemUI VolumeDialogController. Placed above voice interaction but below the navigation bar. |
TYPE_BOOT_PROGRESS |
2021 | 34 | Boot animation and progress indication shown during system startup. Near the top of the stack (layer 34) to remain visible above everything except the pointer and rounded corners. |
TYPE_VOICE_INTERACTION |
2031 | 21 | Voice interaction session overlay (e.g., Google Assistant full-screen UI). Sits above the volume overlay in the layer stack. |
TYPE_ACCESSIBILITY_OVERLAY |
2032 | 31 | Accessibility service overlays for screen readers, magnification controls, and switch access UI. Layer 31 places these above nearly all content, ensuring accessibility tools are always reachable. Requires BIND_ACCESSIBILITY_SERVICE. See Section 66. |
TYPE_DOCK_DIVIDER |
2034 | 3 | Split-screen divider handle. Placed at the same layer as presentation windows, visually integrated with the application workspace. Managed by DockedStackDividerController. |
TYPE_APPLICATION_OVERLAY |
2038 | 11 | The modern replacement for deprecated overlay types (SYSTEM_ALERT, PHONE, SYSTEM_OVERLAY). Third-party applications use this type for legitimate overlay scenarios such as floating widgets, picture-in-picture custom overlays, or chat heads. Requires SYSTEM_ALERT_WINDOW permission and user consent via Settings.canDrawOverlays(). |
TYPE_NOTIFICATION_SHADE |
2040 | 17 | The notification shade (pull-down panel) created by SystemUI. Layer 17 places it above the status bar but below keyguard dialogs, ensuring the shade appears over status bar content while remaining below security-critical UI. |
TYPE_STATUS_BAR_ADDITIONAL |
2041 | 16 | Secondary status bar elements for multi-display configurations or additional status bar components. One layer above the primary status bar. |
Additional system types not always enumerated but present in the layer assignment include:
| Internal Type | Layer | Description |
|---|---|---|
PRESENTATION |
3 | Presentation display windows |
PRIVATE_PRESENTATION |
3 | Private virtual display presentations |
QS_DIALOG |
3 | Quick Settings dialog tiles |
INPUT_CONSUMER |
5 | Invisible input consumer for gesture handling |
SYSTEM_DIALOG |
6 | System dialog windows |
PRIORITY_PHONE |
8 | Priority phone call windows |
SYSTEM_OVERLAY (non-system) |
10 | Legacy system overlay from non-system callers |
STATUS_BAR_SUB_PANEL |
18 | Status bar dropdown sub-panels |
KEYGUARD_DIALOG |
19 | Keyguard (lock screen) dialogs |
VOICE_INTERACTION_STARTING |
20 | Voice interaction starting transition |
SYSTEM_OVERLAY (system) |
23 | System overlay from system-UID callers |
NAVIGATION_BAR_PANEL |
25 | Navigation bar popup panels |
SCREENSHOT |
26 | Screenshot capture surface |
SYSTEM_ERROR (non-system) |
9 | Application crash dialogs (non-system) |
SYSTEM_ERROR (system) |
27 | System error dialogs (system caller) |
MAGNIFICATION_OVERLAY |
28 | Screen magnification overlay frame |
DISPLAY_OVERLAY |
29 | Display overlay for debugging |
ACCESSIBILITY_MAGNIFICATION_OVERLAY |
32 | Accessibility magnification overlay |
SECURE_SYSTEM_OVERLAY |
33 | Secure system overlays that cannot be screenshot |
POINTER |
35 | Mouse pointer cursor surface |
ROUNDED_CORNER (MAX) |
36 | Rounded corner overlay, the absolute topmost layer |
65.6 Z-Order Layer Assignment — The 36-Layer Model
The method WindowManagerPolicy.getWindowLayerFromTypeLw() implements a 36-layer Z-order model. Each window type maps to exactly one layer, though the same layer may be shared by multiple types. The full model, ordered from bottom (layer 1) to top (layer 36), is:
| Layer | Window Type(s) | Purpose |
|---|---|---|
| 1 | WALLPAPER |
Background wallpaper surface |
| 2 | APPLICATION (all types 1–99) |
All activity and application content |
| 3 | PRESENTATION, PRIVATE_PRESENTATION, DOCK_DIVIDER, QS_DIALOG, PHONE |
Application-adjacent system UI |
| 4 | SEARCH_BAR |
Legacy search overlay |
| 5 | INPUT_CONSUMER |
Invisible input event consumer |
| 6 | SYSTEM_DIALOG |
System dialog windows |
| 7 | TOAST (non-system caller) |
Application toast notifications |
| 8 | PRIORITY_PHONE |
Priority phone call overlay |
| 9 | SYSTEM_ALERT (non-system), SYSTEM_ERROR (non-system) |
Application alert and error dialogs |
| 10 | SYSTEM_OVERLAY (non-system caller) |
Legacy system overlay from apps |
| 11 | APPLICATION_OVERLAY |
Modern third-party overlay windows |
| 12 | SYSTEM_ALERT (system caller) |
System-privileged alert dialogs |
| 13 | INPUT_METHOD |
Soft keyboard surface |
| 14 | INPUT_METHOD_DIALOG |
IME picker and settings dialogs |
| 15 | STATUS_BAR |
Primary status bar |
| 16 | STATUS_BAR_ADDITIONAL |
Secondary status bar elements |
| 17 | NOTIFICATION_SHADE |
Pull-down notification panel |
| 18 | STATUS_BAR_SUB_PANEL |
Status bar dropdown menus |
| 19 | KEYGUARD_DIALOG |
Lock screen dialogs |
| 20 | VOICE_INTERACTION_STARTING |
Voice assistant launch transition |
| 21 | VOICE_INTERACTION |
Voice assistant session UI |
| 22 | VOLUME_OVERLAY |
Volume adjustment panel |
| 23 | SYSTEM_OVERLAY (system caller) |
Privileged system overlays |
| 24 | NAVIGATION_BAR |
System navigation bar |
| 25 | NAVIGATION_BAR_PANEL |
Navigation bar popup panels |
| 26 | SCREENSHOT |
Screenshot capture surface |
| 27 | TOAST (system), SYSTEM_ERROR (system) |
System toasts and crash dialogs |
| 28 | MAGNIFICATION_OVERLAY |
Screen magnification frame |
| 29 | DISPLAY_OVERLAY |
Debug display overlay |
| 30 | DRAG |
Drag and drop shadow surface |
| 31 | ACCESSIBILITY_OVERLAY |
Accessibility service overlays |
| 32 | ACCESSIBILITY_MAGNIFICATION_OVERLAY |
Accessibility magnification |
| 33 | SECURE_SYSTEM_OVERLAY |
Secure overlay (immune to screenshots) |
| 34 | BOOT_PROGRESS |
Boot animation |
| 35 | POINTER |
Mouse cursor |
| 36 | MAX (rounded corner overlay) |
Hardware rounded corner masking |
Several design decisions are worth noting. The IME at layer 13 sits above APPLICATION_OVERLAY (layer 11), ensuring the keyboard is never obscured by third-party overlays. The navigation bar at layer 24 is above the volume overlay (22) and voice interaction (21), so gesture navigation always takes priority. Accessibility overlays at layer 31 are above nearly everything, reflecting the principle that accessibility tools must remain functional regardless of what other UI is active. The pointer at layer 35 and rounded corners at layer 36 are the only elements that can appear above boot progress, as they are fundamental to display compositing itself.
The split behavior for TOAST, SYSTEM_ALERT, SYSTEM_ERROR, and SYSTEM_OVERLAY based on caller identity is a security measure introduced to prevent malicious overlays from obscuring system-critical UI. A non-system application’s toast appears at layer 7, well below the status bar, while a system toast at layer 27 can appear above the navigation bar to deliver critical system messages.
65.7 Layer Calculation Formula
The abstract layer number (1–36) from getWindowLayerFromTypeLw() is converted into a numeric base layer value used for SurfaceControl Z-order assignment. The formula is:
mBaseLayer = getWindowLayerLw(type) * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET
Where:
TYPE_LAYER_MULTIPLIER = 10000TYPE_LAYER_OFFSET = 1000
This produces base layer values with a 10,000-unit gap between each abstract layer, providing ample space for inserting multiple windows within the same layer. The offset of 1,000 ensures that even the lowest layer (layer 1) has a positive base value.
Example calculations:
| Window Type | Abstract Layer | Base Layer Calculation | Base Layer Value |
|---|---|---|---|
WALLPAPER |
1 | 1 * 10000 + 1000 | 11,000 |
APPLICATION |
2 | 2 * 10000 + 1000 | 21,000 |
APPLICATION_OVERLAY |
11 | 11 * 10000 + 1000 | 111,000 |
INPUT_METHOD |
13 | 13 * 10000 + 1000 | 131,000 |
STATUS_BAR |
15 | 15 * 10000 + 1000 | 151,000 |
NAVIGATION_BAR |
24 | 24 * 10000 + 1000 | 241,000 |
POINTER |
35 | 35 * 10000 + 1000 | 351,000 |
Within a single layer, multiple windows are spaced using WINDOW_LAYER_MULTIPLIER = 5. When a new window is added to a layer that already contains windows, its Z-order is computed as:
z = mBaseLayer + (position_in_layer * WINDOW_LAYER_MULTIPLIER)
The multiplier of 5 allows up to 2,000 windows per abstract layer (10,000 / 5 = 2,000) while leaving integer slots between them for fine-grained reordering during animations or transitions. In practice, most layers contain fewer than a dozen windows at any given time.
Sub-window ordering uses the sublayer offset directly. A parent window at Z-order z will have its children at z + sublayer, where sublayer is in the range [-2, +3] as defined by getSubWindowLayerFromTypeLw(). This keeps sub-windows tightly coupled to their parent in the composition stack.
65.8 Permission Requirements
Window type creation is gated by permission checks in WindowManagerService.addWindow(). The following table summarizes the permission model:
| Window Type | Required Permission | Enforcement |
|---|---|---|
TYPE_APPLICATION (1–99) |
None | Must have valid activity token from ActivityManagerService |
TYPE_APPLICATION_PANEL (1000–1005) |
None | Must reference valid parent window token |
TYPE_APPLICATION_OVERLAY (2038) |
SYSTEM_ALERT_WINDOW |
Checked via Settings.canDrawOverlays(); requires user consent through Settings UI |
TYPE_PHONE (2002) |
SYSTEM_ALERT_WINDOW |
Deprecated; redirected to TYPE_APPLICATION_OVERLAY behavior for target SDK 26+ |
TYPE_SYSTEM_ALERT (2003) |
SYSTEM_ALERT_WINDOW |
Deprecated; same redirect as TYPE_PHONE |
TYPE_SYSTEM_OVERLAY (2006) |
SYSTEM_ALERT_WINDOW |
Deprecated; same redirect |
TYPE_TOAST (2005) |
None (rate-limited) | Framework enforces toast duration limits and rate limiting; TYPE_TOAST windows auto-dismiss |
TYPE_ACCESSIBILITY_OVERLAY (2032) |
BIND_ACCESSIBILITY_SERVICE |
Only grantable to declared accessibility services via Settings |
TYPE_STATUS_BAR (2000) |
STATUS_BAR_SERVICE |
Signature-level; only system UI process |
TYPE_NAVIGATION_BAR (2019) |
STATUS_BAR_SERVICE |
Signature-level; only system UI process |
TYPE_INPUT_METHOD (2011) |
android.permission.INTERNAL_SYSTEM_WINDOW or IME framework |
Managed by InputMethodManagerService; only the current IME may create |
TYPE_KEYGUARD_DIALOG |
System UID | Only the keyguard (lock screen) process |
TYPE_BOOT_PROGRESS (2021) |
System UID | Only during system boot sequence |
TYPE_DRAG (2016) |
Internal | Created only by DragState within WindowManagerService |
| All other system types (2000+) | INTERNAL_SYSTEM_WINDOW |
Signature-level permission; framework-only |
The SYSTEM_ALERT_WINDOW permission underwent significant changes in Android 8.0 (API 26). Previously, applications could freely use TYPE_SYSTEM_ALERT, TYPE_PHONE, and TYPE_SYSTEM_OVERLAY with just the permission grant. Post-API 26, these types are deprecated for third-party use and all overlay scenarios are funneled through TYPE_APPLICATION_OVERLAY, which additionally requires explicit user consent through a Settings UI toggle. Applications targeting SDK 26 or higher that attempt to use the deprecated types have them silently converted to TYPE_APPLICATION_OVERLAY.
65.9 Window Type Behavior Impact
The window type influences not only Z-ordering but also a range of behavioral properties:
| Behavior | Application (1–99) | Sub-Window (1000–1999) | System (2000–2999) |
|---|---|---|---|
| Focusability | Focusable by default; participates in focus chain through ActivityRecord and Task ordering |
Inherits focusability from parent; PANEL and SUB_PANEL are focusable, MEDIA and MEDIA_OVERLAY are not |
Varies: INPUT_METHOD is focusable, STATUS_BAR and NAVIGATION_BAR have restricted focus, WALLPAPER and TOAST are never focusable |
| Touchability | Receives touch events within bounds; respects FLAG_NOT_TOUCHABLE |
Receives touch within own bounds; MEDIA surfaces pass touch through to parent |
Most system windows are touchable within bounds; POINTER, DRAG, and MAGNIFICATION_OVERLAY consume or redirect input |
| Persistence | Tied to activity lifecycle; removed on Activity.onDestroy() or process death |
Removed when parent is removed; cannot outlive parent window | Varies: STATUS_BAR and NAVIGATION_BAR persist across configuration changes; TOAST auto-removes after timeout; INPUT_METHOD persists while IME service is bound |
| Configuration changes | Destroyed and recreated unless configChanges is declared |
Follows parent behavior | Most persist; system services handle configuration changes internally |
| Multi-display | Shown on the display of the owning Task |
Shown on same display as parent | Some are per-display (STATUS_BAR, NAVIGATION_BAR); others are default-display only (BOOT_PROGRESS) |
| Screenshot capture | Captured by default | Captured with parent | Most captured; SECURE_SYSTEM_OVERLAY (layer 33) is excluded from screenshots |
| Animation | Full transition animations via WindowAnimator |
Animated with parent unless FLAG_LAYOUT_NO_LIMITS |
Minimal or no animation for most types; NOTIFICATION_SHADE and VOLUME_OVERLAY have custom animations |
65.10 DisplayArea Feature Mapping
The DisplayAreaPolicy maps window types to specific DisplayArea.Tokens containers within the display hierarchy. This mapping determines where in the DisplayContent tree a window’s SurfaceControl is attached, which in turn controls its actual compositing order relative to DisplayArea features like TaskDisplayArea and ImeContainer.
The standard mapping in DisplayAreaPolicyBuilder groups types into features:
- Feature: WindowedMagnification — Encompasses layers 0–31, covering everything from wallpaper through accessibility overlays. Windows in this range participate in screen magnification.
- Feature: HideDisplayCutout — Application windows (layer 2) and closely related layers that must be displaced when the display cutout is hidden.
- Feature: OneHanded — Application and adjacent layers that participate in one-handed mode shrinking.
- Feature: FullscreenMagnification — Layers 0–27, the subset of windowed magnification that participates in fullscreen magnification gestures.
- Feature: ImePlaceholder — Layer 13–14, dedicated container for the IME and its dialogs, allowing the IME to be reparented between displays.
Each DisplayArea in the hierarchy corresponds to a contiguous range of layers. The DisplayAreaGroup assigns each window type’s layer to the appropriate container. When a window is added, DisplayContent.findAreaForWindowType() walks the DisplayArea tree to locate the correct container for the window’s type-derived layer. This ensures that the SurfaceControl hierarchy matches the logical Z-order model.
For a detailed treatment of the DisplayArea hierarchy, feature definitions, and the builder pattern used to construct the tree, see Section 22.
65.11 Cross-References
| Section | Relevance |
|---|---|
| Section 7: WindowManagerService Core Architecture | WMS initialization, addWindow() flow, type validation, and token management |
| Section 22: DisplayArea Hierarchy and Policies | How window types map to DisplayArea containers; DisplayAreaPolicyBuilder feature definitions |
| Section 63: Wallpaper Management | TYPE_WALLPAPER lifecycle, visibility token tracking, wallpaper parallax |
| Section 66: Accessibility Window Management | TYPE_ACCESSIBILITY_OVERLAY and TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY creation and management |
| Section 67: Input Method Window Management | TYPE_INPUT_METHOD and TYPE_INPUT_METHOD_DIALOG lifecycle, IME container reparenting |
65.12 Key Source Files
| File | Path | Relevance |
|---|---|---|
WindowManager.java |
frameworks/base/core/java/android/view/WindowManager.java |
All TYPE_* constants in LayoutParams inner class; the authoritative definition of the type taxonomy (6,735 lines) |
WindowManagerPolicy.java |
frameworks/base/services/core/java/com/android/server/policy/WindowManagerPolicy.java |
getWindowLayerFromTypeLw() implementing the 36-layer model; getSubWindowLayerFromTypeLw() for sublayer offsets (1,258 lines) |
WindowState.java |
frameworks/base/services/core/java/com/android/server/wm/WindowState.java |
Sub-window detection, mIsChildWindow, mLayoutAttached, mBaseLayer calculation, parent-child relationship management (6,200+ lines) |
WindowManagerService.java |
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java |
addWindow() entry point with type validation and permission checks |
DisplayAreaPolicy.java |
frameworks/base/services/core/java/com/android/server/wm/DisplayAreaPolicy.java |
Type-to-DisplayArea mapping; feature definitions for magnification, cutout hiding, one-handed mode |
DisplayAreaPolicyBuilder.java |
frameworks/base/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java |
Builder pattern for constructing DisplayArea hierarchy from layer ranges |
PhoneWindowManager.java |
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java |
Policy enforcement for specific window types; permission checks delegated from WMS |
66. Accessibility Window Management
66.1 Overview
The Android accessibility subsystem maintains a parallel representation of the window hierarchy that is purpose-built for screen readers, magnification controllers, and other assistive technologies. This representation must track every visible window, compute magnification regions, intercept input events for gesture-based navigation, and enforce security boundaries around FLAG_SECURE content – all while keeping latency low enough that users who depend on accessibility services experience the system as responsive.
The architecture is split across two process-level layers. The Window Manager (WM) layer lives inside system_server’s WM package and is responsible for observing raw window state from SurfaceFlinger, computing magnification regions, and packaging window metadata. The Accessibility Service layer also lives in system_server but within the accessibility package, where it maintains its own window model, tracks multiple focus types, manages connections to bound accessibility services, and enforces security policy. Communication between the two layers flows through internal interfaces (WindowManagerInternal.AccessibilityControllerInternal, WindowsForAccessibilityCallback, MagnificationCallbacks) rather than Binder, since both reside in the same process.
66.2 Dual-Layer Architecture
graph TB
subgraph "Window Manager Layer (com.android.server.wm)"
SF["SurfaceFlinger<br/>WindowInfosListener"]
AWP["AccessibilityWindowsPopulator<br/>916 lines"]
AC["AccessibilityController<br/>1,802 lines"]
DM["DisplayMagnifier<br/>(inner class)"]
WFO["WindowsForAccessibilityObserver<br/>(inner class)"]
end
subgraph "Accessibility Service Layer (com.android.server.accessibility)"
AWM["AccessibilityWindowManager<br/>2,349 lines"]
FSMC["FullScreenMagnificationController<br/>2,616 lines"]
MCM["MagnificationConnectionManager<br/>1,384 lines"]
AIF["AccessibilityInputFilter<br/>1,496 lines"]
ASP["AccessibilitySecurityPolicy"]
end
subgraph "External"
SVC["Bound A11y Services<br/>(TalkBack, etc.)"]
SYSUI["SystemUI<br/>(Window Magnifier)"]
end
SF -->|"WindowInfosListener<br/>callbacks"| AWP
AWP -->|"AccessibilityWindow<br/>objects"| AC
AC --> DM
AC --> WFO
WFO -->|"WindowsForAccessibilityCallback"| AWM
DM -->|"MagnificationCallbacks"| FSMC
AWM -->|"AccessibilityEvent"| SVC
FSMC -->|"MagnificationSpec"| AC
MCM -->|"IWindowMagnificationConnection"| SYSUI
AIF -->|"filtered input"| SVC
The WM layer operates under the WM global lock, while the accessibility layer uses its own mLock. The AccessibilityWindowsPopulator bridges the two by receiving SurfaceFlinger callbacks on a dedicated thread and synchronizing via its own lock, avoiding contention with the WM lock for window-change processing.
66.3 AccessibilityController – WM Coordinator
AccessibilityController is the central WM-side class that coordinates all accessibility-related window management. It maintains per-display data structures for both magnification tracking and window observation.
Per-display state tracking:
| Field | Type | Purpose |
|---|---|---|
mDisplayMagnifiers |
SparseArray<DisplayMagnifier> |
Per-display magnification region computation and spec management |
mWindowsForAccessibilityObserver |
SparseArray<WindowsForAccessibilityObserver> |
Per-display window list observers |
mFocusedWindow |
SparseArray<IBinder> |
Input-focused window token per display |
mFocusedDisplay |
int |
Currently focused display ID |
mIsImeVisibleArray |
SparseBooleanArray |
IME visibility state per display |
The controller exposes methods called by WM during window lifecycle events:
onWindowTransition()– called when a window undergoes a visibility transition; routes toDisplayMagnifierfor magnification region updates.onWindowFocusChangedNot()– updatesmFocusedWindowandmFocusedDisplay, triggering accessibility focus recalculation.onDisplayRemoved()– cleans up per-display state from all sparse arrays, preventing resource leaks.performComputeChangedWindowsNot()– delegates toWindowsForAccessibilityObserver.computeChangedWindows().
The controller also bridges to AccessibilityWindowsPopulator, which was introduced to decouple window-list computation from the WM lock by receiving window data directly from SurfaceFlinger’s WindowInfosListener callback.
66.4 Window Change Detection Pipeline
The window change pipeline propagates window state from the compositor through to bound accessibility services.
sequenceDiagram
participant SF as SurfaceFlinger
participant AWP as AccessibilityWindowsPopulator
participant AC as AccessibilityController
participant WFO as WindowsForAccessibilityObserver
participant AWM as AccessibilityWindowManager
participant SVC as Bound A11y Services
SF->>AWP: onWindowInfosChanged()<br/>(WindowInfosListener)
AWP->>AWP: Filter: NOT_VISIBLE, CLONE,<br/>empty touchable, empty frame
AWP->>AWP: Build AccessibilityWindow list<br/>with transform matrices
AWP->>AWP: Apply magnification inverse matrix
Note over AWP: Stabilization timer starts
alt No further SF callbacks within 35ms
AWP->>AC: notifyWindowsChanged()
else SF callbacks keep arriving
Note over AWP: Reset 35ms timer,<br/>force after 450ms max
AWP->>AC: notifyWindowsChanged()
end
AC->>WFO: computeChangedWindows()
WFO->>WFO: Populate window list,<br/>detect structural changes
WFO->>AWM: onWindowsForAccessibilityChanged()<br/>(WindowsForAccessibilityCallback)
AWM->>AWM: Update per-display<br/>DisplayWindowsObserver
AWM->>AWM: Recalculate active/focused<br/>window IDs
AWM->>SVC: AccessibilityEvent<br/>(TYPE_WINDOWS_CHANGED)
Stabilization mechanism. SurfaceFlinger may emit rapid sequences of onWindowInfosChanged() callbacks during animations. The AccessibilityWindowsPopulator uses a dual-timer strategy to avoid flooding the accessibility layer:
- Stable timer (35ms): If no new SurfaceFlinger callback arrives within approximately 2 frames (~35ms at 60Hz), the current window state is considered stable and forwarded.
- Maximum wait (450ms): If callbacks keep arriving (e.g., during a long animation), the populator forces a window update after 450ms. This value is set slightly below UiAutomator’s 500ms idle threshold to ensure accessibility services receive updated state before test frameworks declare the system idle.
Filtering rules applied by AccessibilityWindowsPopulator:
| Filter Condition | Rationale |
|---|---|
NOT_VISIBLE flag set |
Invisible windows are irrelevant to accessibility |
CLONE flag set |
Cloned windows duplicate existing entries |
| Empty touchable region | Non-interactive overlays excluded |
| Empty frame bounds | Windows with no spatial extent excluded |
66.5 Magnification System
Android provides two magnification modes, each with distinct architectural characteristics.
Full-screen vs. window-mode magnification:
| Aspect | Full-Screen Magnification | Window-Mode Magnification |
|---|---|---|
| Controller | FullScreenMagnificationController |
MagnificationConnectionManager |
| WM Integration | DisplayMagnifier applies MagnificationSpec globally |
SysUI-hosted magnifier window |
| Rendering | WM transforms all window surfaces | SysUI captures and magnifies a viewport |
| Activation | Triple-tap or shortcut | Shortcut or tile |
| Scope | Entire display | Floating rectangular region |
| Connection States | N/A (always available) | DISCONNECTED -> CONNECTING -> CONNECTED -> DISCONNECTING |
MagnificationSpec fields:
| Field | Type | Description |
|---|---|---|
scale |
float |
Magnification factor (1.0 = no magnification) |
offsetX |
float |
Horizontal offset of the magnified viewport in pixels |
offsetY |
float |
Vertical offset of the magnified viewport in pixels |
FullScreenMagnificationController manages a DisplayMagnification instance per display. The setScaleAndCenter() method updates the spec and triggers animation through a SpecAnimationBridge that uses ValueAnimator to smoothly interpolate between old and new scale/offset values. The resulting MagnificationSpec is applied to window surfaces via WindowManagerService.applyMagnificationSpecLocked().
IME-following behavior. When mMagnificationFollowTypingEnabled is set, the magnification viewport automatically pans to keep the focused text field and IME visible. onRectangleOnScreenRequested() receives the focused content rectangle and adjusts offsetX/offsetY to center it within the magnified viewport.
Window-mode connection lifecycle. MagnificationConnectionManager manages the IPC connection to SysUI’s magnification window:
enableWindowMagnification()transitions toCONNECTING, binds to SysUI.- SysUI responds with a connection callback, transitioning to
CONNECTED. disableWindowMagnification()transitions toDISCONNECTING, SysUI tears down the magnifier overlay.- Cleanup completes, state returns to
DISCONNECTED.
66.6 DisplayMagnifier – Region Computation
The DisplayMagnifier inner class within AccessibilityController computes the magnifiable region of each display – the area that the magnification system can transform. This computation runs on every window transition, layout change, or display resize.
recomputeBounds() algorithm:
- Initialize
availableBoundsto full screen dimensions (or circular path for round displays). - Initialize
mMagnificationRegionandmImeRegionto empty. - Call
populateWindowsOnScreen()to gather visibleWindowStateobjects. - Iterate windows in reverse Z-order (back to front):
a. Skip excluded window types and private flags.
b. Compute each window’s touchable region, apply its transform matrix.
c. If the window is
TYPE_INPUT_METHOD, union its bounds intomImeRegion. d. Subtract already-accounted-for regions to avoid double-counting. e. If the windowshouldMagnify(), union intomMagnificationRegion. f. Otherwise, union intononMagnifiedBoundsand subtract fromavailableBounds. - Send
MESSAGE_NOTIFY_MAGNIFICATION_REGION_CHANGEDvia handler to the accessibility service layer.
Excluded window types and flags:
| Exclusion | Constant | Reason |
|---|---|---|
| Magnification overlay | TYPE_MAGNIFICATION_OVERLAY |
Self-referential: magnifying the magnifier causes artifacts |
| A11y magnification overlay | TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY |
Same self-referential concern |
| Rounded corners overlay | PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY |
Decorative, not content-bearing |
| Excluded-from-magnification | PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION |
Explicitly opted out by the window owner |
Handler messages dispatched by DisplayMagnifier:
| Message | Trigger | Effect |
|---|---|---|
MESSAGE_NOTIFY_MAGNIFICATION_REGION_CHANGED |
recomputeBounds() completes |
Notifies MagnificationCallbacks of updated region |
MESSAGE_NOTIFY_DISPLAY_SIZE_CHANGED |
Display configuration change | Forces full magnification recalculation |
MESSAGE_NOTIFY_IME_WINDOW_VISIBILITY_CHANGED |
IME shown/hidden | Updates IME tracking for viewport following |
MESSAGE_NOTIFY_IME_REGION_CHANGED |
IME bounds change | Updates IME region for overlap handling |
MESSAGE_NOTIFY_USER_CONTEXT_CHANGED |
Window transition | Signals services that user context shifted |
66.7 AccessibilityWindowManager – Service-Side Window Tracking
AccessibilityWindowManager maintains the accessibility service layer’s authoritative model of the window hierarchy. It receives window lists from the WM layer via WindowsForAccessibilityCallback and translates them into AccessibilityWindowInfo objects that bound services can query.
Focus tracking model:
| Focus Type | Field | Updated By | Semantics |
|---|---|---|---|
| Active window | mActiveWindowId |
TYPE_WINDOW_STATE_CHANGED events |
The window the user is currently interacting with; receives input |
| Top focused window | mTopFocusedWindowId |
WM window list updates | The topmost window with input focus on the focused display |
| A11y focused window | mAccessibilityFocusedWindowId |
WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED |
The window containing the accessibility cursor (e.g., TalkBack’s green rectangle) |
| Top focused display | mTopFocusedDisplayId |
WM callback | The display containing the top focused window |
| A11y focused display | mAccessibilityFocusedDisplayId |
A11y focus movement | The display containing the a11y cursor |
The active window is conceptually distinct from the input-focused window: it tracks the window the user last explicitly interacted with (via touch or TYPE_WINDOW_STATE_CHANGED), while mTopFocusedWindowId reflects WM’s current input routing target. These can diverge when, for example, a notification shade is pulled down but the user’s last interaction was with the underlying activity.
Per-display structure. Each display has a DisplayWindowsObserver that maintains its own window list, handles onWindowsForAccessibilityChanged() callbacks, and generates AccessibilityEvent.TYPE_WINDOWS_CHANGED events with appropriate change types (added, removed, bounds changed, title changed, etc.).
Host-embedded window mapping. For embedded windows (e.g., SurfaceView hosting cross-process content), the manager maintains a host-to-embedded map so that accessibility services can traverse from a host window into its embedded children, providing a unified navigation tree.
66.8 Input Interception – AccessibilityInputFilter
AccessibilityInputFilter extends InputFilter to intercept raw input events before they reach the normal dispatch pipeline. It uses bitwise feature flags to compose multiple accessibility input-processing stages.
Feature flag registry:
| Flag | Value | Component | Function |
|---|---|---|---|
FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP |
0x001 |
FullScreenMagnificationGestureHandler |
Triple-tap to toggle full-screen magnification |
FLAG_FEATURE_TOUCH_EXPLORATION |
0x002 |
TouchExplorer |
Explore-by-touch for screen readers |
FLAG_FEATURE_FILTER_KEY_EVENTS |
0x004 |
Key event filtering | Route key events to a11y services |
FLAG_FEATURE_AUTOCLICK |
0x008 |
AutoclickController |
Auto-click on mouse pointer dwell |
FLAG_FEATURE_INJECT_MOTION_EVENTS |
0x010 |
Motion injection | Allow services to inject touch events |
FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER |
0x020 |
Magnification control | Programmatic magnification without gesture handler |
FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER |
0x040 |
External trigger | Magnification activated by non-gesture interaction |
FLAG_SERVICE_HANDLES_DOUBLE_TAP |
0x080 |
Double-tap dispatch | Route double-tap to service instead of click |
FLAG_REQUEST_MULTI_FINGER_GESTURES |
0x100 |
Multi-finger detection | Enable multi-finger gesture recognition |
FLAG_REQUEST_2_FINGER_PASSTHROUGH |
0x200 |
Two-finger passthrough | Pass two-finger gestures through to app |
FLAG_SEND_MOTION_EVENTS |
0x400 |
Motion event dispatch | Include raw motion events in gesture callbacks |
FLAG_FEATURE_INTERCEPT_GENERIC_MOTION_EVENTS |
0x800 |
Generic motion interception | Intercept trackball, joystick events |
FLAG_FEATURE_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP |
0x1000 |
Two-finger triple-tap handler | Alternative magnification activation gesture |
FLAG_FEATURE_MOUSE_KEYS |
0x2000 |
Mouse keys | Keyboard-driven pointer control |
Flags are combined via bitwise OR and set through setUserAndEnabledFeatures(). The filter instantiates only the components corresponding to enabled flags. When FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP is set, FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER is ignored because the gesture handler is a superset. Event processing pipelines are chained per display using EventStreamTransformation, where each enabled component passes unhandled events to the next in the chain.
66.9 Security Model
The accessibility subsystem enforces security boundaries to prevent unauthorized access to sensitive window content.
flowchart TD
REQ["A11y Service requests<br/>window content / screenshot"]
CHK1{"Service has<br/>CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT?"}
CHK2{"Window has<br/>FLAG_SECURE?"}
CHK3{"Service can capture<br/>secure layers?"}
ALLOW["Content provided"]
REDACT["Content redacted or<br/>error returned"]
BLOCK["Request blocked:<br/>ERROR_TAKE_SCREENSHOT_SECURE_WINDOW"]
REQ --> CHK1
CHK1 -->|No| BLOCK
CHK1 -->|Yes| CHK2
CHK2 -->|No| ALLOW
CHK2 -->|Yes| CHK3
CHK3 -->|Yes| ALLOW
CHK3 -->|No| REDACT
Key security enforcement points:
AccessibilitySecurityPolicychecksCAPABILITY_CAN_RETRIEVE_WINDOW_CONTENTbefore any content query. Only services declared with this capability (and granted by the user) can access window trees.FLAG_SECUREblocking: InAbstractAccessibilityServiceConnection.provideWindowSurfaceInfo(), if a window hasFLAG_SECUREand the service lackscanCaptureSecureLayers()permission, screenshot requests are rejected withERROR_TAKE_SCREENSHOT_SECURE_WINDOW.RETRIEVE_WINDOW_CONTENTpermission (android.permission.RETRIEVE_WINDOW_CONTENT) is enforced at theAccessibilityManagerServiceAPI boundary forfindAccessibilityNodeInfosByViewId()and related tree-query methods.- Content redaction: When a service has general content access but not secure-layer capture, the
SECURE_CONTENT_POLICY_REDACTpolicy causes secure surfaces to be blacked out in screenshots rather than blocking the entire request.
66.10 Multi-Display Accessibility
All core accessibility data structures are indexed by display ID using SparseArray. This design enables independent tracking per display:
AccessibilityController:mDisplayMagnifiers,mFocusedWindow, andmIsImeVisibleArrayare all keyed by display ID. Each display can have independent magnification state.AccessibilityWindowManager: Each display has its ownDisplayWindowsObservermaintaining a separate window list, enabling independentTYPE_WINDOWS_CHANGEDevent streams.AccessibilityInputFilter: Feature flags and gesture handlers are managed per display viaenableFeaturesForDisplay()/disableFeaturesForDisplay(), allowing different input processing on different displays.- Focus arbitration:
mTopFocusedDisplayIdidentifies the display with the global input focus.mLastNonProxyTopFocusedDisplayIdseparately tracks the last non-proxy display that held focus, handling virtual display proxying scenarios. - Display removal:
onDisplayRemoved()propagates through all layers, cleaning up sparse-array entries and tearing down per-display observers.
66.11 Cross-References
| Section | Relationship |
|---|---|
| Section 65 – Window Types | TYPE_ACCESSIBILITY_OVERLAY, TYPE_MAGNIFICATION_OVERLAY, and TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY are excluded from magnification region computation |
| Section 67 – IME | DisplayMagnifier separately tracks IME region (mImeRegion); magnification viewport auto-follows IME when mMagnificationFollowTypingEnabled is set |
| Section 22 – DisplayArea | Per-display DisplayMagnifier instances align with the DisplayArea hierarchy; magnification spec is applied at the display level |
| Section 64 – Color Management | Color inversion and high-contrast modes interact with accessibility but are handled through separate display transform pipelines |
66.12 Key Source Files
| File | Path | Lines | Role |
|---|---|---|---|
AccessibilityController.java |
frameworks/base/services/core/java/com/android/server/wm/ |
~1,802 | WM-side coordinator; contains DisplayMagnifier and WindowsForAccessibilityObserver inner classes |
AccessibilityWindowsPopulator.java |
frameworks/base/services/core/java/com/android/server/wm/ |
916 | SurfaceFlinger bridge; WindowInfosListener implementation; stabilization timers |
AccessibilityWindowManager.java |
frameworks/base/services/accessibility/java/com/android/server/accessibility/ |
~2,349 | Service-side window model; focus tracking; host-embedded mapping |
FullScreenMagnificationController.java |
frameworks/base/services/accessibility/java/com/android/server/accessibility/magnification/ |
~2,616 | Full-screen magnification with per-display DisplayMagnification state; animation bridge |
MagnificationConnectionManager.java |
frameworks/base/services/accessibility/java/com/android/server/accessibility/magnification/ |
~1,384 | Window-mode magnification; SysUI connection lifecycle |
AccessibilityInputFilter.java |
frameworks/base/services/accessibility/java/com/android/server/accessibility/ |
~1,496 | Input interception; feature-flag-driven composition of touch exploration, magnification gestures, autoclick |
AccessibilitySecurityPolicy.java |
frameworks/base/services/accessibility/java/com/android/server/accessibility/ |
– | Security enforcement; capability and permission checks |
AbstractAccessibilityServiceConnection.java |
frameworks/base/services/accessibility/java/com/android/server/accessibility/ |
– | Base class for service connections; FLAG_SECURE enforcement on screenshots |
67. IME Window Management
67.1 Overview
The Input Method Editor (IME) window is one of the most complex windows in the Android display system. Unlike ordinary application windows that attach to a single task and follow straightforward Z-ordering rules, the IME window floats across application boundaries, must track focus changes between apps, animate independently of both the hosting display and the target application, and handle edge cases introduced by split-screen, multi-display, and rotation transitions.
Android addresses this complexity through a three-target architecture that decouples the three
concerns an IME window must satisfy: where it appears in the Z-order, which window receives the
characters it produces, and which window controls its show/hide animations. These three targets
can point to the same window in simple cases, but diverge in split-screen, picture-in-picture, and
multi-display scenarios. The ImeInsetsSourceProvider manages visibility and insets state through
a well-defined state machine, while DisplayContent orchestrates target computation, parent
assignment, and container placement. InputMethodManagerService (IMMS) governs the IME
lifecycle at the system-service level, binding to IME processes, dispatching show/hide requests,
and coordinating with the window manager through ImeInsetsSourceProvider.
The core source files span approximately 15,000 lines of tightly coupled logic across
InputMethodManagerService.java, ImeInsetsSourceProvider.java, DisplayContent.java, and
WindowState.java.
67.2 Three-Target Architecture
The foundational design pattern for IME window management is the separation of concerns into
three independent targets, each stored as a field on DisplayContent:
| Target | Field | Purpose |
|---|---|---|
| Layering Target | mImeLayeringTarget |
Determines the Z-order position of the IME window. The IME is placed directly above this window in the window hierarchy. |
| Input Target | mImeInputTarget |
Determines which window receives dispatched input from the IME. Characters typed on the soft keyboard are routed to this window. |
| Control Target | mImeControlTarget |
Determines which window (or remote target) controls IME show/hide animations via the insets animation API. |
In the common single-app-fullscreen case, all three targets point to the same WindowState
representing the focused activity’s main window. The architecture reveals its value in more
complex configurations.
graph TD
IME["IME Window<br>TYPE_INPUT_METHOD"]
subgraph Targets
LT["mImeLayeringTarget<br>Z-order anchor"]
IT["mImeInputTarget<br>Input dispatch destination"]
CT["mImeControlTarget<br>Animation controller"]
end
IME -->|"placed above"| LT
IME -->|"characters routed to"| IT
IME -->|"animated by"| CT
subgraph Example: Split Screen
TOP["Top App Window"]
BOT["Bottom App Window"]
end
LT -->|"e.g."| BOT
IT -->|"e.g."| BOT
CT -->|"RemoteInsetsControlTarget<br>owned by shell"| SHELL["SystemUI / Shell"]
Why targets diverge. In split-screen mode, the IME must layer on top of the focused split
side, input goes to the focused app, but animation control is handed to a RemoteInsetsControlTarget
owned by the shell so that the divider and both apps can coordinate the resize animation. During
task transitions, the layering target may temporarily point to a snapshot window while the input
target has already moved to the incoming activity.
67.3 ImeInsetsSourceProvider — Visibility and Insets
ImeInsetsSourceProvider (688 lines) extends InsetsSourceProvider to manage the IME-specific
insets lifecycle. It tracks server-side visibility, coordinates with layout passes, and gates
control dispatch on readiness conditions.
Key fields:
mServerVisible— whether the server considers the IME visible (set by IMMS).mImeShowing— whether the IME is actually rendering content.mGivenInsetsReady— whether the IME process has reported its inset dimensions.mFrozen— blockssetServerVisible()calls during transitions to prevent flickering.mStatsToken— token for latency tracking of show/hide operations.
Readiness gate. The method isLeashReadyForDispatching() returns true only when all three
conditions hold: isServerVisible() is true, the IME surface is drawn, and mGivenInsetsReady
is true. Until all three are satisfied, the control leash is not dispatched to the client, which
prevents the client from starting its animation before the IME surface has actual content.
onPostLayout() is invoked after each layout pass. It dispatches the animation leash to the
insets control target once readiness conditions are met and updates the IME visibility state.
setFrozen(boolean frozen) is called during display transitions (rotation, task switch) to
block parent-class setServerVisible() calls. This prevents the IME from flickering to a hidden
state and back during the transition.
onImeInputTargetChanged() handles the case where the input target switches while the IME
is visible, coordinating leash handoff between the old and new control targets.
State machine:
stateDiagram-v2
[*] --> SHOW_REQUEST: showSoftInput called
SHOW_REQUEST --> PRE_LAYOUT: IMMS binds and requests show
PRE_LAYOUT --> POST_LAYOUT: WM layout pass completes
POST_LAYOUT --> CONTROL_DISPATCH: isLeashReadyForDispatching true
CONTROL_DISPATCH --> CLIENT_ANIMATION: Leash dispatched to control target
CLIENT_ANIMATION --> VISIBLE: Animation completes
VISIBLE --> HIDE: hideSoftInput called
HIDE --> [*]: IME surface removed or hidden
VISIBLE --> FROZEN: Transition begins
FROZEN --> VISIBLE: Transition ends and setFrozen false
FROZEN --> HIDE: Target lost during transition
The FROZEN state is critical for rotation and task-switch correctness. While frozen, the
provider ignores setServerVisible(false) calls that would otherwise cause a visible flicker
as the system temporarily loses and re-establishes the IME target.
67.4 IME Target Computation
Target computation is driven by DisplayContent and triggered whenever focus changes, a window
is added or removed, or a transition alters the window hierarchy.
computeImeLayeringTarget() walks the window list using mComputeImeLayeringTargetPredicate,
which delegates to WindowState.canBeImeLayeringTarget(). The predicate rejects:
- IME windows themselves (TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG).
- Windows in pinned (picture-in-picture) mode.
- Screenshot windows used for transition animations.
- Windows belonging to non-focusable apps or tasks.
- Activities involved in transient-launch transitions.
- Windows where
FLAG_NOT_FOCUSABLEandFLAG_ALT_FOCUSABLE_IMare inconsistent — having exactly one of these flags set means the window cannot be an IME target.
The FLAG_NOT_FOCUSABLE and FLAG_ALT_FOCUSABLE_IM interaction follows XNOR logic: a window
is eligible for IME layering only if both flags share the same value — either both are set
or both are cleared (the normal case for a focusable window that wants IME). Having exactly
one flag set makes the window ineligible.
flowchart TD
START["computeImeLayeringTarget"] --> WALK["Walk window list top to bottom"]
WALK --> PRED["Apply canBeImeLayeringTarget"]
PRED --> C1{"Is IME window type?"}
C1 -->|Yes| REJECT["Reject"]
C1 -->|No| C2{"Is pinned mode?"}
C2 -->|Yes| REJECT
C2 -->|No| C3{"Is screenshot window?"}
C3 -->|Yes| REJECT
C3 -->|No| C4{"App or task non-focusable?"}
C4 -->|Yes| REJECT
C4 -->|No| C5{"Transient-launch activity?"}
C5 -->|Yes| REJECT
C5 -->|No| C6{"FLAG_NOT_FOCUSABLE XNOR<br>FLAG_ALT_FOCUSABLE_IM valid?"}
C6 -->|No| REJECT
C6 -->|Yes| ACCEPT["Accept as layering target"]
ACCEPT --> SET["setImeLayeringTarget"]
SET --> SCREENSHOT{"Task transition active?"}
SCREENSHOT -->|Yes| SNAP["showImeScreenshot to preserve perception"]
SCREENSHOT -->|No| REPARENT["Reparent ImeContainer above target"]
setImeLayeringTarget() performs two additional actions beyond storing the reference: if a
task transition is in progress, it calls showImeScreenshot() to capture the current IME frame
and display it as a snapshot so the user does not perceive a gap; it then reparents the
ImeContainer so the IME surface sits above the new target in the Z-order.
67.5 IME Control and Parent Assignment
Control target computation. updateImeControlTarget() calls computeImeControlTarget(),
which applies the following logic:
- If the display uses
DISPLAY_IME_POLICY_FALLBACK_DISPLAYor is a virtual display without local IME, return aRemoteInsetsControlTargetso the default display’s shell controls animations. - If the display is in split-screen mode, return a
RemoteInsetsControlTargetowned by the shell, because the shell must coordinate the resize of both split sides when the IME appears or disappears. - Otherwise, return the
mImeInputTarget’s window state as the control target, giving the focused app direct control over IME animations.
The RemoteInsetsControlTarget is an IPC wrapper that forwards insets control operations to
a remote process (typically SystemUI or the shell). This allows the IME animation to be
orchestrated by a component that has visibility into the entire display layout, not just the
single app that happens to have focus.
Parent assignment. updateImeParent() calls computeImeParent() to determine where in the
surface hierarchy the IME container should be attached:
- App parent: When a single app is focused and the IME control target is that app, the IME container is attached to the app’s parent surface. This ensures the IME participates in the app’s surface transactions and moves with it during animations.
- Display root: When the control target is a
RemoteInsetsControlTarget(split-screen, multi-display fallback), the IME container is attached to the display’s root surface so it floats above all apps.
The parent assignment directly affects how transitions interact with the IME. When attached to an app, a task-level transition animation will carry the IME along. When attached to the display root, the IME remains stationary during task transitions and must be managed separately.
67.6 ImeContainer — DisplayArea Integration
The ImeContainer class (defined within DisplayContent, lines 5398–5491) is a
DisplayArea.Tokens subclass tagged with FEATURE_IME and positioned at ABOVE_TASKS in the
display area hierarchy. It serves as the container for the IME window and any IME dialog windows.
Key design decisions:
- Orientation decoupling.
ImeContaineroverridesgetOrientation()to always returnSCREEN_ORIENTATION_UNSET. The IME never drives display orientation — it adapts to whatever orientation the current app has established. - Conditional layer assignment. The
mNeedsLayerflag controls whether the container requests a layer from SurfaceFlinger. When the IME is not active, the container avoids consuming a layer slot. This is a resource optimization for displays where no IME is shown. - Reparenting. The container supports dynamic reparenting via
updateImeParent(). Unlike most display areas that have a fixed position in the hierarchy,ImeContainermoves between app surfaces and the display root depending on the current control target.
67.7 SOFT_INPUT Flags
Applications declare their preferred IME behavior through SOFT_INPUT flags set on
WindowManager.LayoutParams.softInputMode. These flags are divided into two groups: state
flags that control initial visibility, and adjustment flags that control how the window resizes.
State flags:
| Flag | Value | Behavior |
|---|---|---|
SOFT_INPUT_STATE_UNSPECIFIED |
0 | System decides based on context. Typically hides IME on new window focus. |
SOFT_INPUT_STATE_UNCHANGED |
1 | Preserves whatever IME state was active before this window gained focus. |
SOFT_INPUT_STATE_HIDDEN |
2 | Hides IME when user navigates forward to this window. |
SOFT_INPUT_STATE_ALWAYS_HIDDEN |
3 | Hides IME whenever this window has focus, regardless of navigation direction. |
SOFT_INPUT_STATE_VISIBLE |
4 | Shows IME when user navigates forward to this window. |
SOFT_INPUT_STATE_ALWAYS_VISIBLE |
5 | Shows IME whenever this window gains focus, regardless of navigation direction. |
Adjustment flags:
| Flag | Behavior |
|---|---|
SOFT_INPUT_ADJUST_RESIZE |
The window’s content area is resized to make room for the IME. The window receives a new layout with reduced available height. Deprecated in API 30 in favor of insets-based handling. |
SOFT_INPUT_ADJUST_PAN |
The window is panned upward so the focused input field remains visible. The window itself is not resized. |
SOFT_INPUT_ADJUST_NOTHING |
The window makes no adjustment. The IME may cover content. |
The distinction between HIDDEN and ALWAYS_HIDDEN (and correspondingly VISIBLE and
ALWAYS_VISIBLE) lies in navigation direction sensitivity. The non-ALWAYS variants only
trigger when the user navigates forward (e.g., starting a new activity), while the ALWAYS
variants apply regardless of navigation direction, including back navigation.
67.8 Multi-Display IME Routing
Android supports multiple displays, each with its own window hierarchy. IME routing on secondary displays is governed by the display’s IME policy.
| Policy | Constant | Behavior |
|---|---|---|
| Local | DISPLAY_IME_POLICY_LOCAL |
The IME window is hosted on this display. An ImeContainer exists in the display’s hierarchy and the IME renders locally. |
| Fallback to default | DISPLAY_IME_POLICY_FALLBACK_DISPLAY |
The IME window is hosted on the default display (display 0). Input is routed from the secondary display to the IME on the default display. |
When DISPLAY_IME_POLICY_FALLBACK_DISPLAY is active, computeImeControlTarget() returns a
RemoteInsetsControlTarget that bridges the secondary display’s insets state to the default
display where the IME actually renders. The input target on the secondary display still receives
keystrokes, but the IME surface itself is only visible on the default display.
This policy exists because not all displays can host arbitrary windows. Virtual displays
created by DisplayManager for casting or virtual desktops often lack the surface capabilities
or security context to host an IME safely. Routing to the default display ensures the IME
always renders on a trusted, fully capable display.
The mImeInputTarget on the secondary display and the actual IME window on the default display
communicate through the InputMethodManagerService, which manages the cross-display binding.
From the user’s perspective, they type on the secondary display’s app and the IME appears on the
primary display (or is hidden entirely if the secondary display is a projection).
67.9 IME and Rotation
Display rotation introduces a particularly challenging interaction with the IME because the IME window must maintain visual continuity while the display coordinate system changes.
Frozen state. When a rotation begins, ImeInsetsSourceProvider.setFrozen(true) is called.
This blocks all setServerVisible() calls from propagating to the parent class. Without this
freeze, the rotation sequence would briefly clear the IME target (as windows are re-laid-out),
causing setServerVisible(false) followed by setServerVisible(true), resulting in a visible
flicker.
Fixed rotation transform. During a fixed-rotation transition (where one app displays in a
different orientation than the display), linkFixedRotationTransform() is called on the IME
window to apply the same rotation matrix as the target app. This allows the IME to appear in
the app’s orientation even though the display has not yet rotated.
IME screenshot. The AsyncRotationController calls hideImeImmediately() at the start of
a rotation animation. Before hiding, the system captures the current IME frame via
showImeScreenshot() and displays it as a static snapshot. This snapshot is transformed along
with the rotating display content, maintaining the illusion of continuity. Once the rotation
completes and the IME is re-laid-out in the new orientation, the snapshot is removed and the
live IME surface is shown.
The combination of frozen state, fixed rotation transforms, and screenshots ensures that the user never sees the IME disappear and reappear during rotation, even though the underlying window is being destroyed and recreated in the new orientation.
67.10 IME Window Lifecycle
The following sequence illustrates the complete lifecycle of an IME show/hide cycle triggered by an application gaining focus on an input field.
sequenceDiagram
participant App as Application
participant WMS as WindowManagerService
participant DC as DisplayContent
participant IMMS as InputMethodManagerService
participant Provider as ImeInsetsSourceProvider
participant IME as IME Process
App->>WMS: Window gains focus with EditText
WMS->>DC: updateFocusedWindowLocked
DC->>DC: computeImeLayeringTarget
DC->>DC: computeImeControlTarget
DC->>DC: updateImeParent
App->>IMMS: showSoftInput
IMMS->>IME: bind and request show
IME->>WMS: addWindow TYPE_INPUT_METHOD
WMS->>DC: setInputMethodWindowLocked
DC->>DC: Recompute all IME targets
IME->>WMS: Report insets dimensions
WMS->>Provider: mGivenInsetsReady = true
WMS->>Provider: onPostLayout
Provider->>Provider: isLeashReadyForDispatching check
Provider->>App: Dispatch control leash
App->>App: Run IME show animation
Note over App,IME: User types on soft keyboard
IME->>IMMS: Key events
IMMS->>App: Dispatch to mImeInputTarget
App->>IMMS: hideSoftInput
IMMS->>IME: Request hide
Provider->>App: Dispatch hide animation
App->>App: Run IME hide animation
IME->>WMS: removeWindow
The sequence highlights several important ordering constraints:
- Target computation occurs before the IME window is added, based on the focused app window.
- The control leash is not dispatched until three conditions are met: server visibility, drawn state, and given insets readiness.
- Input dispatch flows through
mImeInputTarget, which may differ from the layering or control targets. - The hide sequence mirrors the show sequence, with the control target running the hide animation before the IME window is actually removed.
67.11 Cross-References
- Section 23 — Insets System.
ImeInsetsSourceProviderextends the general insets framework described in Section 23. The IME is one of several insets sources (along with status bar and navigation bar), but has unique readiness gating and frozen-state logic. - Section 58 — Display Rotation. The frozen state, fixed rotation transforms, and IME screenshot mechanisms described in Section 67.9 interact directly with the rotation state machine from Section 58.
- Section 65 — Window Types.
TYPE_INPUT_METHODandTYPE_INPUT_METHOD_DIALOGare system window types described in Section 65. Their Z-order base layer and special handling incanBeImeLayeringTarget()are IME-specific extensions of the type system. - Section 15 — Window Transitions. IME screenshots during task transitions, the interaction
between
setImeLayeringTarget()and transition animations, and the frozen-state blocking of visibility changes during transitions all connect to the transition framework in Section 15.
Part XIV: Shell Feature Implementations
68. Picture-in-Picture (PIP) Architecture
68.1 Overview
Picture-in-Picture (PIP) allows an activity to continue rendering in a small, floating overlay window while the user navigates elsewhere. Android’s PIP subsystem lives entirely inside the WM Shell process and is split along two axes:
| Axis | Variant A | Variant B |
|---|---|---|
| Form-factor | Phone (pip/phone/) — touch-driven, draggable |
TV (pip/tv/) — D-pad-driven, gravity-based |
| Generation | v1 (pip/) — PipTaskOrganizer-centric |
v2 (pip2/) — PipScheduler + handler-based |
The v1 stack (43 files, ≈ 18 400 lines) uses a monolithic PipTaskOrganizer that
merges task-listener callbacks with animation scheduling. The v2 rewrite
(33 files, ≈ 11 600 lines) decomposes responsibility into a dedicated
PipTransitionState state-machine, a PipScheduler for transition initiation,
a PipTaskListener for the narrow set of task-organizer callbacks, and four
specialised animator classes.
graph TD
subgraph "PIP v1 Architecture (Phone)"
PC1["PipController<br/>(phone/)"] --> PTO["PipTaskOrganizer"]
PC1 --> PTH["PipTouchHandler"]
PTO -->|"ShellTaskOrganizer<br/>TaskListener"| STO["ShellTaskOrganizer"]
PTO --> PAC["PipAnimationController"]
PTO --> PT1["PipTransition"]
PT1 -->|"startAnimation()"| TR["Shell Transitions"]
PAC --> PSTH["PipSurfaceTransactionHelper"]
PTH --> PMH["PipMotionHelper"]
PTH --> PDT["PipDismissTargetHandler"]
PTH --> PRG["PipResizeGestureHandler"]
PC1 --> PMC1["PhonePipMenuController"]
end
subgraph "PIP TV Architecture"
TVC["TvPipController"] --> TVT["TvPipTransition"]
TVC --> TVMC["TvPipMenuController"]
TVC --> TVBA["TvPipBoundsAlgorithm"]
TVMC -->|"D-pad events"| TVC
TVT -->|"startAnimation()"| TR
end
subgraph "PIP v2 Architecture (Phone)"
PC2["PipController<br/>(pip2/phone/)"] --> PS["PipScheduler"]
PC2 --> PTH2["PipTouchHandler"]
PS --> PT2["PipTransition<br/>(pip2/)"]
PT2 --> CPH["ContentPipHandler"]
PT2 --> PEH["PipExpandHandler"]
PT2 --> PDCO["PipDisplayChangeObserver"]
PTL["PipTaskListener"] -->|"onTaskInfoChanged"| STO
PT2 -->|"startAnimation()"| TR
PTS["PipTransitionState<br/>(pip2/)"] -.->|"listener"| PS
PTS -.->|"listener"| PT2
PTS -.->|"listener"| PTL
end
68.2 PipTaskOrganizer — Task Lifecycle Management (v1)
PipTaskOrganizer (line 121, 1 961 lines) implements ShellTaskOrganizer.TaskListener
and DisplayController.OnDisplaysChangedListener. It is the single owner of the
PIP task’s SurfaceControl leash and WindowContainerToken.
68.2.1 TaskOrganizer Protocol Callbacks
| Callback | Line | Behaviour |
|---|---|---|
onTaskAppeared() |
801 | Stores mTaskInfo, mToken, mLeash; calculates entry bounds via PipBoundsAlgorithm; triggers enter animation |
onTaskInfoChanged() |
983 | Applies new PictureInPictureParams (aspect ratio, actions); defers changes during entry animation |
onTaskVanished() |
954 | Cancels animations; calls onExitPipFinished() for cleanup; nulls leash and token |
68.2.2 Enter and Exit Flows
Enter PIP follows two animation paths selected by PipTransitionController.ANIM_TYPE_*:
- Alpha entry (
enterPipWithAlphaAnimation, line 872) — sets leash alpha to 0, applies crop/position/corner-radius via aboundsChangeTransaction, then fades alpha 0 → 1 inTRANSITION_DIRECTION_TO_PIP. - Bounds entry (
animateResizePip, line 1 675) — creates aPipTransitionAnimator<Rect>that interpolates the window from its original bounds to the calculated PIP rectangle. An app-iconPipContentOverlayis attached when no source-rect hint is available.
Exit PIP (exitPip, line 612) scales the leash from PIP bounds to the
destination fullscreen bounds, sets windowing mode to FULLSCREEN via a
WindowContainerTransaction, and optionally reparents into split-screen
(TRANSIT_EXIT_PIP_TO_SPLIT). onExitPipFinished() (line 1 063) clears
the leash, token, and shadow radius.
68.2.3 Surface Transaction Coordination
All visual mutations flow through PipSurfaceTransactionHelper (411 lines):
| Helper method | Purpose |
|---|---|
cropAndPosition() |
Sets window crop and layer position on a SurfaceControl.Transaction |
scale() |
Applies a scale matrix for exit animations |
round() |
Applies rounded corner radius when in PIP |
Atomic updates are ensured by attaching a boundsChangeTransaction to the
WindowContainerTransaction via wct.setBoundsChangeTransaction(mToken, tx)
(line 915), so the SurfaceFlinger frame and WM state update land together.
68.3 PipTransition — Shell Transitions Integration (v1)
PipTransition (line 94, 1 466 lines) extends PipTransitionController and
serves as the Shell Transitions handler for all PIP-related window transitions.
68.3.1 State Machine
PipTransitionState (line 34, 165 lines) defines five states tracked in an @IntDef:
stateDiagram-v2
[*] --> UNDEFINED
UNDEFINED --> TASK_APPEARED : onTaskAppeared()
TASK_APPEARED --> ENTRY_SCHEDULED : applyEnterPipSyncTransaction()
ENTRY_SCHEDULED --> ENTERING_PIP : animation starts
ENTERING_PIP --> ENTERED_PIP : onFinishResize()
ENTERED_PIP --> EXITING_PIP : exitPip() / TRANSIT_TO_BACK
EXITING_PIP --> UNDEFINED : onExitPipFinished()
ENTERING_PIP --> EXITING_PIP : early exit during enter
ENTERED_PIP --> ENTERED_PIP : resize / move
note right of ENTERED_PIP
shouldBlockResizeRequest()
returns true unless
state ≥ ENTERING_PIP
and state ≠ EXITING_PIP
end note
| Constant | Value | Line |
|---|---|---|
UNDEFINED |
0 | 36 |
TASK_APPEARED |
1 | 37 |
ENTRY_SCHEDULED |
2 | 38 |
ENTERING_PIP |
3 | 39 |
ENTERED_PIP |
4 | 40 |
EXITING_PIP |
5 | 41 |
68.3.2 Transitions Framework Hooks
PipTransition implements three key Shell Transitions methods:
handleRequest()(line 390) — interceptsTRANSIT_PIPandTRANSIT_TO_BACKrequests, sets state toEXITING_PIPwhen the current PIP task moves to back.startAnimation()(line 217) — the main dispatcher; routes tostartEnterAnimation()(line 1 029),startExitAnimation()(line 702),removePipImmediately()(line 950), orcleanupPipExitTransition()(line 969) based on transition type and tracked IBinder tokens (mExitTransition,mMoveToBackTransition,mRequestedEnterTransition).mergeAnimation()(line 375) — callsend()to cancel the current animator, allowing a superseding transition to take over.
68.3.3 Fixed-Rotation Handling
PIP must cope with display rotation that occurs independently of the PIP activity’s
orientation. PipTransition tracks a @FixedRotationState (lines 99–108):
| State | Value | Meaning |
|---|---|---|
FIXED_ROTATION_UNDEFINED |
0 | No rotation pending |
FIXED_ROTATION_CALLBACK |
1 | Rotation via swipe-to-home callback |
FIXED_ROTATION_TRANSITION |
2 | Rotation detected in incoming transition |
When fixed rotation is active, onFixedRotationStarted() (line 654) fades PIP out;
onFixedRotationFinished() (line 662) restores visibility.
68.4 PipAnimationController — Animation Types and Physics
PipAnimationController (line 59, 868 lines) is the animation factory for v1 PIP.
68.4.1 Transition Direction Constants
| Constant | Value | Line | Usage |
|---|---|---|---|
TRANSITION_DIRECTION_NONE |
0 | 71 | No-op |
TRANSITION_DIRECTION_SAME |
1 | 72 | Resize within PIP |
TRANSITION_DIRECTION_TO_PIP |
2 | 73 | Enter PIP |
TRANSITION_DIRECTION_LEAVE_PIP |
3 | 74 | Exit to fullscreen |
TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN |
4 | 75 | Exit to split |
TRANSITION_DIRECTION_REMOVE_STACK |
5 | 76 | Remove PIP task |
TRANSITION_DIRECTION_SNAP_AFTER_RESIZE |
6 | 77 | Snap edge post-resize |
TRANSITION_DIRECTION_USER_RESIZE |
7 | 78 | User-initiated resize |
TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND |
8 | 79 | Menu expand toggle |
68.4.2 Animator Hierarchy
PipTransitionAnimator<T> (line 303) extends ValueAnimator and implements both
AnimatorUpdateListener and AnimatorListener. Two anonymous concrete subclasses
are created via factory methods:
ofAlpha()(line 553) — interpolates aFloatalpha value; appliestx.setAlpha(leash, alpha)each frame.ofBounds()(line 593) — interpolatesRectbounds using aRectEvaluator(line 705); computes source-rect insets with a secondmInsetsEvaluator(line 706); supports rotation deltas (90° increments, lines 778–798).
Both paths are wired through setupPipTransitionAnimator() (line 200) which sets
Interpolators.FAST_OUT_SLOW_IN and float values [0f, 1f].
68.4.3 Transaction Handler
PipTransactionHandler (line 285) allows external consumers (e.g. the menu
controller) to intercept per-frame SurfaceControl.Transaction applications.
If handlePipTransaction() returns true, the animator skips its own
tx.apply(). This enables the menu to synchronise its own surface updates
with the PIP animation.
68.5 Touch Handling — PipTouchHandler, Drag, Resize, Fling, Snap
PipTouchHandler (line 74, 1 091 lines) is the central touch coordinator for
phone PIP. It receives InputEvents via PipInputConsumer and dispatches
to a chain of sub-handlers.
68.5.1 Gesture Dispatch
sequenceDiagram
participant IC as PipInputConsumer
participant TH as PipTouchHandler
participant DG as DefaultPipTouchGesture
participant MH as PipMotionHelper
participant DT as PipDismissTargetHandler
participant RG as PipResizeGestureHandler
IC->>TH: handleTouchEvent(InputEvent)
alt Resize detected
TH->>RG: willStartResizeGesture(ev)
RG-->>TH: true
TH->>RG: onMotionEvent(ev)
else Dismiss target
TH->>DT: maybeConsumeMotionEvent(ev)
DT-->>TH: true (consumed)
else Normal gesture
TH->>DG: onDown / onMove / onUp
alt Dragging
DG->>MH: movePip(bounds, isDragging=true)
else Fling (ACTION_UP)
alt High X-velocity (>18000)
DG->>MH: stashToEdge(velX, velY)
else Normal fling
DG->>MH: flingToSnapTarget(velX, velY)
end
else Double-tap
DG->>MH: expandLeavePip()
else Single-tap
DG->>TH: showMenu()
end
end
68.5.2 DefaultPipTouchGesture (line 790)
The inner class tracks three fields: mStartPosition (Point), mDelta (PointF),
and mShouldHideMenuAfterFling.
onDown()(line 809) — records start position, detects dismiss zone.onMove()(line 836) — accumulates delta, callsmMotionHelper.movePip().onUp()(line 875) — reads velocity fromPipTouchState; branches into stash, fling-snap, double-tap expand, or single-tap menu.
68.5.3 Stash-to-Edge Logic
shouldStash() (line 986) triggers when either:
- Fling velocity exceeds
DEFAULT_STASH_VELOCITY_THRESHOLD(18 000 px/s, line 77), or - The PIP window is released with its edge past the display boundary.
Stashing is suppressed on edges that have a display cutout (lines 997–1 007).
68.5.4 PipMotionHelper Physics
PipMotionHelper (line 62, 741 lines) uses PhysicsAnimator with a friction of
DEFAULT_FRICTION = 1.9f (line 74) for fling dynamics. Three spring
configurations govern different motion styles:
| Config | Stiffness | Damping | Line | Usage |
|---|---|---|---|---|
| Fling X/Y | friction 1.9 | — | 584 | Snap-to-edge after fling |
| Dismiss spring | STIFFNESS_MEDIUM |
DAMPING_RATIO_NO_BOUNCY |
119 | Magnetic pull into dismiss target |
| Catch-up spring | 5 000 | DAMPING_RATIO_NO_BOUNCY |
124 | Catch-up to finger after leaving dismiss zone |
Key durations: SHRINK_STACK_FROM_MENU_DURATION = 250 ms (line 67),
LEAVE_PIP_DURATION = 300 ms (line 70), UNSTASH_DURATION = 250 ms (line 69).
68.6 TV PIP — TvPipController and Remote Navigation
TV PIP replaces touch input with D-pad remote control and uses gravity-based positioning instead of free-form drag.
68.6.1 TvPipController State Model
TvPipController (line 75, 811 lines) defines three states:
| State | Value | Description |
|---|---|---|
STATE_NO_PIP |
0 | No PIP window |
STATE_PIP |
1 | PIP at gravity-based position (e.g. bottom-right) |
STATE_PIP_MENU |
2 | PIP centred with menu overlay |
The controller implements TvPipMenuController.Delegate, PipTransitionController.PipTransitionCallback,
TvPipBoundsController.PipBoundsListener, and DisplayController.OnDisplaysChangedListener.
68.6.2 Remote Control Navigation
D-pad events flow from TvPipMenuView → TvPipMenuController.onPipMovement() (line 611)
→ TvPipController.movePip(keycode) (line 408) → TvPipBoundsAlgorithm.updateGravity(keycode)
(line 246). The algorithm maps keycodes to Gravity flags:
| Keycode | Gravity change |
|---|---|
KEYCODE_DPAD_UP |
Gravity.TOP |
KEYCODE_DPAD_DOWN |
Gravity.BOTTOM |
KEYCODE_DPAD_LEFT |
Gravity.LEFT |
KEYCODE_DPAD_RIGHT |
Gravity.RIGHT |
When PIP is in expanded mode, movement is restricted to the axis perpendicular to the expansion orientation (lines 251–259).
68.6.3 TV Menu System
TvPipMenuController (line 52, 654 lines) implements three menu modes:
| Mode | Value | UI |
|---|---|---|
MODE_NO_MENU |
0 | Invisible |
MODE_MOVE_MENU |
1 | Directional arrows; D-pad moves PIP |
MODE_ALL_ACTIONS_MENU |
2 | Grid of action buttons (close, expand, custom) |
showPictureInPictureMenu(boolean moveMenu) (line 354 of TvPipController)
selects between showMovementMenu() and showMenu(). An
ActionBroadcastReceiver (line 747) handles broadcast intents
(ACTION_SHOW_PIP_MENU, ACTION_CLOSE_PIP, ACTION_MOVE_PIP) sent from
the TV notification or system UI.
68.6.4 TvPipTransition
TvPipTransition (line 85, 842 lines) extends PipTransitionController and
uses TV-specific fade+zoom animations. The enter sequence (line 400) chains:
fade-out of the current screen with a ZOOM_ANIMATION_SCALE_FACTOR of 0.97f
(line 87) → 500 ms delay → fade-in of the PIP window with menu. The exit
sequence (line 507) reverses this. TvPipTransitionAnimatorUpdateListener
(line 722) applies per-frame surface property updates including alpha, bounds,
and menu synchronisation.
68.6.5 TvPipBoundsAlgorithm
TvPipBoundsAlgorithm (line 53, 366 lines) extends PipBoundsAlgorithm and
adds:
- Gravity-based positioning — bounds are computed from gravity flags, not pixel coordinates.
- Expanded mode — two fixed-dimension variants:
mFixedExpandedHeightInPxfor horizontal expansion andmFixedExpandedWidthInPxfor vertical expansion (lines 59–60). - Keep-clear areas —
TvPipKeepClearAlgorithm(771 lines, Kotlin) ensures PIP does not overlap system navigation bars or other reserved regions.
68.7 PIP v2 Improvements
The pip2/ directory contains a ground-up rewrite for phone PIP that preserves
the TV PIP path unchanged.
68.7.1 Architectural Decomposition
The monolithic PipTaskOrganizer (1 961 lines) is replaced by three focused
components:
| v1 Component | v2 Replacement | Lines |
|---|---|---|
PipTaskOrganizer |
PipTaskListener |
253 |
| (scheduling logic in PipTaskOrganizer) | PipScheduler |
471 |
PipTransition (1 466 lines) |
PipTransition + handlers |
1 241 + 681 |
PipTransitionState (165 lines) |
PipTransitionState |
537 |
68.7.2 Enhanced State Machine
The v2 PipTransitionState (line 71, 537 lines) defines ten states vs five
in v1, adding granular tracking for bounds changes:
| Constant | Value | Line | New in v2? |
|---|---|---|---|
UNDEFINED |
0 | 74 | No |
SWIPING_TO_PIP |
1 | — | Yes |
SCHEDULED_ENTER_PIP |
2 | — | Yes |
ENTERING_PIP |
3 | — | No |
ENTERED_PIP |
4 | — | No |
SCHEDULED_BOUNDS_CHANGE |
5 | — | Yes |
CHANGING_PIP_BOUNDS |
6 | — | Yes |
CHANGED_PIP_BOUNDS |
7 | — | Yes |
EXITING_PIP |
8 | — | No |
EXITED_PIP |
9 | — | Yes |
State transitions carry context via Bundle extras (e.g.
extra.putParcelable(PIP_TASK_LEASH, pipChange.getLeash())). A shouldTransitionToState()
validator (line 478) prevents invalid transitions. An idle-state callback
(setOnIdlePipTransitionStateRunnable, line 288) queues work until the state
machine reaches a stable state.
68.7.3 PipScheduler (line 60, 471 lines)
PipScheduler implements PipTransitionStateChangedListener and provides
explicit scheduling methods that wrap operations in
WindowContainerTransaction:
scheduleExitPipViaExpand()(line 194)scheduleRemovePip()(line 217)scheduleAnimateResizePip(Rect, boolean, int)(line 248)scheduleMoveToDisplay()(line 266)scheduleFinishPipBoundsChange()(line 291)scheduleUserResizePip()(line 304) — four overloads for direct matrix transforms withoutWindowContainerTransaction.
68.7.4 Transition Handlers
PipTransition (v2, line 98, 1 241 lines) delegates to sub-handlers:
| Handler | Lines | Purpose |
|---|---|---|
ContentPipHandler |
171 | Content-PiP entry (auto-enter) |
PipExpandHandler |
510 | Expand-to-fullscreen and split |
PipDisplayChangeObserver |
112 | Display rotation / config changes |
PipTransitionUtils |
163 | Static helpers (find changes by token, get leash) |
68.7.5 Specialised Animators
v2 replaces the single PipAnimationController with four dedicated classes:
| Animator | Lines | Purpose |
|---|---|---|
PipEnterAnimator |
276 | Enter animation with app-icon overlay |
PipResizeAnimator |
183 | Bounds-change animation with base-bounds scaling |
PipExpandAnimator |
216 | Expand-to-fullscreen with insets interpolation |
PipAlphaAnimator |
152 | Opacity fade in / out |
Each extends ValueAnimator and manages its own start/finish
SurfaceControl.Transaction, improving testability through constructor-injected
supplier interfaces.
68.8 Integration with Shell Transitions System
PIP is one of the most complex consumers of the Shell Transitions framework (see §15 Shell Transitions). The integration points are:
-
PipTransitionController(460 lines) is the base class that registers withTransitions.addHandler(). BothPipTransition(v1/v2) andTvPipTransitionextend it. -
Transition types defined for PIP:
TRANSIT_PIPis defined inWindowManager(line 380). The remaining types —TRANSIT_EXIT_PIP,TRANSIT_EXIT_PIP_TO_SPLIT,TRANSIT_REMOVE_PIP,TRANSIT_CLEANUP_PIP_EXIT— are defined incom.android.wm.shell.transition.Transitions. -
Transition lifecycle:
handleRequest()intercepts requests before they execute;startAnimation()drives the visual transition;mergeAnimation()handles superseding transitions;onTransitionConsumed()cleans up when a transition is absorbed. -
Swipe-to-home — Launcher’s gesture sends
setInSwipePipToHomeTransition(true)onPipTransitionState;handleSwipePipToHomeTransition()(line 1 240 ofPipTransition) bridges the Launcher animation with Shell’s own enter animation viastartSwipePipToHome()/stopSwipePipToHome()onPipTaskOrganizer.
68.9 Cross-References
| Section | Relationship |
|---|---|
| §12 Shell Features | PIP is a Shell feature registered via ShellInit; Pip.java (85 lines) defines the external interface |
| §15 Shell Transitions | PipTransitionController registers as a Transitions.TransitionHandler; PIP defines custom transit types |
| §13 TaskOrganizer | PipTaskOrganizer / PipTaskListener implement ShellTaskOrganizer.TaskListener |
| §16 SyncTransactionQueue | PipTaskOrganizer uses SyncTransactionQueue for ordered surface updates |
| §14 DisplayController | Both Phone and TV PIP listen for OnDisplaysChangedListener events |
68.10 Key Source Files
| File | Path | Lines |
|---|---|---|
| PipTaskOrganizer | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java |
1 961 |
| PipTransition (v1) | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java |
1 466 |
| PipAnimationController | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java |
868 |
| PipTransitionController | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java |
460 |
| PipSurfaceTransactionHelper | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java |
411 |
| PipTransitionState (v1) | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java |
165 |
| PipTouchHandler | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java |
1 091 |
| PipMotionHelper | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java |
741 |
| PipController (v1) | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java |
1 399 |
| PipResizeGestureHandler | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java |
693 |
| PhonePipMenuController (v1) | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java |
598 |
| PipDismissTargetHandler | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java |
311 |
| TvPipController | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java |
811 |
| TvPipTransition | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java |
842 |
| TvPipMenuController | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java |
654 |
| TvPipBoundsAlgorithm | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java |
366 |
| TvPipKeepClearAlgorithm | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt |
771 |
| PipTransition (v2) | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java |
1 241 |
| PipTransitionState (v2) | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java |
537 |
| PipScheduler (v2) | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java |
471 |
| PipTaskListener (v2) | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java |
253 |
| PipTouchHandler (v2) | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java |
1 225 |
| PipController (v2) | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java |
928 |
| PipExpandHandler (v2) | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandler.java |
510 |
| PipEnterAnimator (v2) | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java |
276 |
| PipExpandAnimator (v2) | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java |
216 |
| PipResizeAnimator (v2) | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java |
183 |
| PipAlphaAnimator (v2) | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java |
152 |
69. Bubbles System Architecture
69.1 Architecture Overview — Notification-to-Window Bridge
The Android Bubbles system provides a floating, interactive UI that bridges the
notification system with full windowed activities. A bubble starts life as a
notification with Notification.BubbleMetadata, but when expanded it hosts a
complete TaskView running an embedded Activity — effectively a miniature
window floating above all other content.
The Bubbles subsystem lives entirely within the WindowManager Shell module and is organized around five architectural pillars:
| Pillar | Primary Class | Responsibility |
|---|---|---|
| Orchestration | BubbleController (3,961 lines) |
Lifecycle, notification listening, permission enforcement |
| Rendering | BubbleStackView (4,305 lines) |
Physics-based stack/floating UI, drag, dismiss |
| Data Model | BubbleData (1,447 lines) |
Ordered bubble list, overflow, selection state |
| Transitions | BubbleTransitions (2,179 lines) |
Shell transition handlers for expand/collapse/convert |
| Positioning | BubblePositioner (1,063 lines) |
Screen-aware layout calculations for all modes |
Two distinct UI modes exist:
- Classic Bubble Stack — floating circles rendered by
BubbleStackView, draggable anywhere on screen, managed entirely within System UI. - Bubble Bar — a launcher-integrated bar where bubbles appear as icons in a taskbar-like strip, with expanded views rendered by
BubbleBarLayerViewand itsBubbleBarExpandedView.
graph TB
subgraph "Notification System"
NS[NotificationListenerService]
NE[NotificationEntry]
BM[BubbleMetadata]
end
subgraph "BubbleController — Orchestration"
BC[BubbleController]
BDL[BubbleData.Listener]
BVC[BubbleViewCallback]
end
subgraph "Data Layer"
BD[BubbleData]
BO[BubbleOverflow]
BR[BubbleDataRepository]
BPR[BubblePersistentRepository]
BVR[BubbleVolatileRepository]
end
subgraph "Classic Stack Mode"
BSV[BubbleStackView]
SAC[StackAnimationController]
EAC[ExpandedAnimationController]
PAL[PhysicsAnimationLayout]
BEV[BubbleExpandedView]
end
subgraph "Bubble Bar Mode"
BBLV[BubbleBarLayerView]
BBEV[BubbleBarExpandedView]
BBAH[BubbleBarAnimationHelper]
BBMV[BubbleBarMenuView]
end
NS -->|"onNotificationPosted"| BC
NE --> BM
BC --> BD
BD -->|"applyUpdate()"| BDL
BDL --> BVC
BVC -->|"Classic"| BSV
BVC -->|"Bar"| BBLV
BSV --> PAL
PAL --> SAC
PAL --> EAC
BSV --> BEV
BBLV --> BBEV
BBLV --> BBAH
BD --> BO
BD --> BR
BR --> BPR
BR --> BVR
69.2 BubbleController — Lifecycle Management
BubbleController is the central orchestrator, implementing
ConfigurationChangeListener and managing all bubble lifecycle events across
both UI modes.
Class declaration:
// frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
public class BubbleController implements ConfigurationChangeListener,
// ... additional interfaces
69.2.1 Core Fields and Dependencies
private final BubbleData mBubbleData; // Data model
@Nullable private BubbleStackView mStackView; // Classic stack (nullable)
private Bubbles.BubbleStateListener mBubbleStateListener; // Launcher listener for bar mode
private BubbleViewCallback mBubbleViewCallback; // Active view callback
The controller selects the active BubbleViewCallback based on display mode:
mBubbleViewCallback = isShowingAsBubbleBar()
? mBubbleBarViewCallback
: mBubbleStackViewCallback;
69.2.2 BubbleViewCallback Interface
This interface abstracts the two rendering modes:
public interface BubbleViewCallback {
void removeBubble(Bubble removedBubble);
void addBubble(Bubble addedBubble);
void updateBubble(Bubble updatedBubble);
void selectionChanged(BubbleViewProvider selectedBubble);
void suppressionChanged(Bubble bubble, boolean isSuppressed);
void expansionChanged(boolean isExpanded);
void bubbleOrderChanged(List<Bubble> bubbleOrder, boolean updatePointer);
void bubbleOverflowChanged(boolean hasBubbles);
void updateVisibility(boolean visible);
}
69.2.3 Notification Listening and Data Flow
The controller registers a BubbleData.Listener that receives an Update
object after every data mutation:
private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() {
@Override
public void applyUpdate(BubbleData.Update update) {
ensureBubbleViewsAndWindowCreated();
loadOverflowBubblesFromDisk();
// Process removals, additions, updates, selection changes,
// expansion changes, suppression changes, order changes
}
};
The data listener dispatches to mBubbleViewCallback for each change type
(add, remove, update, selection, expansion, suppression, ordering).
69.2.4 Package and Shortcut Monitoring
BubbleController registers a LauncherApps.Callback to track:
- Package removal →
mBubbleData.removeBubblesWithPackageName(s, DISMISS_PACKAGE_REMOVED) - Shortcut invalidation →
mBubbleData.removeBubblesWithInvalidShortcuts(...) - Package unavailability → iterates packages, removes associated bubbles
69.2.5 Dismiss Reasons
The Bubbles interface defines 19 dismiss reason constants:
| Constant | Value | Trigger |
|---|---|---|
DISMISS_USER_GESTURE |
1 | User swipe/drag to dismiss |
DISMISS_AGED |
2 | Bubble exceeded time limit |
DISMISS_TASK_FINISHED |
3 | Embedded task completed |
DISMISS_BLOCKED |
4 | DND or other suppression |
DISMISS_NOTIF_CANCEL |
5 | Notification cancelled by app |
DISMISS_NO_LONGER_BUBBLE |
7 | Metadata removed |
DISMISS_PACKAGE_REMOVED |
13 | App uninstalled |
DISMISS_SHORTCUT_REMOVED |
12 | Shortcut invalidated |
DISMISS_USER_GESTURE_FROM_LAUNCHER |
18 | Dismissed from bubble bar |
DISMISS_JUMPCUT_BUBBLE_SWITCH |
19 | Replaced during jumpcut switch |
69.2.6 Inner Classes
| Inner Class | Purpose |
|---|---|
BubblesImeListener |
Monitors IME state to adjust bubble positioning |
IBubblesImpl |
AIDL binder stub implementing IBubbles |
BubblesImpl |
Implementation of Bubbles interface for SysUI |
BubbleTaskViewController |
Manages embedded TaskView lifecycle |
69.3 BubbleStackView — Physics-Based Stack Rendering
At 4,305 lines, BubbleStackView is the largest class in the Bubbles system.
It extends FrameLayout and manages the classic floating bubble stack UI
with physics-based animations, drag handling, magnetic dismiss targets, and
expanded view rendering.
69.3.1 View Hierarchy
public class BubbleStackView extends FrameLayout
Key child views and controllers:
private PhysicsAnimationLayout mBubbleContainer; // Physics-animated bubble icons
private StackAnimationController mStackAnimationController; // Stack positioning physics
private ExpandedAnimationController mExpandedAnimationController; // Expanded row animations
private ExpandedViewAnimationController mExpandedViewAnimationController;
private FrameLayout mExpandedViewContainer; // Hosts BubbleExpandedView
private MagnetizedObject<?> mMagnetizedObject; // Dismiss magnet physics
private MagnetizedObject.MagneticTarget mMagneticTarget; // Dismiss circle target
69.3.2 Key Constants
static final float FLYOUT_DRAG_PERCENT_DISMISS = 0.25f;
private static final float FLYOUT_DISMISS_VELOCITY = 2000f;
private static final int FADE_IN_DURATION = 320;
static final int FLYOUT_HIDE_AFTER = 5000; // 5 seconds
private static final float EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT = 0.1f;
private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150;
private static final float PERCENT_HIDDEN_WHEN_STASHED = 0.55f;
private static final int ANIMATE_STASH_DELAY = 700;
69.3.3 Magnetic Dismiss System
BubbleStackView uses MagnetizedObject from
com.android.wm.shell.shared.magnetictarget to create a physics-based
magnetic dismiss experience. Two separate magnet listeners handle:
mStackMagnetListener— magnetizes the entire collapsed stack toward the dismiss circle during drag.mIndividualBubbleMagnetListener— magnetizes a single bubble being dragged out of the expanded row.
When a bubble sticks to the magnetic target, it snaps to center with a spring
animation. On release within the target, dismissBubbleIfExists() is called.
69.3.4 Flyout System
When a new notification arrives for a bubble, a BubbleFlyoutView appears
next to the stack showing a preview of the message. It auto-hides after
FLYOUT_HIDE_AFTER (5s) and can be dragged to dismiss with a
SpringAnimation controlling the collapse transition:
private final SpringAnimation mFlyoutTransitionSpring =
new SpringAnimation(this, mFlyoutCollapseProperty);
stateDiagram-v2
[*] --> Collapsed: addBubble()
Collapsed --> Dragging: onTouchEvent DOWN
Dragging --> Collapsed: release near edge
Dragging --> MagnetizedToDismiss: enter dismiss zone
MagnetizedToDismiss --> Dismissed: release in zone
MagnetizedToDismiss --> Dragging: exit dismiss zone
Collapsed --> FlyoutShowing: new notification
FlyoutShowing --> Collapsed: timeout 5s / drag dismiss
Collapsed --> Expanded: tap bubble
Expanded --> Collapsed: tap outside / back press
Expanded --> BubbleDragOut: drag single bubble
BubbleDragOut --> Expanded: snapBubbleBack()
BubbleDragOut --> Dismissed: dismissDraggedOutBubble()
Dismissed --> [*]
69.4 Two UI Modes — Classic Stack vs. Bubble Bar
The Bubbles system supports two fundamentally different rendering modes,
selected at initialization based on whether the launcher reports bubble bar
support via setLauncherHasBubbleBar().
69.4.1 Classic Bubble Stack (BubbleStackView)
- Floating circles overlapping in a physics-driven stack
- Entire stack is draggable to any screen edge
- Stack auto-stashes after
ANIMATE_STASH_DELAY(700ms) with 55% hidden - Expansion fans bubbles into a horizontal row via
ExpandedAnimationController - Expanded view appears below the row as a
BubbleExpandedView
69.4.2 Bubble Bar Mode (BubbleBarLayerView + Launcher)
- Bubbles appear as icons in the launcher’s taskbar
BubbleBarLayerViewis the WindowManager-added overlay for expanded viewsBubbleBarExpandedViewreplacesBubbleExpandedView, adding:BubbleBarCaptionView— window caption/drag handleBubbleBarMenuView— context menu viewBubbleBarMenuViewController— menu logic (“Don’t bubble conversation”, “App settings”, “Dismiss”, and optionally “Move to fullscreen” behind flag)BubbleBarExpandedViewDragController— drag-to-reposition/dismiss
BubbleBarAnimationHelperdrives expand, collapse, and switch animations- State updates are sent to the launcher via
Bubbles.BubbleStateListener.onBubbleStateChange(BubbleBarUpdate)
69.4.3 Mode Selection
// BubbleController.java
public boolean isShowingAsBubbleBar() { ... }
// Selects callback at initialization
mBubbleViewCallback = isShowingAsBubbleBar()
? mBubbleBarViewCallback // → BubbleBarLayerView
: mBubbleStackViewCallback; // → BubbleStackView
69.5 BubbleData — Data Model and Overflow Management
BubbleData maintains the authoritative state for all bubbles and publishes
atomic Update objects through its Listener interface.
69.5.1 Data Collections
private final List<Bubble> mBubbles; // Active stack
private final List<Bubble> mOverflowBubbles; // Aged-out overflow
private final HashMap<String, Bubble> mPendingBubbles; // Loading
private final ArrayMap<LocusId, Bubble> mSuppressedBubbles; // Suppressed by locus visibility
private BubbleViewProvider mSelectedBubble; // Currently selected
private boolean mExpanded; // Expansion state
private int mMaxBubbles; // from BubblePositioner.getMaxBubbles()
private int mMaxOverflowBubbles; // from R.integer.bubbles_max_overflow
When mBubbles.size() > mMaxBubbles, the oldest bubbles are removed and moved
to overflow. The overflow itself is capped at mMaxOverflowBubbles + 1,
removing the oldest entry when exceeded.
69.5.2 Update Object
The Update class is a snapshot of all changes from a single data operation:
static final class Update {
boolean expandedChanged;
boolean orderChanged;
boolean expanded;
boolean showOverflowChanged;
@Nullable BubbleViewProvider selectedBubble;
@Nullable Bubble addedBubble;
@Nullable Bubble updatedBubble;
@Nullable Bubble suppressedBubble;
@Nullable Bubble unsuppressedBubble;
boolean shouldShowEducation;
final List<Pair<Bubble, Integer>> removedBubbles;
final List<Bubble> overflowBubbles;
@Nullable BubbleBarLocation mBubbleBarLocation;
}
69.5.3 Bubble Entity Model
The Bubble class (1,409 lines) implements BubbleViewProvider and wraps
notification or shortcut data:
public class Bubble implements BubbleViewProvider {
public static final String KEY_APP_BUBBLE = "key_app_bubble";
public static final String KEY_NOTE_BUBBLE = "key_note_bubble";
private final String mKey;
private ShortcutInfo mShortcutInfo;
private String mPackageName;
private String mTitle;
private Icon mIcon;
private UserHandle mUser;
private int mFlags;
private PendingIntent mPendingIntent;
private int mDesiredHeight;
private int mDesiredHeightResId;
private BadgedImageView mIconView; // Classic stack icon
private BubbleBarExpandedView mBubbleBarExpandedView; // Bar mode view
}
Multiple constructors support creation from: notification entries, shortcut info, pending intents, running tasks, and app bubble intents.
69.6 BubbleTransitions — Shell Transition Handlers
BubbleTransitions (2,179 lines) implements the Shell Transitions protocol to
coordinate bubble window animations with the system transition framework.
69.6.1 Transition Handler Classes
Each transition type is an inner class implementing both TransitionHandler
and the BubbleTransition marker interface:
| Handler Class | Purpose |
|---|---|
LaunchNewTaskBubbleForExistingTransition |
Opens a new task inside an existing bubble’s TaskView |
JumpcutBubbleSwitchTransition |
Jumpcutting between two bubbles (close one, open another) |
LaunchOrConvertToBubble |
Converts an existing activity to a bubble or launches new |
ConvertToBubble |
Converts a freeform/fullscreen window into a bubble |
ConvertFromBubble |
Moves a bubble task to fullscreen or split |
DraggedBubbleIconToFullscreen |
Handles bubble icon dragged to fullscreen zone |
FloatingToBarConversion |
Converts floating stack to bubble bar mode |
69.6.2 Transition Lifecycle
// Pending transitions are stored by IBinder token
final Map<IBinder, TransitionHandler> mPendingEnterTransitions;
final Map<IBinder, TransitionHandler> mEnterTransitions;
Each handler follows the pattern:
- Store pending →
storePendingEnterTransition() - Inflate views →
onInflatedCallback.accept(handler) - Start animation →
startAnimation()withTransitionInfo - Finish →
finishCallback.onTransitionFinished(wct)
sequenceDiagram
participant BC as BubbleController
participant BT as BubbleTransitions
participant TH as TransitionHandler
participant WM as WindowManager
participant EV as ExpandedViewAnimator
BC->>BT: startLaunchNewTaskBubble(bubble, transition)
BT->>TH: new LaunchNewTaskBubbleForExistingTransition()
TH->>TH: store in mPendingEnterTransitions
WM->>TH: onTransitionReady(transition, info, startT, finishT)
TH->>TH: Match TaskInfo from TransitionInfo.Change
TH->>TH: Apply startTransaction (reparent, set bounds)
TH->>TH: addListenerForTaskId(taskView, taskId)
TH->>EV: animateExpand(closingBubble, cleanup)
EV-->>TH: animation complete
TH->>WM: finishCallback.onTransitionFinished(finishWct)
69.7 BubbleExpandedView — Embedded Activity UI
BubbleExpandedView (1,032 lines) extends LinearLayout and hosts a full
TaskView that runs the bubble’s target Activity within an embedded window.
69.7.1 View Composition
public class BubbleExpandedView extends LinearLayout {
private AlphaOptimizedButton mManageButton; // "Manage" action button
private TaskView mTaskView; // Embedded activity window
private final FrameLayout mExpandedViewContainer; // Clipped container
private BubbleTaskViewListener mTaskViewListener; // Task lifecycle callbacks
}
The mExpandedViewContainer is configured with setClipToOutline(true) and
a round-rect ViewOutlineProvider to produce the characteristic rounded
expanded bubble window.
69.7.2 TaskView Integration
The TaskView (from com.android.wm.shell.taskview) embeds a real Activity
task within the bubble’s view hierarchy. BubbleTaskViewListener monitors
task lifecycle events and relays them through a callback:
new BubbleTaskViewListener.Callback() {
public void onTaskCreated() { ... }
public void onBackPressed() { mStackView.onBackPressed(); }
}
The TaskView content width is managed by BubblePositioner:
void updateTaskViewContentWidth() {
if (mTaskView != null) {
int width = mPositioner.getTaskViewContentWidth(isStackOnLeft);
if (mTaskView.getWidth() != width) {
mTaskView.setLayoutParams(lp);
}
}
}
69.7.3 BubbleBarExpandedView (Bar Mode)
In bubble bar mode, BubbleBarExpandedView (803 lines) extends FrameLayout
and adds a window-caption-like UI with:
BubbleBarCaptionView— drag handle at top for repositioningBubbleBarMenuViewController— context menu with “Don’t bubble conversation”, “App settings”, “Dismiss”, and optionally “Move to fullscreen” (behind flag) actions- Caption insets —
mTaskView.setCaptionInsets(Insets.of(0, mCaptionHeight, 0, 0))reserves space for the caption bar - Surface clipping —
mTaskView.setEnableSurfaceClipping(true)with corner radius
69.8 StackAnimationController — Physics Engine
StackAnimationController (1,089 lines) extends
PhysicsAnimationLayout.PhysicsAnimationController and drives the physical
behavior of the collapsed bubble stack.
69.8.1 Spring Constants
private static final float STACK_SPRING_STIFFNESS = 700f;
public static final int SPRING_TO_TOUCH_STIFFNESS = 12000;
public static final float IME_ANIMATION_STIFFNESS = SpringForce.STIFFNESS_LOW;
private static final int CHAIN_STIFFNESS = 800;
private static final float SPRING_AFTER_FLING_DAMPING_RATIO = 0.85f;
The controller uses androidx.dynamicanimation.animation.SpringForce with
configurable stiffness and damping ratios to produce fluid, physically
plausible motion.
69.8.2 Stack Positioning
private PointF mStackPosition = new PointF(-1, -1);
public PointF getStackPosition() { return mStackPosition; }
public void moveStackFromTouch(float x, float y) {
springStack(x, y, SPRING_TO_TOUCH_STIFFNESS);
mFirstBubbleSpringingToTouch = true;
}
public float flingStackThenSpringToEdge(float x, float velX, float velY) {
// Fling with velocity, then spring to nearest edge
flingThenSpringFirstBubbleWithStackFollowing(...);
}
The stack always settles at a horizontal edge via
getStackPositionAlongNearestHorizontalEdge(). The
flingThenSpringFirstBubbleWithStackFollowing method first runs a
FlingAnimation then chains into a SpringAnimation for smooth deceleration.
69.8.3 PhysicsAnimationLayout
PhysicsAnimationLayout (in animation/) extends FrameLayout and provides
the foundation for chained physics animations:
public class PhysicsAnimationLayout extends FrameLayout {
abstract static class PhysicsAnimationController {
abstract void onChildAdded(View child, int index);
abstract void onChildRemoved(View child, int index, Runnable finishRemoval);
abstract void onChildReordered(View child, int oldIndex, int newIndex);
}
}
Children added to the layout automatically inherit spring-based chain
animations. Each bubble icon follows the one in front of it through chained
DynamicAnimation properties.
69.8.4 ExpandedAnimationController
ExpandedAnimationController manages the fan-out to a horizontal row:
private static final float DAMPING_RATIO_MEDIUM_LOW_BOUNCY = 0.65f;
private static final float DAMPING_RATIO_OVERFLOW_BOUNCY = 0.90f;
private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS = 400;
Key methods:
collapseBackToStack(PointF, boolean, Runnable)— animates from row back to overlapping stackprepareForBubbleDrag(View)— readies a bubble for individual drag outdragBubbleOut(View, float, float)— tracks drag positionsnapBubbleBack(View, float, float)— springs a released bubble back to its row positiondismissDraggedOutBubble(View, ...)— animates dismissal of a dragged-out bubble
69.9 Foldable Device Support
The fold/ subdirectory (3 files) handles foldable device transitions:
69.9.1 BubblesUnfoldListener
// frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/fold/BubblesUnfoldListener.kt
class BubblesUnfoldListener(
private val bubbleData: BubbleData,
private val foldLockSettingsObserver: BubblesFoldLockSettingsObserver,
private val onStartBarToFloatingOrFullscreenTransition: (Bubble, Boolean) -> Unit
) : ShellUnfoldProgressProvider.UnfoldListener
When the device folds while a bubble is expanded in bar mode, and the device
is configured to stay awake on fold (isStayAwakeOnFold()), the listener
triggers a transition from bar mode to floating or fullscreen — since the
inner display’s bubble bar is no longer visible on the outer cover screen.
If the selected bubble’s top activity is fixed-orientation landscape, it transitions to fullscreen instead of floating, since a landscape activity cannot fit in the narrower floating bubble window.
69.9.2 BubblesFoldLockSettingsObserver
fun interface BubblesFoldLockSettingsObserver {
fun isStayAwakeOnFold(): Boolean
}
Implemented by BubblesFoldLockSettingsObserverImpl, which reads the
system setting for whether the device remains awake when folded.
69.9.3 BubbleTaskUnfoldTransitionMerger
Located in the root bubbles directory, this class coordinates the unfold animation progress for bubble task views, ensuring smooth visual transitions during fold/unfold.
69.10 Storage and Persistence
The storage/ subdirectory provides a two-tier persistence model:
69.10.1 BubbleEntity
data class BubbleEntity(
@UserIdInt val userId: Int,
val packageName: String,
val shortcutId: String,
val key: String,
val desiredHeight: Int,
@DimenRes val desiredHeightResId: Int,
val title: String? = null,
val taskId: Int,
val locus: String? = null,
val isDismissable: Boolean = false
)
69.10.2 Volatile Repository
BubbleVolatileRepository maintains an in-memory cache of up to
CAPACITY = 16 entities per user. It also manages LauncherApps shortcut
caching/uncaching to keep bubble shortcuts pinned:
private fun cache(bubbles: List<BubbleEntity>) {
launcherApps.cacheShortcuts(key.pkg, bubbles.map { it.shortcutId }, ...)
}
69.10.3 Persistent Repository
BubblePersistentRepository serializes to/from XML on disk via
BubbleXmlHelper:
fun persistsToDisk(bubbles: SparseArray<List<BubbleEntity>>): Boolean
fun readFromDisk(): SparseArray<List<BubbleEntity>>
This enables bubble state to survive reboots — overflow bubbles are lazily
reloaded in mBubbleDataListener.applyUpdate().
69.11 Cross-References
-
§14 SurfaceControlViewHost —
BubbleExpandedViewembedsTaskViewwhich usesSurfaceControlViewHost(SCVH) to host the activity’s surface within the bubble’s view hierarchy. The embedded task surface is positioned and clipped throughSurfaceControltransactions coordinated inBubbleTransitions. -
§65 Window Types — Bubbles are added to
WindowManagerwithTYPE_APPLICATION_OVERLAYpriority layering. TheBubbleStackView(classic mode) andBubbleBarLayerView(bar mode) are both added as top-level window manager views withLAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYSto ensure proper rendering around display cutouts. -
Shell Transitions —
BubbleTransitionsimplementsTransitions.TransitionHandlerfrom the Shell Transitions framework (§19), participating in the system-wide transition coordination for window open/close/resize operations involving bubble tasks. -
TaskView — The
com.android.wm.shell.taskview.TaskViewcomponent provides the embedded window surface. Bubble-specific wrapping occurs inBubbleTaskViewwhich managesTaskViewlifecycle, task ID tracking, andTaskOrganizerregistration.
69.12 Key Source Files
| File | Path | Lines | Purpose |
|---|---|---|---|
BubbleController.java |
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ |
3,961 | Central orchestrator, lifecycle management |
BubbleStackView.java |
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ |
4,305 | Classic floating stack UI with physics |
BubbleTransitions.java |
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ |
2,179 | Shell transition handlers |
BubbleData.java |
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ |
1,447 | Data model, ordering, overflow |
Bubble.java |
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ |
1,409 | Individual bubble entity |
BubblePositioner.java |
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ |
1,063 | Screen-aware layout calculations |
BubbleExpandedView.java |
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ |
1,032 | Classic mode expanded TaskView host |
StackAnimationController.java |
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ |
1,089 | Spring physics for stack movement |
ExpandedAnimationController.java |
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ |
~600 | Fan-out row animation controller |
PhysicsAnimationLayout.java |
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ |
~1,200 | Chained spring animation framework |
BubbleBarLayerView.java |
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/ |
~752 | Bubble bar mode overlay view |
BubbleBarExpandedView.java |
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/ |
803 | Bar mode expanded view with caption |
BubbleBarAnimationHelper.java |
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/ |
~852 | Bar mode expand/collapse/switch anims |
BubbleFlyoutView.java |
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ |
— | Notification preview popup |
Bubbles.java |
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ |
— | Public interface, dismiss reason constants |
BubbleEntity.kt |
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/ |
~35 | Persistence data class |
BubblePersistentRepository.kt |
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/ |
— | XML disk persistence |
BubbleVolatileRepository.kt |
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/ |
— | In-memory cache (capacity 16) |
BubblesUnfoldListener.kt |
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/fold/ |
~50 | Fold state transition handler |
BubblesFoldLockSettingsObserver.kt |
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/fold/ |
~25 | Stay-awake-on-fold setting |
70. Split Screen Implementation
70.1 Stage-Based Architecture
Android’s split-screen implementation uses a stage-based architecture where the screen is
divided into two independent task containers called stages. Each stage is a root task managed by
StageTaskListener and orchestrated by StageCoordinator. The architecture sits within the
WM Shell module and integrates with the system’s transition framework for all animations.
The fundamental design principle is that stages are position-independent containers. A stage
holds child tasks and can be placed in either the top-or-left or bottom-or-right position.
The SplitScreen interface defines the stage type constants:
| Constant | Value | Description |
|---|---|---|
STAGE_TYPE_UNDEFINED |
-1 | Default/unspecified stage |
STAGE_TYPE_MAIN |
0 | The primary (main) stage |
STAGE_TYPE_SIDE |
1 | The secondary (side) stage |
STAGE_TYPE_A |
2 | Position-independent stage (flexible split) |
STAGE_TYPE_B |
3 | Position-independent stage (flexible split) |
STAGE_TYPE_C |
4 | Position-independent stage (flexible split) |
Split positions are defined in SplitScreenConstants:
| Constant | Value |
|---|---|
SPLIT_POSITION_UNDEFINED |
-1 |
SPLIT_POSITION_TOP_OR_LEFT |
0 |
SPLIT_POSITION_BOTTOM_OR_RIGHT |
1 |
Snap positions control the divider ratio:
| Constant | Value | Ratio |
|---|---|---|
SNAP_TO_2_33_66 |
0 | 33%–66% |
SNAP_TO_2_50_50 |
1 | 50%–50% |
SNAP_TO_2_66_33 |
2 | 66%–33% |
SNAP_TO_2_90_10 |
3 | 90%–10% |
SNAP_TO_2_10_90 |
4 | 10%–90% |
SNAP_TO_3_33_33_33 |
5 | Three-way split |
SNAP_TO_3_45_45_10 |
6 | 45%–45%–10% |
SNAP_TO_3_10_45_45 |
7 | 10%–45%–45% |
SNAP_TO_NONE |
10 | No snap position |
graph TD
subgraph "Split Screen Architecture"
SSC[SplitScreenController<br/>Public API] --> SC[StageCoordinator<br/>4804 lines - Master Orchestrator]
SC --> MS[StageTaskListener<br/>Main Stage]
SC --> SS[StageTaskListener<br/>Side Stage]
SC --> SL[SplitLayout<br/>Divider & Bounds]
SC --> SST[SplitScreenTransitions<br/>Animation Engine]
SC --> SMD[SplitMultiDisplayHelper<br/>Multi-Display Support]
SC --> SOO[StageOrderOperator<br/>Flexible Stage Ordering]
MS --> CT1[Child Tasks]
SS --> CT2[Child Tasks]
SL --> DV[Divider Bar<br/>Drag Handle]
end
70.2 StageCoordinator: Master Orchestrator
StageCoordinator (4,804 lines) is the central controller for split-screen state. It extends
StageCoordinatorAbstract which implements SplitLayout.SplitLayoutHandler,
DisplayChangeController.OnDisplayChangingListener, and Transitions.TransitionHandler. The class manages two StageTaskListener instances (mMainStage
and mSideStage), tracks the side stage position (mSideStagePosition), and owns the
SplitLayout for divider management.
Key fields:
public class StageCoordinator extends StageCoordinatorAbstract {
private StageTaskListener mMainStage;
private StageTaskListener mSideStage;
private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;
private SplitLayout mSplitLayout;
private ValueAnimator mDividerFadeInAnimator;
private boolean mDividerVisible;
}
The constructor accepts dependencies including Optional<DesktopTasksController>,
Optional<DesktopUserRepositories>, and DesktopState for desktop mode integration,
plus SplitState, TransactionPool, and MSDLPlayer for haptic feedback.
Core lifecycle methods:
prepareEnterSplitScreen(WindowContainerTransaction wct)— Activates stages, configures root task hierarchy, and applies initial bounds fromSplitLayoutprepareExitSplitScreen(int dismissTop, WindowContainerTransaction wct, @ExitReason int exitReason)— Deactivates stages and resets the dismissed stageonSnappedToDismiss(boolean closedBottomRightStage, @ExitReason int exitReason)— Triggered when the divider is dragged past a dismiss threshold; determines which stage survives and starts a dismiss transition viamSplitTransitions.startDismissTransition()onDoubleTappedDivider()— CallsswitchSplitPosition()to swap the two stagesupdateSurfaceBounds(@Nullable SplitLayout, Transaction, boolean)— Applies geometric bounds to stage surfaces during resize
The coordinator integrates with StageOrderOperator when the enableFlexibleSplit() flag is
enabled, allowing more than two stages and position-independent ordering.
stateDiagram-v2
[*] --> Idle
Idle --> EnteringSplit : startTasks() / prepareEnterSplitScreen()
EnteringSplit --> SplitActive : Transition completes
SplitActive --> Resizing : Divider drag
Resizing --> SplitActive : onLayoutSizeChanged()
SplitActive --> Dismissing : onSnappedToDismiss()
SplitActive --> SwapStages : onDoubleTappedDivider()
SwapStages --> SplitActive : switchSplitPosition()
Dismissing --> Idle : Exit transition completes
SplitActive --> Idle : EXIT_REASON_RETURN_HOME
SplitActive --> Idle : EXIT_REASON_DESKTOP_MODE
SplitActive --> Idle : EXIT_REASON_CHILD_TASK_ENTER_PIP
70.3 SplitScreenController: Public API
SplitScreenController (1,388 lines) is the public-facing API that implements
SplitDragPolicy.Starter and RemoteCallable<SplitScreenController>. It creates and owns the
StageCoordinator and exposes the SplitScreen interface to external modules (Launcher,
SystemUI, Recents).
Exit reasons are defined as @IntDef constants:
| Constant | Value | Trigger |
|---|---|---|
EXIT_REASON_UNKNOWN |
0 | Catch-all |
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW |
1 | App incompatible |
EXIT_REASON_APP_FINISHED |
2 | Task closed |
EXIT_REASON_DEVICE_FOLDED |
3 | Foldable fold event |
EXIT_REASON_DRAG_DIVIDER |
4 | Divider dragged to dismiss |
EXIT_REASON_RETURN_HOME |
5 | Home pressed |
EXIT_REASON_ROOT_TASK_VANISHED |
6 | Root task removed |
EXIT_REASON_CHILD_TASK_ENTER_PIP |
9 | Task entered PIP |
EXIT_REASON_DESKTOP_MODE |
12 | Switched to desktop |
EXIT_REASON_CHILD_TASK_ENTER_BUBBLE |
14 | Task entered bubble |
EXIT_REASON_DRAG_TO_FULLSCREEN |
15 | Task dragged to fullscreen |
Enter reasons:
| Constant | Value |
|---|---|
ENTER_REASON_UNKNOWN |
0 |
ENTER_REASON_MULTI_INSTANCE |
1 |
ENTER_REASON_DRAG |
2 |
ENTER_REASON_LAUNCHER |
3 |
The SplitScreen interface provides the primary external API:
public interface SplitScreen {
void startTasks(int taskId1, Bundle options1, int taskId2, Bundle options2,
@SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId);
void registerSplitScreenListener(SplitScreenListener listener, Executor executor);
void goToFullscreenFromSplit();
void setSplitscreenFocus(boolean leftOrTop);
}
The SplitScreenListener callback interface notifies observers of:
onStagePositionChanged(@StageType int stage, @SplitPosition int position)onTaskStageChanged(int taskId, @StageType int stage, boolean visible)onSplitBoundsChanged(Rect rootBounds, Rect mainBounds, Rect sideBounds)onSplitVisibilityChanged(boolean visible)
70.4 StageTaskListener: Per-Stage Task Monitoring
StageTaskListener (626 lines) implements ShellTaskOrganizer.TaskListener and acts as the
per-stage task container. Each instance tracks child tasks within a single stage, monitors
their lifecycle, and reports changes upward to StageCoordinator.
Key properties:
mIsActive— Whether this stage is currently activemId— The@StageTypeidentifier (unique per stage)mVisible— Current visibility state
The StageListenerCallbacks interface defines the upward communication:
public interface StageListenerCallbacks {
void onRootTaskAppeared(RunningTaskInfo taskInfo);
void onStageVisibilityChanged(StageTaskListener stageTaskListener);
void onChildTaskStatusChanged(StageTaskListener stage, int taskId,
boolean present, boolean visible);
void onChildTaskMovedToBubble(StageTaskListener stage, int taskId);
void onRootTaskVanished(StageTaskListener stageTaskListener);
void onNoLongerSupportMultiWindow(StageTaskListener stageTaskListener);
}
When enableFlexibleSplit() is enabled, only one stage at a time has mIsActive = true.
The listener manages its own SplitDecorManager for decorating the stage’s visual boundary
and provides methods such as addTask(), removeTask(), containsTask(int taskId),
getChildCount(), activate(WindowContainerTransaction), and deactivate().
graph LR
subgraph "StageTaskListener Flow"
TO[ShellTaskOrganizer] -->|taskAppeared| STL[StageTaskListener]
TO -->|taskVanished| STL
TO -->|taskInfoChanged| STL
STL -->|onRootTaskAppeared| SC[StageCoordinator]
STL -->|onChildTaskStatusChanged| SC
STL -->|onStageVisibilityChanged| SC
STL -->|onChildTaskMovedToBubble| SC
STL --> SDM[SplitDecorManager<br/>Visual Decorations]
end
70.5 SplitScreenTransitions: Animation Choreography
SplitScreenTransitions (708 lines) manages all transition animations for entering, exiting,
and resizing split-screen. It uses a session-based architecture with three pending slots:
| Session Type | Class | Purpose |
|---|---|---|
| Enter | EnterSession |
Tracks entering animation, snap position, resize flag |
| Dismiss | DismissSession |
Tracks exit reason and which stage stays on top |
| Resize | TransitSession |
Generic session for resize/passthrough transitions |
The base TransitSession holds:
class TransitSession {
final IBinder mTransition;
TransitionConsumedCallback mConsumedCallback;
TransitionFinishedCallback mFinishedCallback;
OneShotRemoteHandler mRemoteHandler;
boolean mCanceled;
final int mExtraTransitType;
}
EnterSession extends this with mResizeAnim (whether to animate resize), mEnteringPosition
(the snap position), and mRequireRootsInTransition (set when a remote transition is provided).
DismissSession tracks mReason (@ExitReason) and mDismissTop (@StageType).
The playAnimation() method orchestrates the visual transition:
- Initializes the transition with
initTransition() - Checks if the pending transition was canceled
- Delegates to a remote handler if one is registered (e.g., Launcher provides custom anims)
- Falls back to
playInternalAnimation()which uses alpha fade-in/fade-out withALPHA_INandALPHA_OUTinterpolators andFADE_DURATION(133ms)
Custom transit types used:
TRANSIT_SPLIT_SCREEN_PAIR_OPEN— Both tasks enter simultaneouslyTRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE— Second task enters an existing stageTRANSIT_SPLIT_DISMISS_SNAP— Divider-drag dismissTRANSIT_SPLIT_DISMISS— Programmatic dismissTRANSIT_SPLIT_PASSTHROUGH— Passthrough for remote handlers
70.6 Divider Management
The divider is managed by SplitLayout which implements
DisplayInsetsController.OnInsetsChangedListener. It defines the SplitLayoutHandler
interface that StageCoordinator implements:
public interface SplitLayoutHandler {
void onSnappedToDismiss(boolean snappedToEnd, int reason);
void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY,
boolean shouldUseParallaxEffect);
void onLayoutSizeChanged(SplitLayout layout);
void onLayoutPositionChanging(SplitLayout layout);
void onDoubleTappedDivider();
}
During a divider drag, the coordinator follows this sequence:
onLayoutPositionChanging()— Updates surface bounds in real-time using a pooledSurfaceControl.TransactionwithsetFrameTimelineVsync()for smooth renderingonLayoutSizeChanging()— Applies parallax effects (controlled by constantsPARALLAX_NONE,PARALLAX_DISMISSING,PARALLAX_ALIGN_CENTER,PARALLAX_FLEX,PARALLAX_FLEX_HYBRID) and callsStageTaskListener.onResizing()on each stageonLayoutSizeChanged()— Commits final bounds viaWindowContainerTransaction, callsupdateWindowBounds(), and triggersStageTaskListener.onResized()for cleanuponSnappedToDismiss()— If the divider passes the dismiss threshold, determines the surviving stage and callsprepareExitSplitScreen()followed bymSplitTransitions.startDismissTransition()
The divider bar is identified in transitions by FLAG_IS_DIVIDER_BAR and rendered at
Integer.MAX_VALUE layer during animations. Visibility is toggled via
setDividerVisibility(boolean visible, @Nullable Transaction t).
sequenceDiagram
participant U as User
participant SL as SplitLayout
participant SC as StageCoordinator
participant STL as StageTaskListener
participant WCT as WindowContainerTransaction
U->>SL: Drag divider
SL->>SC: onLayoutPositionChanging()
SC->>SC: updateSurfaceBounds(layout, t)
SL->>SC: onLayoutSizeChanging(layout, offsetX, offsetY)
SC->>STL: onResizing(bounds, otherBounds, displayBounds, t)
U->>SL: Release divider
alt Normal resize
SL->>SC: onLayoutSizeChanged(layout)
SC->>WCT: updateWindowBounds()
SC->>STL: onResized(t)
else Snap to dismiss
SL->>SC: onSnappedToDismiss(closedBottomRight)
SC->>SC: prepareExitSplitScreen()
SC->>SST: startDismissTransition()
else Double tap
SL->>SC: onDoubleTappedDivider()
SC->>SC: switchSplitPosition()
end
70.7 TV Split Screen Variant
The TV form factor has specialized split-screen support via TvStageCoordinator (109 lines)
which extends StageCoordinator and implements TvSplitMenuController.StageController.
The TV variant adds an on-screen menu overlay instead of a draggable divider, since TV
users navigate with a remote control (D-pad) rather than touch.
TvStageCoordinator overrides the split lifecycle hooks:
@Override
protected void onSplitScreenEnter() {
mTvSplitMenuController.addSplitMenuViewToSystemWindows();
mTvSplitMenuController.registerBroadcastReceiver();
}
@Override
protected void onSplitScreenExit() {
mTvSplitMenuController.unregisterBroadcastReceiver();
mTvSplitMenuController.removeSplitMenuViewFromSystemWindows();
}
The TvSplitMenuController (215 lines) implements TvSplitMenuView.Listener and provides
stage control via the StageController interface:
grantFocusToStage(@SplitPosition int stageToFocus)— Shifts input focusexitStage(@SplitPosition int stageToClose)— Closes one sideswapStages()— InvokesonDoubleTappedDivider()to swap stage positions
The menu is shown via a broadcast action "com.android.wm.shell.splitscreen.SHOW_MENU"
and rendered through SystemWindows as a TvSplitMenuView (117 lines).
TvSplitScreenController (148 lines) extends SplitScreenController to inject
the TV-specific coordinator and system windows dependency.
70.8 Multi-Display Split Scenarios
Multi-display split-screen is controlled by feature flags enableMultiDisplaySplit() and
enableNonDefaultDisplaySplit(). The SplitMultiDisplayHelper class manages per-display
split-screen state:
class SplitMultiDisplayHelper(private val displayManager: DisplayManager) {
private val displayTaskMap: MutableMap<Int, SplitTaskHierarchy> = mutableMapOf()
data class SplitTaskHierarchy(
var rootTaskInfo: RunningTaskInfo? = null,
var mainStage: StageTaskListener? = null,
var sideStage: StageTaskListener? = null,
var rootTaskLeash: SurfaceControl? = null,
var splitLayout: SplitLayout? = null
)
}
Each display can maintain its own SplitTaskHierarchy with independent main/side stages
and a separate SplitLayout. The helper provides:
getCachedOrSystemDisplayIds()— Returns connected display IDs (cached for performance)getDisplayRootTaskInfo(int displayId)— Retrieves the root task info for a display
The SplitMultiDisplayProvider interface defines:
getDisplayRootForDisplayId(int displayId)— Gets the display’s rootWindowContainerTokenprepareMovingSplitScreenRoot(WindowContainerTransaction, int displayId)— Prepares root migration between displays
When enableMultiDisplaySplit() is true, StageCoordinator initializes multi-display
support in its constructor and uses the helper to manage stage lifecycle per display.
70.9 Integration with PIP and Desktop Modes
Split-screen actively integrates with both PIP (Picture-in-Picture) and desktop windowing:
PIP Integration:
EXIT_REASON_CHILD_TASK_ENTER_PIP(value 9) — When a task in split enters PIP mode, split-screen exits gracefully, allowing the remaining task to go fullscreen- The coordinator prevents the remaining app from also entering PIP by setting
wct.setDoNotPip(rootTaskInfo.token)during dismissal MixedTransitionHelper.getPipReplacingChange()is used to detect PIP transitions that overlap with split transitionsPipFlagsintegration is imported for coordinating PIP-related behaviors
Desktop Mode Integration:
EXIT_REASON_DESKTOP_MODE(value 12) — Split exits when switching to desktop windowingStageCoordinatorholdsOptional<DesktopTasksController>,Optional<DesktopUserRepositories>, andDesktopStatereferences- Desktop mode’s
DesktopTasksController.onDesktopSplitSelectChoice()allows moving a desktop task into split-screen - The
SplitScreen.SplitSelectListenerinterface providesonRequestEnterSplitSelect()for desktop→split transitions
Bubble Integration:
EXIT_REASON_CHILD_TASK_ENTER_BUBBLE(value 14) — When a task converts to a bubbleStageTaskListener.StageListenerCallbacks.onChildTaskMovedToBubble()notifies the coordinator
70.10 Key Source Files
| File | Path | Lines | Description |
|---|---|---|---|
| StageCoordinator | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java |
4,804 | Master orchestrator for dual-stage management |
| StageCoordinatorAbstract | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinatorAbstract.java |
578 | Base class implementing SplitLayoutHandler |
| StageCoordinator2 | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator2.kt |
103 | Kotlin wrapper for flexible constructor delegation |
| SplitScreenController | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java |
1,388 | Public API, exit/enter reason definitions |
| SplitScreen | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java |
~150 | Interface for stage types, listeners, external API |
| SplitScreenTransitions | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java |
708 | Transition animation choreography |
| StageTaskListener | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java |
626 | Per-stage task lifecycle monitoring |
| StageOrderOperator | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt |
~200 | Flexible split stage ordering |
| SplitMultiDisplayHelper | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitMultiDisplayHelper.kt |
~150 | Per-display split hierarchy management |
| SplitMultiDisplayProvider | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitMultiDisplayProvider.kt |
~40 | Display root token provider interface |
| SplitStatusBarHider | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitStatusBarHider.kt |
~210 | Status bar immersive mode for split |
| SplitTransitionModifier | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitTransitionModifier.kt |
~273 | Transition modification hooks |
| SplitscreenEventLogger | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java |
~480 | Metrics/analytics logging |
| SplitLayout | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java |
~1,729 | Divider geometry, snap targets, parallax |
| SplitScreenConstants | frameworks/base/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java |
~300 | Constants: positions, snap values, flags |
| TvStageCoordinator | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java |
109 | TV-specific coordinator with menu overlay |
| TvSplitScreenController | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java |
148 | TV-specific controller |
| TvSplitMenuController | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuController.java |
215 | D-pad menu for TV split navigation |
| TvSplitMenuView | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuView.java |
117 | TV split menu view component |
71. Desktop Windowing Mode
71.1 Desktop Mode Architecture
Desktop windowing mode provides a freeform, overlapping-window experience on Android,
distinct from the legacy WINDOWING_MODE_FREEFORM. While freeform mode simply allows
free-positioned windows, desktop mode adds a complete windowing paradigm: window decorations
(title bars, resize handles), virtual desks, minimize/maximize support, snap tiling, keyboard
shortcuts, and immersive mode toggling.
The architecture centers on DesktopTasksController (6,953 lines) as the primary controller,
backed by DesktopRepository (1,736 lines) for state management and DesksOrganizer for
multi-desk workspace support. Window chrome is rendered by DesktopModeWindowDecoration
(2,174 lines) and managed by DesktopModeWindowDecorViewModel (2,211 lines).
The DesktopMode interface defines the external API exposed to SystemUI and Launcher:
public interface DesktopMode {
void addVisibleTasksListener(DesktopRepository.VisibleTasksListener listener,
Executor callbackExecutor);
void addDeskChangeListener(DesktopRepository.DeskChangeListener listener,
Executor callbackExecutor);
void moveFocusedTaskToDesktop(int displayId, DesktopModeTransitionSource source);
void moveFocusedTaskToFullscreen(int displayId, DesktopModeTransitionSource source);
void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop);
void toggleFocusedTaskFullscreenState(int displayId, DesktopModeTransitionSource source);
boolean isDisplayInDesktopMode(int displayId);
void registerDesktopFirstListener(DesktopFirstListener listener);
}
graph TD
subgraph "Desktop Mode Architecture"
DM[DesktopMode Interface<br/>External API] --> DTC[DesktopTasksController<br/>6953 lines]
DTC --> DR[DesktopRepository<br/>1736 lines - State Store]
DTC --> DO[DesksOrganizer<br/>Multi-Desk Manager]
DTC --> EDTH[EnterDesktopTaskTransitionHandler]
DTC --> ExDTH[ExitDesktopTaskTransitionHandler]
DTC --> DDTH[DragToDesktopTransitionHandler<br/>1606 lines]
DTC --> DIC[DesktopImmersiveController]
DTC --> DTL[DesktopTasksLimiter]
DR --> Desk1[Desk Data]
DR --> Desk2[Desk Data]
DTC --> DMWDVM[DesktopModeWindowDecorViewModel<br/>2211 lines]
DMWDVM --> DMWD[DesktopModeWindowDecoration<br/>2174 lines]
DMWD --> CC[CaptionController]
DMWD --> DRIL[DragResizeInputListener]
end
71.2 DesktopTasksController: Central Controller
DesktopTasksController (6,953 lines) is the heavyweight controller that implements
Transitions.TransitionHandler, DragAndDropController.DragAndDropListener, and
UserChangeListener. It takes an extensive set of constructor dependencies spanning
nearly 50 parameters, reflecting its role as the orchestration hub.
Key constructor dependencies:
class DesktopTasksController(
private val context: Context,
private val shellTaskOrganizer: ShellTaskOrganizer,
private val transitions: Transitions,
private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler,
private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler,
private val dragToDesktopTransitionHandler: DragToDesktopTransitionHandler,
private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
private val desktopImmersiveController: DesktopImmersiveController,
private val userRepositories: DesktopUserRepositories,
private val desksOrganizer: DesksOrganizer,
private val desktopState: DesktopState,
// ... plus ~35 more dependencies
) : RemoteCallable<DesktopTasksController>,
Transitions.TransitionHandler,
DragAndDropController.DragAndDropListener,
UserChangeListener
Core methods by category:
Task Lifecycle:
showDesktopApps(displayId, remoteTransition)— Brings desktop apps to frontmoveToFullscreen(taskInfo, transitionSource)— Exits task from desktop to fullscreenmoveToFullscreenWithAnimation()— Animated fullscreen transitionmoveTaskToFront(taskInfo)— Z-order reorderingminimizeTask(taskInfo, minimizeReason)— Sends task to minimized stateminimizeMultiActivityPipTask()— Special PIP+minimize handling
Window Management:
toggleDesktopTaskSize(taskInfo, interaction)— Toggle between default and maximized boundssnapToHalfScreen(taskInfo, position)— Snap-tile to left/right halfonDesktopSplitSelectChoice(taskInfo)— Enter split from desktop mode
Desk Management:
moveTaskToDesk(taskId, deskId)— Move task between virtual desksmoveTaskToDefaultDeskAndActivate(taskId)— Default desk reassignment
The controller also provides a DesktopModeImpl inner class that implements the
DesktopMode interface for external consumers, dispatching calls to the main executor.
sequenceDiagram
participant User
participant Handle as App Handle / Menu
participant DTC as DesktopTasksController
participant Trans as Transitions
participant EDTH as EnterDesktopTransitionHandler
participant DR as DesktopRepository
User->>Handle: Tap "Enter Desktop"
Handle->>DTC: moveFocusedTaskToDesktop(displayId, source)
DTC->>DR: addActiveTask(taskId, deskId)
DTC->>Trans: startTransition(TRANSIT_ENTER_DESKTOP_FROM_*)
Trans->>EDTH: startAnimation(transition, info, startT, finishT)
EDTH->>EDTH: Animate bounds change (fullscreen → freeform)
EDTH->>Trans: finishCallback
Trans->>DTC: onTransitionFinished()
DTC->>DR: updateVisibleTasks()
71.3 Window Decorations
Desktop mode windows are decorated with a full chrome system built on a layered class hierarchy:
Base class: WindowDecoration<T extends View & TaskFocusStateConsumer> (937 lines) — Abstract
base that manages the SurfaceControl hierarchy for window chrome, caption insets, and shadow
rendering. It creates additional view containers for captions and handles.
Desktop decoration: DesktopModeWindowDecoration (2,174 lines) extends
WindowDecoration<WindowDecorLinearLayout> and adds:
- Caption bar with app icon, title, and window control buttons
- Resize corner handles with configurable sizes (from
DragResizeWindowGeometry) - Edge resize handles with
getResizeEdgeHandleSize()andgetResizeHandleEdgeInset() - Box shadow rendering
- Handle menu integration
- Maximize menu integration
- App-to-web education tooltips
Caption system: The caption is controlled by CaptionController (470 lines) and
specialized subclasses:
AppHeaderController(776 lines) — Full app header with close/maximize/minimize buttonsAppHandleController— Compact drag handle for the app
abstract class CaptionController<T>(
private val context: Context,
private val windowDecoration: DesktopModeWindowDecoration,
// ...
) {
abstract fun createCaptionView(): WindowDecorationViewHolder<*>
abstract fun getCaptionHeight(): Int
abstract fun getCaptionWidth(): Int
open fun relayout(...)
fun addCaptionInset(wct: WindowContainerTransaction)
fun checkTouchEventInCustomizableRegion(ev: MotionEvent): Boolean
}
Resize handling: DragResizeInputListener (777 lines) creates invisible input windows
over the task edges and corners. When the user touches these regions, it delegates to
DragPositioningCallback which updates task bounds via WindowContainerTransaction. The
FluidResizeTaskPositioner (238 lines) implements smooth, frame-by-frame resizing.
View model: DesktopModeWindowDecorViewModel (2,211 lines) implements WindowDecorViewModel
and manages the lifecycle of all decoration instances. It handles touch events, drag detection
(via DragDetector), focus state, and coordinates between the decoration and the controller.
graph TD
subgraph "Window Decoration Stack"
WD[WindowDecoration<br/>937 lines - Base]
WD --> DMWD[DesktopModeWindowDecoration<br/>2174 lines]
DMWD --> CC[CaptionController<br/>470 lines - Abstract]
CC --> AHC[AppHeaderController<br/>776 lines]
CC --> AHandC[AppHandleController]
AHC --> MMC[MaximizeMenuController]
AHC --> HMC[HandleMenuController]
DMWD --> DRIL[DragResizeInputListener<br/>777 lines]
DRIL --> DPC[DragPositioningCallback]
DPC --> FRTP[FluidResizeTaskPositioner<br/>238 lines]
DMWD --> BS[BoxShadowHelper]
DMWDVM[DesktopModeWindowDecorViewModel<br/>2211 lines] --> DMWD
DMWDVM --> DD[DragDetector]
DMWDVM --> DMTEL[DesktopModeTouchEventListener]
end
71.4 Multi-Desk Workspaces
Desktop mode supports multiple virtual desks (workspaces), managed by DesksOrganizer
and tracked in DesktopRepository. The Desk data class holds per-desk state:
data class Desk(
val deskId: Int,
var displayId: Int,
val activeTasks: ArraySet<Int> = ArraySet(),
val visibleTasks: ArraySet<Int> = ArraySet(),
val minimizedTasks: ArraySet<Int> = ArraySet(),
val closingTasks: ArraySet<Int> = ArraySet(),
val freeformTasksInZOrder: ArrayList<Int> = ArrayList(),
var fullImmersiveTaskId: Int? = null,
var leftTiledTaskId: Int? = null,
var rightTiledTaskId: Int? = null,
) {
var boundsByTaskId: MutableMap<Int, Rect> = mutableMapOf()
}
The DesksOrganizer interface defines desk lifecycle operations:
interface DesksOrganizer {
fun warmUpDefaultDesk(displayId: Int, userId: Int)
fun createDesk(displayId: Int, userId: Int, callback: OnCreateCallback)
fun activateDesk(wct: WindowContainerTransaction, deskId: Int, skipReorder: Boolean)
fun deactivateDesk(wct: WindowContainerTransaction, deskId: Int, skipReorder: Boolean)
fun removeDesk(wct: WindowContainerTransaction, deskId: Int, userId: Int)
fun moveTaskToDesk(wct: WindowContainerTransaction, deskId: Int, task: TaskInfo)
fun minimizeTask(wct: WindowContainerTransaction, deskId: Int, task: RunningTaskInfo)
fun unminimizeTask(wct: WindowContainerTransaction, deskId: Int, task: RunningTaskInfo)
}
DesktopRepository (1,736 lines) manages the desk state with two backend implementations
selected by DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND:
SingleDesktopData— Legacy single-desk modeMultiDesktopData— Full multi-desk support
Key repository interfaces for desk change notifications:
interface DeskChangeListener {
// Notified when desks are added, removed, or active desk changes
}
interface ActiveTasksListener {
// Notified when tasks become active/inactive in desks
}
interface VisibleTasksListener {
// Notified when visible task set changes
}
Desk switching is animated by DeskSwitchTransitionHandler using the custom transit type
TRANSIT_DESKTOP_MODE_DESK_TO_DESK_SWITCH, with slide animations managed by
DeskSwitchAnimationUtils and wallpaper crossfade by DeskWallpaperAnimator.
71.5 Minimize Support
Task minimization in desktop mode sends windows to a hidden state while keeping them in
the desk’s minimizedTasks set. The implementation spans several components:
DesktopTasksController.minimizeTask(taskInfo, minimizeReason)— Entry point that updatesDesktopRepositorystate and starts aTRANSIT_MINIMIZE (Transitions.TRANSIT_FIRST_CUSTOM + 20)transitionDesktopMinimizationTransitionHandler— Handles the minimize animation, playing a scale-down + fade-out effect usingMinimizeAnimator.create(). This handler only handles animation — it does not initiate transitions (itshandleRequest()returnsnull)DesksOrganizer.minimizeTask()/unminimizeTask()— Manages the desk-level container reordering for minimized tasksDesktopWindowLimitRemoteHandler— Handles theTRANSIT_DESKTOP_MODE_TASK_LIMIT_MINIMIZEtransition type, which auto-minimizes the least-recently-used task when the visible task limit is reached (DesktopTasksLimiter)
The Desk data class tracks:
minimizedTasks: ArraySet<Int>— Task IDs currently minimizedboundsBeforeMinimizeByTaskId: SparseArray<Rect>(in repository) — Saved bounds for restore
71.6 Transition Handlers
Desktop mode uses a rich set of specialized Transitions.TransitionHandler implementations,
each responsible for a specific transition type defined in DesktopModeTransitionTypes:
| Transit Type Constant | Value Offset | Purpose |
|---|---|---|
TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON |
+1 | Menu-triggered enter |
TRANSIT_ENTER_DESKTOP_FROM_OVERVIEW_TASK_MENU |
+2 | Overview/Recents enter |
TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT |
+3 | Keyboard shortcut enter |
TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN |
+4 | Generic enter |
TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG |
+5 | Drag-to-fullscreen exit |
TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON |
+6 | Menu-triggered exit |
TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT |
+7 | Keyboard shortcut exit |
TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN |
+8 | Generic exit |
TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP |
+9 | Begin drag-to-desktop |
TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP |
+10 | Finalize drag-to-desktop |
TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP |
+11 | Cancel drag-to-desktop |
TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE |
+12 | Toggle maximize/restore |
TRANSIT_DESKTOP_MODE_TASK_LIMIT_MINIMIZE |
+13 | Auto-minimize excess task |
TRANSIT_DESKTOP_MODE_DESK_TO_DESK_SWITCH |
+14 | Virtual desk switch |
EnterDesktopTaskTransitionHandler (202 lines) — Handles transitions entering desktop mode.
Uses EnterDesktopTaskAnimator for the bounds animation (fullscreen → freeform window). Tracks
pending transitions and integrates with InteractionJankMonitor for performance tracking via
CUJ CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU.
ExitDesktopTaskTransitionHandler (199 lines) — Handles exit from desktop back to fullscreen.
Applies DesktopToFullscreenTaskAnimator for the reverse bounds animation.
DragToDesktopTransitionHandler (1,606 lines) — The most complex handler, managing the three-phase drag-to-desktop gesture:
TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP— User starts dragging from the app handle; task scales down usingMoveToDesktopAnimatorwithDRAG_FREEFORM_SCALETRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP— User releases in the desktop area; task finalizes position with spring physics (SpringForce,PhysicsAnimator)TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP— User drags back to status bar area; task returns to fullscreen withReturnToDragStartAnimator
This handler integrates with SplitScreenController for drag-from-split scenarios
(CUJ CUJ_DESKTOP_MODE_MOVE_FROM_SPLIT_SCREEN) and BubbleController for bubble conversion
(TRANSIT_CONVERT_TO_BUBBLE).
stateDiagram-v2
[*] --> Fullscreen
Fullscreen --> DragStarted : User drags app handle
DragStarted --> DragMoving : TRANSIT_START_DRAG_TO_DESKTOP
DragMoving --> DesktopMode : Release in desktop area<br/>TRANSIT_END_DRAG_TO_DESKTOP
DragMoving --> Fullscreen : Return to status bar<br/>TRANSIT_CANCEL_DRAG_TO_DESKTOP
DesktopMode --> Fullscreen : Exit via menu/shortcut<br/>TRANSIT_EXIT_DESKTOP_MODE_*
DesktopMode --> Resized : Toggle maximize<br/>TRANSIT_TOGGLE_RESIZE
Resized --> DesktopMode : Toggle restore
DesktopMode --> Minimized : Minimize button/shortcut
Minimized --> DesktopMode : Restore from taskbar
DesktopMode --> DeskSwitch : TRANSIT_DESK_TO_DESK_SWITCH
DeskSwitch --> DesktopMode : New desk active
71.7 Keyboard Shortcuts Integration
DesktopModeKeyGestureHandler (377 lines) implements InputManager.KeyGestureEventHandler
and registers for system-level keyboard shortcuts. The handler connects desktop mode
operations to keyboard gestures via the InputManager API.
Registered gesture types:
val supportedGestures = listOf(
KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY,
KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW,
KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW,
KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW,
KeyGestureEvent.KEY_GESTURE_TYPE_SWITCH_TO_PREVIOUS_DESK,
KeyGestureEvent.KEY_GESTURE_TYPE_SWITCH_TO_NEXT_DESK,
KeyGestureEvent.KEY_GESTURE_TYPE_QUIT_FOCUSED_DESKTOP_TASK,
KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_FULLSCREEN,
)
The handleKeyGestureEvent() method dispatches each gesture:
| Gesture | Action |
|---|---|
MOVE_TO_NEXT_DISPLAY |
moveToNextDesktopDisplay() |
SNAP_LEFT_FREEFORM_WINDOW |
snapToHalfScreen(SPLIT_POSITION_TOP_OR_LEFT) |
SNAP_RIGHT_FREEFORM_WINDOW |
snapToHalfScreen(SPLIT_POSITION_BOTTOM_OR_RIGHT) |
TOGGLE_MAXIMIZE_FREEFORM_WINDOW |
toggleDesktopTaskSize() |
MINIMIZE_FREEFORM_WINDOW |
minimizeTask() |
SWITCH_TO_PREVIOUS_DESK |
activatePreviousDesk() |
SWITCH_TO_NEXT_DESK |
activateNextDesk() |
QUIT_FOCUSED_DESKTOP_TASK |
Close focused task |
TOGGLE_FULLSCREEN |
toggleFocusedTaskFullscreenState() |
All gesture callbacks dispatch to the mainExecutor to ensure operations run on the
Shell main thread. The handler takes Optional<DesktopTasksController> and
Optional<DesktopModeWindowDecorViewModel> — it only registers shortcuts when both are present.
graph LR
subgraph "Keyboard Shortcut Flow"
IM[InputManager] -->|KeyGestureEvent| KGH[DesktopModeKeyGestureHandler]
KGH -->|SNAP_LEFT| DTC1[DesktopTasksController<br/>.snapToHalfScreen]
KGH -->|TOGGLE_MAXIMIZE| DTC2[DesktopTasksController<br/>.toggleDesktopTaskSize]
KGH -->|MINIMIZE| DTC3[DesktopTasksController<br/>.minimizeTask]
KGH -->|SWITCH_DESK| DTC4[DesktopTasksController<br/>.activatePreviousDesk / nextDesk]
KGH -->|MOVE_DISPLAY| DTC5[DesktopTasksController<br/>.moveToNextDesktopDisplay]
KGH -->|QUIT_TASK| STO[ShellTaskOrganizer<br/>Close task]
KGH -->|TOGGLE_FULLSCREEN| DTC6[DesktopTasksController<br/>.toggleFullscreenState]
end
71.8 Key Source Files
| File | Path | Lines | Description |
|---|---|---|---|
| DesktopTasksController | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt |
6,953 | Central desktop mode controller |
| DesktopMode | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java |
~90 | External interface for desktop mode |
| DesktopRepository | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/data/DesktopRepository.kt |
1,736 | Desk and task state management |
| Desk | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/data/Desk.kt |
99 | Per-desk data model |
| DesksOrganizer | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt |
120 | Multi-desk interface |
| DeskSwitchTransitionHandler | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskSwitchTransitionHandler.kt |
~501 | Desk switch animations |
| DragToDesktopTransitionHandler | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt |
1,606 | Three-phase drag-to-desktop gesture |
| EnterDesktopTaskTransitionHandler | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java |
202 | Enter desktop transition animations |
| ExitDesktopTaskTransitionHandler | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java |
199 | Exit desktop transition animations |
| DesktopMinimizationTransitionHandler | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandler.kt |
~165 | Minimize animation handler |
| DesktopModeTransitionTypes | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt |
~120 | Transit type constants and helpers |
| DesktopModeKeyGestureHandler | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt |
377 | Keyboard shortcut handler |
| DesktopModeWindowDecoration | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java |
2,174 | Window chrome (caption, resize handles) |
| DesktopModeWindowDecorViewModel | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java |
2,211 | Decoration lifecycle management |
| WindowDecoration | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java |
937 | Base decoration class |
| CaptionController | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/caption/CaptionController.kt |
470 | Caption bar management |
| AppHeaderController | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/caption/AppHeaderController.kt |
776 | Full app header with buttons |
| DragResizeInputListener | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java |
777 | Edge/corner resize touch input |
| FluidResizeTaskPositioner | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java |
238 | Smooth resize positioning |
| DesktopModeVisualIndicator | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java |
~534 | Snap/drop zone visual feedback |
| ToggleResizeDesktopTaskTransitionHandler | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt |
~150 | Maximize/restore toggle animation |
| WindowDragTransitionHandler | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/WindowDragTransitionHandler.kt |
~88 | Window repositioning transition |
| ShellDesktopState | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ShellDesktopState.kt |
~27 | Shell-level desktop state |
| DesktopUserRepositories | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt |
~184 | Per-user repository management |
72. Predictive Back and Back Navigation
72.1 Architecture Overview — Shell vs WMS Split
Android’s predictive back system follows the standard Shell–WMS split architecture, with a clear separation between animation (Shell process) and navigation logic (system_server):
| Layer | Process | Responsibility |
|---|---|---|
| BackNavigationController | system_server (WMS) | Target determination, transition preparation, callback routing |
| BackAnimationController | Shell (SystemUI) | Gesture tracking, animation orchestration, transition handling |
| ShellBackAnimationRegistry | Shell (SystemUI) | Animation type → runner mapping |
| CrossTaskBackAnimation | Shell (SystemUI) | Inter-task visual animation with surface transforms |
| BackAnimationRunner | Shell (SystemUI) | Animation lifecycle, jank monitoring |
The two sides communicate through AIDL interfaces: IBackAnimation allows Launcher to
register callbacks with Shell, while BackAnimationAdapter (wrapping IBackAnimationRunner)
lets WMS deliver animation targets to Shell when a predictive back gesture begins.
graph TB
subgraph "App Process"
OBC[OnBackInvokedCallback]
OBD[OnBackInvokedDispatcher]
OBC --> OBD
end
subgraph "system_server — WMS"
BNC[BackNavigationController]
AH[AnimationHandler]
NM[NavigationMonitor]
BNC --> AH
BNC --> NM
end
subgraph "Shell Process — SystemUI"
BAC[BackAnimationController]
REG[ShellBackAnimationRegistry]
CTA[CrossTaskBackAnimation]
BAR[BackAnimationRunner]
BTH[BackTransitionHandler]
BAC --> REG
REG --> CTA
CTA --> BAR
BAC --> BTH
end
OBD -- "setOnBackInvokedCallback\n(IWindowSession)" --> BNC
BNC -- "BackAnimationAdapter\n(IBackAnimationRunner)" --> BAC
BAC -- "IOnBackInvokedCallback\n(onBackStarted/Progressed/Invoked)" --> OBC
72.2 BackNavigationController — Server-Side Target Determination
BackNavigationController (2,352 lines) is the server-side brain of back navigation. It
resides within the WMS lock scope and determines what happens when the user swipes back.
Key Fields:
| Field | Type | Purpose |
|---|---|---|
mAnimationHandler |
AnimationHandler |
Manages animation adaptor composition |
mNavigationMonitor |
NavigationMonitor |
Watches for focus/window changes during back |
mLastBackType |
@BackTargetType int |
Tracks the resolved back navigation type |
mCurrentAnimationBuilder |
ScheduleAnimationBuilder |
Pending animation configuration |
Target Determination in startBackNavigation():
The method executes under wmService.mGlobalLock and follows a strict resolution order:
- Find focused window —
wmService.getFocusedWindowLocked(), with fallback to top task - Check callback info —
window.getOnBackInvokedCallbackInfo()determines if the app registered anOnBackInvokedCallback - Resolve back type using a priority chain:
TYPE_CALLBACK— app-registered callback (no system animation)TYPE_DIALOG_CLOSE— non-base-application window (dialog/popup)TYPE_CROSS_ACTIVITY— previous activity exists in the same taskTYPE_CROSS_TASK— returning to a different taskTYPE_RETURN_TO_HOME— going back to the launcher
- Prepare animation targets via
AnimationHandler.composeAnimations()which createsBackWindowAnimationAdaptorinstances for opening and closing containers
flowchart TD
START[startBackNavigation called] --> LOCK[Acquire WMS global lock]
LOCK --> FOCUS{Find focused\nwindow}
FOCUS -->|null| FALLBACK[Use top task window]
FOCUS -->|found| CALLBACK{Has OnBackInvoked\nCallback?}
FALLBACK --> CALLBACK
CALLBACK -->|no| NULL_RET[Return null]
CALLBACK -->|yes, app callback| TYPE_CB[TYPE_CALLBACK\nNo system animation]
CALLBACK -->|yes, system| RESOLVE[Resolve navigation target]
RESOLVE --> DIALOG{Non-base window?}
DIALOG -->|yes| TYPE_DLG[TYPE_DIALOG_CLOSE]
DIALOG -->|no| PREV{Previous activity\nin same task?}
PREV -->|yes| TYPE_ACT[TYPE_CROSS_ACTIVITY]
PREV -->|no| TASK{Previous task\nexists?}
TASK -->|yes| TYPE_TASK[TYPE_CROSS_TASK]
TASK -->|no| TYPE_HOME[TYPE_RETURN_TO_HOME]
TYPE_CB --> BUILD[Build BackNavigationInfo]
TYPE_DLG --> COMPOSE[AnimationHandler.composeAnimations]
TYPE_ACT --> COMPOSE
TYPE_TASK --> COMPOSE
TYPE_HOME --> COMPOSE
COMPOSE --> BUILD
BUILD --> RETURN[Return to Shell]
AnimationHandler Inner Class:
The AnimationHandler (within BackNavigationController) distinguishes three switch types:
| Constant | Value | Description |
|---|---|---|
TASK_SWITCH |
1 | Closing and opening are different Tasks |
ACTIVITY_SWITCH |
2 | Both are Activities in the same Task |
DIALOG_CLOSE |
3 | Target is a WindowState (dialog) |
The initiate() method creates BackWindowAnimationAdaptor pairs for the closing and opening
containers, optionally promoting activities to their parent TaskFragment when embedded.
NavigationMonitor Inner Class:
Monitors focus changes during an active back gesture. If the focused window changes
unexpectedly (e.g., activity crash), it calls cancelBackNavigating() to abort the animation
and notify Shell via the RemoteCallback observer.
72.3 BackAnimationController — Shell-Side Orchestration
BackAnimationController (1,933 lines) is the Shell-side coordinator. It implements
RemoteCallable<BackAnimationController> and Transitions.TransitionHandler (via its inner
BackTransitionHandler), connecting gesture input to animation output.
Key Components:
| Component | Role |
|---|---|
mShellBackAnimationRegistry |
Maps BackTargetType → BackAnimationRunner |
mCurrentTracker / mQueuedTracker |
BackTouchTracker instances for gesture state |
mBackAnimationAdapter |
AIDL bridge wrapping IBackAnimationRunner sent to WMS |
mActiveCallback |
Currently active IOnBackInvokedCallback |
mBackTransitionHandler |
Handles Shell transitions for back navigation |
IBackAnimation AIDL Interface:
interface IBackAnimation {
void setBackToLauncherCallback(IOnBackInvokedCallback callback,
IRemoteAnimationRunner runner);
void clearBackToLauncherCallback();
void customizeStatusBarAppearance(AppearanceRegion appearance);
}
This AIDL allows the Launcher process to register its own back-to-home callback and animation runner, enabling the distinctive “shrink to icon” home animation.
Gesture → Animation Flow (onMotionEvent):
The onMotionEvent() method is the entry point for all back gesture events:
- ACTION_DOWN →
onGestureStarted()→ createsBackTouchTracker, callsstartBackNavigation()which invokesmActivityTaskManager.startBackNavigation() - ACTION_MOVE →
onMove()→ dispatchesonBackProgressed()to the active callback - ACTION_UP →
onGestureFinished()→ eitherstartPostCommitAnimation()(trigger) or dispatchesonBackCancelled()(cancel)
72.4 CrossTaskBackAnimation — Inter-Task Visual Animation
CrossTaskBackAnimation (445 lines) extends ShellBackAnimation and produces the visual
effect when navigating between different tasks. It operates in two distinct phases:
Phase 1 — Gesture Tracking:
- Tracks finger position to scale and translate both closing and entering windows
- Minimum scale:
0.8f(80% of original size) - Windows are positioned side-by-side with
mInterWindowMargingap - Vertical movement follows a
DecelerateInterpolatorfor natural feel
Phase 2 — Post-Commit Animation:
- Triggered by
onGestureCommitted()when gesture crosses threshold - Uses
SpringAnimationwith fling velocity from gesture for physics-based motion ValueAnimatorof 500ms withEMPHASIZEDinterpolator drives final positions- Entering window expands to fill screen; closing window shrinks to center
sequenceDiagram
participant User as User Gesture
participant BAC as BackAnimationController
participant CTA as CrossTaskBackAnimation
participant SC as SurfaceControl.Transaction
User->>BAC: ACTION_DOWN (edge swipe)
BAC->>BAC: startBackNavigation()
BAC-->>CTA: Runner.onAnimationStart(apps)
CTA->>CTA: startBackAnimation()
loop Phase 1: Gesture Tracking
User->>BAC: ACTION_MOVE (progress)
BAC-->>CTA: Callback.onBackProgressed(event)
CTA->>CTA: updateGestureBackProgress(progress)
CTA->>SC: setMatrix/setWindowCrop/setCornerRadius
end
User->>BAC: ACTION_UP (commit)
BAC-->>CTA: Callback.onBackInvoked()
CTA->>CTA: onGestureCommitted()
Note over CTA,SC: Phase 2: Post-Commit Animation
CTA->>CTA: SpringAnimation (fling velocity)
CTA->>CTA: ValueAnimator (500ms EMPHASIZED)
loop Post-Commit Frames
CTA->>CTA: updatePostCommitEnteringAnimation()
CTA->>CTA: updatePostCommitClosingAnimation()
CTA->>SC: Apply transform matrices
end
CTA->>CTA: finishAnimation()
CTA->>BAC: onAnimationFinished callback
Surface Transform Application:
Each frame, applyTransform() computes a transformation matrix:
scale = targetRect.width / startTaskRect.width
matrix = Scale(scale, scale) * Translate(left, top)
transaction.setMatrix(leash, matrix).setWindowCrop(leash, startRect).setCornerRadius(leash, r)
The transaction is committed with setFrameTimelineVsync() for proper frame pacing.
72.5 Predictive Back Gesture Flow
The complete flow from touch to navigation outcome involves coordination across three processes:
sequenceDiagram
participant Input as InputDispatcher
participant Nav as NavigationBarView
participant Shell as BackAnimationController
participant WMS as BackNavigationController
participant App as App Callback
Note over Input,App: 1. Gesture Initiation
Input->>Nav: Edge swipe detected
Nav->>Shell: onBackMotion(x, y, ACTION_DOWN, edge)
Shell->>Shell: onGestureStarted() → BackTouchTracker.ACTIVE
Shell->>WMS: startBackNavigation(observer, adapter)
WMS->>WMS: Resolve target type & compose animations
WMS-->>Shell: BackNavigationInfo{type, callback, targets}
Note over Input,App: 2. Threshold Crossing
Nav->>Shell: onThresholdCrossed()
Shell->>Shell: tryPilferPointers() — claim gesture
Shell->>Shell: ShellBackAnimationRegistry.startGesture(type)
Note over Input,App: 3. Progress Updates
loop Gesture Movement
Nav->>Shell: onBackMotion(x, y, ACTION_MOVE, edge)
Shell->>Shell: dispatchOnBackProgressed(callback, event)
Shell->>App: IOnBackInvokedCallback.onBackProgressed(BackMotionEvent)
end
Note over Input,App: 4a. Commit Path
Nav->>Shell: setTriggerBack(true)
Nav->>Shell: onBackMotion(x, y, ACTION_UP, edge)
Shell->>Shell: onGestureFinished() → startPostCommitAnimation()
Shell->>App: IOnBackInvokedCallback.onBackInvoked()
App-->>Shell: Animation finishes
Shell->>WMS: IBackAnimationFinishedCallback(triggerBack=true)
Note over Input,App: 4b. Cancel Path
Nav->>Shell: setTriggerBack(false)
Nav->>Shell: onBackMotion(x, y, ACTION_UP, edge)
Shell->>App: IOnBackInvokedCallback.onBackCancelled()
Shell->>WMS: IBackAnimationFinishedCallback(triggerBack=false)
BackTouchTracker States:
| State | Description |
|---|---|
INITIAL |
No gesture in progress |
ACTIVE |
Gesture started, tracking motion |
FINISHED |
Gesture released, awaiting animation completion |
The dual-tracker design (mCurrentTracker + mQueuedTracker) allows a new gesture to
be queued while the previous one’s post-commit animation is still running.
72.6 Custom App Back Animations via OnBackInvokedCallback
Apps opt into predictive back by registering an OnBackInvokedCallback through
OnBackInvokedDispatcher:
Activity/Dialog → getOnBackInvokedDispatcher() → OnBackInvokedDispatcher
→ registerOnBackInvokedCallback(priority, callback)
→ IWindowSession.setOnBackInvokedCallbackInfo(window, callbackInfo)
→ WindowState stores OnBackInvokedCallbackInfo
Callback Priority Levels:
| Priority | Constant | Meaning |
|---|---|---|
| 0 | PRIORITY_DEFAULT |
Standard back handling |
| 1 | PRIORITY_OVERLAY |
Higher priority (e.g., drawer close) |
| -1 | PRIORITY_SYSTEM_NAVIGATION_OBSERVER |
System observer only |
When BackNavigationController.startBackNavigation() finds a non-system callback, it returns
TYPE_CALLBACK with the callback reference. Shell then bypasses system animations entirely
and dispatches onBackStarted(), onBackProgressed(), and onBackInvoked()/onBackCancelled()
directly to the app’s callback.
The isAnimationCallback() flag controls whether the app receives progress events for
custom animation. When set, the app can animate its own views in response to
onBackProgressed(BackMotionEvent), using getProgress() (0.0–1.0) and touch coordinates.
72.7 Integration with Transition System and Input System
Transition System Integration:
The BackTransitionHandler inner class of BackAnimationController implements
Transitions.TransitionHandler to manage two key transition types:
| Transition Type | Purpose |
|---|---|
TRANSIT_PREPARE_BACK_NAVIGATION |
Makes the behind-activity visible for animation |
TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION |
Restores visibility if animation is cancelled |
On the server side, BackNavigationController.AnimationHandler.initiate() calls
prepareTransitionIfNeeded() which starts a TRANSIT_PREPARE_BACK_NAVIGATION transition.
This makes the opening (behind) activity visible and ready for animation without actually
committing the navigation.
If the user cancels the gesture, BackTransitionHandler.createClosePrepareTransition() starts
a TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION to hide the activity again.
When the gesture commits, BackTransitionHandler can hand off to another
TransitionHandler via takeOverAnimation(), passing WindowAnimationState data
so the receiving handler can seamlessly continue the animation.
Input System Integration:
BackAnimationControllerregisters as aKeyGestureEventHandlerviaInputManager.registerKeyGestureEventHandler()forKEY_GESTURE_TYPE_BACK- This enables 3-button navigation predictive back: keyboard/button back events are intercepted and routed through the same animation pipeline
- The
setSwipeThresholds()method configures the non-linear progress mapping based on screen width, ensuring consistent behavior across device sizes tryPilferPointers()invokes a pilfer callback to claim the gesture from the input dispatcher, preventing other consumers from receiving the touch events
72.8 BackAnimationRunner and Animation Lifecycle
BackAnimationRunner (~220 lines) wraps an IOnBackInvokedCallback and
IRemoteAnimationRunner pair, managing the lifecycle of a single back animation instance.
Lifecycle States:
| State | Field | Meaning |
|---|---|---|
| Waiting | mWaitingAnimation = true |
Gesture started, awaiting onAnimationStart from WMS |
| Active | mWaitingAnimation = false |
Animation targets delivered, animation running |
| Cancelled | mAnimationCancelled = true |
Animation cancelled before targets arrived |
startAnimation() Flow:
When WMS delivers RemoteAnimationTarget[] arrays (via IBackAnimationRunner.onAnimationStart),
BackAnimationRunner.startAnimation():
- Creates a
RemoteAnimationFinishedStubweak-reference callback to avoid leaks - Starts jank monitoring via
InteractionJankMonitor.begin()using the CUJ type (e.g.,CUJ_PREDICTIVE_BACK_CROSS_TASKorCUJ_PREDICTIVE_BACK_HOME) - Delegates to the
IRemoteAnimationRunner.onAnimationStart()of the concrete animation - On completion,
onAnimationFinished()posts to the Shell main thread, ends jank monitoring, releases all surface leashes, and invokes the finished callback
The RemoteAnimationFinishedStub uses a WeakReference<BackAnimationRunner> to prevent the
Binder callback from holding the runner in memory if the animation is abandoned.
Registry Initialization in ShellBackAnimationRegistry:
The registry maps each BackNavigationInfo.BackTargetType to a runner at construction time:
TYPE_CROSS_ACTIVITY → CrossActivityBackAnimation.getRunner()
TYPE_CROSS_TASK → CrossTaskBackAnimation.getRunner()
TYPE_DIALOG_CLOSE → DialogCloseAnimation.getRunner()
TYPE_RETURN_TO_HOME → (set by Launcher via IBackAnimation.setBackToLauncherCallback)
When a gesture starts, getAnimationRunnerAndInit() checks if a customized cross-activity
animation is available (via mCustomizeActivityAnimation.prepareNextAnimation()), falling
back to the default if not. This allows apps to specify custom enter/exit animations
through overridePendingTransition() that are respected during predictive back.
72.9 Key Source Files
| File | Path | Lines | Description |
|---|---|---|---|
| BackNavigationController | frameworks/base/services/core/java/com/android/server/wm/BackNavigationController.java |
2,352 | Server-side target resolution, AnimationHandler, NavigationMonitor |
| BackAnimationController | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java |
1,933 | Shell-side gesture orchestration, BackTransitionHandler |
| CrossTaskBackAnimation | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java |
445 | Inter-task two-phase visual animation |
| BackAnimationRunner | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java |
~220 | Animation lifecycle wrapper with jank monitoring |
| ShellBackAnimationRegistry | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java |
~190 | Type → runner mapping, customization support |
| ShellBackAnimation | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java |
~55 | Abstract base for all back animation types |
| BackAnimation | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java |
~105 | External interface for gesture event delivery |
| IBackAnimation.aidl | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/back/IBackAnimation.aidl |
~50 | AIDL for Launcher ↔ Shell communication |
| BackAnimationBackground | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java |
— | Background surface behind animating windows |
| BackAnimationConstants | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationConstants.java |
— | Shared animation threshold constants |
| StatusBarCustomizer | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/back/StatusBarCustomizer.java |
— | Status bar appearance during back animation |
Part XV: Client-Side Architecture
73. ViewRootImpl and View-to-WMS Bridge
73.1 ViewRootImpl as the App↔WMS Bridge
ViewRootImpl (13,827 lines) is the single most important class connecting the Android
application-side view hierarchy to the Window Manager Service. Every window in an Android
app — whether an Activity, Dialog, or system window — has exactly one ViewRootImpl that
serves as the bridge between the View tree and WMS.
Class Identity:
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
AttachedSurfaceControl {...}
Critical Fields:
| Field | Type | Purpose |
|---|---|---|
mWindowSession |
IWindowSession |
Per-app Binder to WMS for window operations |
mWindow |
W (IWindow.Stub) |
Reverse callback channel from WMS to app |
mSurface |
Surface |
Active drawing surface |
mSurfaceControl |
SurfaceControl |
Handle to the compositor surface |
mView |
View |
Root of the view hierarchy (typically DecorView) |
mAttachInfo |
View.AttachInfo |
Shared state propagated to all views |
mChoreographer |
Choreographer |
Frame scheduling tied to VSYNC |
mAttachInfo.mThreadedRenderer |
ThreadedRenderer |
Hardware-accelerated rendering engine |
IWindowSession — The Per-App WMS Channel:
IWindowSession is obtained once per process via WindowManagerGlobal.getWindowSession()
and represents the app’s authenticated connection to WMS. Key methods include:
| Method | Direction | Purpose |
|---|---|---|
addToDisplayAsUser() |
App → WMS | Register window, obtain InputChannel |
relayout() |
App → WMS | Request size/visibility change, get SurfaceControl |
relayoutAsync() |
App → WMS (oneway) | Async relayout when no sync needed |
finishDrawing() |
App → WMS | Signal first frame drawn |
setOnBackInvokedCallbackInfo() |
App → WMS | Register predictive back callback |
setInsets() |
App → WMS | Report touchable/content insets |
graph LR
subgraph "App Process"
VRI[ViewRootImpl]
DV[DecorView]
VH[View Hierarchy]
TR[ThreadedRenderer]
CH[Choreographer]
WIR[WindowInputEventReceiver]
DV --> VH
VRI --> DV
VRI --> TR
VRI --> CH
VRI --> WIR
end
subgraph "system_server"
WMS[WindowManagerService]
WS[WindowState]
SC[SurfaceControl / SurfaceFlinger]
ID[InputDispatcher]
end
VRI -- "IWindowSession\n(addToDisplay, relayout,\nfinishDrawing)" --> WMS
WMS -- "IWindow (W)\n(resized, insetsChanged)" --> VRI
ID -- "InputChannel" --> WIR
TR -- "SurfaceControl.Transaction\n(buffer submit)" --> SC
WMS --> WS
WS --> SC
73.2 The Relayout Protocol — SurfaceControl Acquisition
The relayoutWindow() method (starting at line 9615) is how ViewRootImpl negotiates
window geometry and obtains its drawing surface from WMS.
Sync vs Async Relayout Decision:
ViewRootImpl intelligently chooses between synchronous and asynchronous relayout:
- Async (
relayoutAsync): Used when frame computation shows only position OR size changed (not both), and no visibility change or sync sequence is pending. This is a fire-and-forget oneway Binder call. - Sync (
relayout): Required when both position and size change simultaneously (triggers BLAST sync), or on visibility changes, or whenmSyncSeqIdhasn’t caught up. Returns aSurfaceControland updated frame information.
Relayout Call Flow:
sequenceDiagram
participant VRI as ViewRootImpl
participant WS as IWindowSession
participant WMS as WindowManagerService
participant SF as SurfaceFlinger
VRI->>VRI: performTraversals() detects change
VRI->>VRI: relayoutWindow(params, visibility)
alt Async Path (position-only or size-only)
VRI->>WS: relayoutAsync(window, attrs, w, h, vis, flags, seq)
Note right of WS: Oneway — no return value
else Sync Path (both changed or first layout)
VRI->>WS: relayout(window, attrs, w, h, vis, flags, seq, ...)
WS->>WMS: Compute new frames
WMS->>SF: Create/resize SurfaceControl
WS-->>VRI: result, outSurfaceControl, frames, insets
VRI->>VRI: mSurface.copyFrom(mSurfaceControl)
VRI->>VRI: Update ThreadedRenderer surface
end
VRI->>VRI: Continue to measure → layout → draw
After relayout returns, ViewRootImpl:
- Updates
mWinFrameInScreenwith the new frame bounds - Copies the
SurfaceControlto the localmSurfacefor drawing - Notifies
ThreadedRendererof the new surface viasetSurfaceControl() - Processes returned
InsetsStatefor keyboard/status-bar insets
73.3 Choreographer Integration and Frame Scheduling
Choreographer (1,714 lines) drives Android’s frame scheduling loop, tying all UI work
to VSYNC signals from the display subsystem.
Callback Types (executed in order per frame):
| Constant | Value | Purpose |
|---|---|---|
CALLBACK_INPUT |
0 | Input event processing |
CALLBACK_ANIMATION |
1 | Property animations, ValueAnimator |
CALLBACK_INSETS_ANIMATION |
2 | Insets animation updates |
CALLBACK_TRAVERSAL |
3 | View measure/layout/draw |
CALLBACK_COMMIT |
4 | Post-draw frame commit bookkeeping |
ViewRootImpl’s Choreographer Usage:
scheduleTraversals() is the critical entry point that connects invalidation to rendering:
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mQueue.postSyncBarrier(); // Block sync messages
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, // Execute at traversal phase
mTraversalRunnable, null); // → doTraversal() → performTraversals()
notifyRendererOfFramePending();
}
}
The sync barrier is critical: it blocks all synchronous Handler messages until the
traversal completes, ensuring that layout changes requested via View.post() after a
requestLayout() see the updated measurements (as documented in the code comments).
flowchart TD
VSYNC[VSYNC Signal] --> DF[Choreographer.doFrame]
DF --> CB_INPUT[CALLBACK_INPUT\nProcess input events]
CB_INPUT --> CB_ANIM[CALLBACK_ANIMATION\nRun ValueAnimators]
CB_ANIM --> CB_INSETS[CALLBACK_INSETS_ANIMATION\nInsets transitions]
CB_INSETS --> CB_TRAV[CALLBACK_TRAVERSAL\nViewRootImpl.doTraversal]
CB_TRAV --> PT[performTraversals]
PT --> MEASURE[performMeasure]
MEASURE --> LAYOUT[performLayout]
LAYOUT --> DRAW[draw → ThreadedRenderer]
DRAW --> CB_COMMIT[CALLBACK_COMMIT\nFrame bookkeeping]
CB_COMMIT --> NEXT[Wait for next VSYNC]
NEXT -.-> VSYNC
73.4 Input Event Dispatch — The InputStage Chain
Input events flow from the system’s InputDispatcher through a Binder-backed
InputChannel into ViewRootImpl’s WindowInputEventReceiver, then through a chain of
InputStage processors.
InputStage Chain Construction (line 1769):
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage, ...);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage, ...);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage, ...);
Stage Pipeline (first → last):
| Stage | Type | Responsibility |
|---|---|---|
NativePreImeInputStage |
Async | Native activity input handling (pre-IME) |
ViewPreImeInputStage |
Sync | View.dispatchKeyEventPreIme() — app pre-IME hook |
ImeInputStage |
Async | Routes to InputMethodManager for IME processing |
EarlyPostImeInputStage |
Sync | System shortcuts, accessibility, trackball → arrow keys |
NativePostImeInputStage |
Async | Native activity input handling (post-IME) |
ViewPostImeInputStage |
Sync | Main dispatch: mView.dispatchKeyEvent(), dispatchPointerEvent() |
SyntheticInputStage |
Sync | Generates synthetic events (trackball → key, joystick → touch) |
InputStage Base Class Contract:
abstract class InputStage {
private final InputStage mNext;
protected static final int FORWARD = 0;
protected static final int FINISH_HANDLED = 1;
protected static final int FINISH_NOT_HANDLED = 2;
public final void deliver(QueuedInputEvent q) {
if (finished) forward(q);
else if (shouldDrop) finish(q, false);
else { result = onProcess(q); apply(q, result); }
}
}
Each stage calls onProcess() and returns FORWARD (pass to next), FINISH_HANDLED
(consumed), or FINISH_NOT_HANDLED (not consumed but stop). The AsyncInputStage
subclass adds the ability to defer processing while waiting for system server responses.
flowchart LR
IC[InputChannel\nfrom WMS] --> WIR[WindowInput\nEventReceiver]
WIR --> Q[QueuedInputEvent]
Q --> NPI[NativePreIme\nInputStage]
NPI --> VPI[ViewPreIme\nInputStage]
VPI --> IME[ImeInput\nStage]
IME --> EPI[EarlyPostIme\nInputStage]
EPI --> NP[NativePostIme\nInputStage]
NP --> VP[ViewPostIme\nInputStage]
VP --> SYN[Synthetic\nInputStage]
VP -.->|dispatchKeyEvent\ndispatchPointerEvent| VIEW[View Hierarchy\ntouch/key dispatch]
73.5 Hardware Rendering — ThreadedRenderer and RenderThread
Modern Android uses hardware-accelerated rendering where the UI thread records drawing
commands into RenderNode objects, and a separate RenderThread replays them via the GPU.
Rendering Path in draw():
The draw() method (line 5767) checks isHardwareEnabled():
- Hardware path:
mAttachInfo.mThreadedRenderer.draw(mView, ...)— records theRenderNodetree and enqueues work forRenderThread - Software fallback:
drawSoftware()— locks theSurfacecanvas and draws directly
ThreadedRenderer Integration:
| Operation | Thread | Description |
|---|---|---|
renderer.draw(view, ...) |
UI thread | Walks View tree, updates RenderNodes |
| RenderNode recording | UI thread | Each View’s draw() records to its RenderNode |
| Frame submission | RenderThread | GPU commands execute, buffer queued to SurfaceFlinger |
FrameDrawingCallback |
RenderThread | Notifies completion for sync operations |
The UI thread is freed as soon as RenderNode recording completes. RenderThread asynchronously executes the GPU work and submits the buffer, enabling the UI thread to process the next frame’s input and animations without waiting for GPU completion.
Frame Completion and BLAST Sync:
After ThreadedRenderer completes a frame, BLASTBufferQueue manages the buffer submission
to SurfaceFlinger. When a synchronized resize is required (mSyncSeqId), ViewRootImpl uses
SurfaceSyncGroup to ensure the buffer and window geometry update arrive atomically at
SurfaceFlinger.
73.6 SurfaceView — Sub-Surface Creation and Off-Thread Rendering
SurfaceView (2,510 lines) provides a dedicated compositing surface within the view
hierarchy, enabling off-thread rendering for video, camera, games, and other content
that cannot be rendered on the UI thread’s schedule.
Architecture:
graph TB
subgraph "App Window Surface (ViewRootImpl)"
DV[DecorView]
VH[View Hierarchy]
SV[SurfaceView\nTransparent hole]
end
subgraph "SurfaceFlinger Composition"
WS[Window Surface Layer]
SVS[SurfaceView Surface Layer\nChild of Window]
WP[Wallpaper Layer]
end
subgraph "App Rendering Threads"
RT[RenderThread\nUI content]
GT[Game/Video Thread\nSurfaceView content]
end
DV --> VH
VH --> SV
RT --> WS
GT --> SVS
WS --> |"Parent"| SVS
SVS --> |"Behind by default\nmSubLayer < 0"| WP
style SV fill:#ff9,stroke:#333
style SVS fill:#9f9,stroke:#333
Key Implementation Details:
| Field | Type | Purpose |
|---|---|---|
mSurfaceControl |
SurfaceControl |
Child surface reparented under window surface |
mBlastBufferQueue |
BLASTBufferQueue |
Buffer queue for BLAST mode rendering |
mSubLayer |
int |
Z-order relative to parent (negative = behind) |
mSurfaceHolder |
SurfaceHolder |
Public API for locking canvas / callbacks |
updateSurface() Method (line 1265):
Called during ViewRootImpl’s traversal cycle, this method:
- Checks for changes — size, format, visibility, position, alpha, z-order
- Creates surface controls if needed via
createBlastSurfaceControls() - Applies geometry through
performSurfaceTransaction()— sets position, size, crop, corner radius, z-order, and buffer transform hint - Manages BLASTBufferQueue — creates or updates the buffer queue for BLAST mode
- Fires SurfaceHolder callbacks —
surfaceCreated(),surfaceChanged(),surfaceDestroyed()to notify app code
BLAST Mode:
All SurfaceViews use BLAST (Buffer Lifecycle Across Surface and Thread) mode:
BLASTBufferQueuereplaces the legacy buffer queue path- Buffers are submitted via
SurfaceControl.Transactionfor atomic composition syncNextTransaction()enables synchronized buffer delivery with parent surface updates
Off-Thread Rendering Model:
Unlike regular Views that render on the UI thread, SurfaceView’s Surface can be drawn
to from any thread. The app obtains a Canvas via SurfaceHolder.lockCanvas() (software)
or uses OpenGL/Vulkan directly with the Surface’s native handle. This rendering is
completely decoupled from the Choreographer/VSYNC cycle of the parent window.
73.7 DecorView Hosting and Window Attribute Management
DecorView (in com.android.internal.policy) is the root view of every Activity window.
ViewRootImpl hosts it as mView and manages its lifecycle:
Window Setup Flow:
Activity.setContentView()→PhoneWindow.setContentView()→ creates DecorViewWindowManagerImpl.addView(decorView, layoutParams)→ creates ViewRootImplViewRootImpl.setView(decorView, attrs, ...):- Stores
mView = decorView - Calls
requestLayout()to trigger first traversal - Creates
InputChannelfor input delivery - Calls
mWindowSession.addToDisplayAsUser()→ WMS createsWindowState - Registers back callback via
registerBackCallbackOnWindow()
- Stores
Window Attribute Management:
ViewRootImpl manages WindowManager.LayoutParams (mWindowAttributes) and propagates
changes to WMS during relayout. Key attributes include:
| Attribute | Effect |
|---|---|
flags (FLAG_KEEP_SCREEN_ON, etc.) |
Window behavior flags |
softInputMode |
IME interaction mode |
systemUiVisibility |
Status/navigation bar visibility |
surfaceInsets |
Extra surface padding for shadows |
type |
Window type (application, dialog, system) |
The collectViewAttributes() method aggregates flags from the view hierarchy (e.g.,
KEEP_SCREEN_ON) before each relayout.
73.8 Measure → Layout → Draw Pipeline Connection to Window System
performTraversals() is the central method (~1,061 lines) that drives the complete
rendering pipeline. It executes three phases in sequence:
flowchart TD
START[performTraversals called by Choreographer] --> CHECK{First traversal\nor window changed?}
CHECK -->|yes| RELAYOUT[relayoutWindow\nNegotiate size with WMS\nObtain SurfaceControl]
CHECK -->|no changes| MEASURE_ONLY[Skip relayout]
RELAYOUT --> SURFACE{New surface?}
SURFACE -->|yes| HWSETUP[Initialize ThreadedRenderer\nwith new surface]
SURFACE -->|no| MEASURE
HWSETUP --> MEASURE
MEASURE_ONLY --> MEASURE
subgraph "Phase 1: Measure"
MEASURE[performMeasure\nchildWidthMeasureSpec\nchildHeightMeasureSpec]
MEASURE --> VIEW_MEASURE[mView.measure\nRecursive measure of\nentire view hierarchy]
end
VIEW_MEASURE --> LAYOUT_CHECK{Layout needed?}
LAYOUT_CHECK -->|yes| LAYOUT_PHASE
LAYOUT_CHECK -->|no| DRAW_CHECK
subgraph "Phase 2: Layout"
LAYOUT_PHASE[performLayout]
LAYOUT_PHASE --> VIEW_LAYOUT[host.layout\n0, 0, measuredWidth, measuredHeight]
VIEW_LAYOUT --> RECHECK{Layout requesters\nduring layout?}
RECHECK -->|yes| SECOND_PASS[Second measure + layout pass]
RECHECK -->|no| LAYOUT_DONE[Layout complete]
end
SECOND_PASS --> LAYOUT_DONE
LAYOUT_DONE --> DRAW_CHECK{Draw needed?}
DRAW_CHECK -->|yes| DRAW_PHASE
DRAW_CHECK -->|no| DONE
subgraph "Phase 3: Draw"
DRAW_PHASE[draw]
DRAW_PHASE --> HW_CHECK{Hardware\naccelerated?}
HW_CHECK -->|yes| HW_DRAW[ThreadedRenderer.draw\nRecord RenderNodes\nSubmit to RenderThread]
HW_CHECK -->|no| SW_DRAW[drawSoftware\nLock canvas → draw → unlock]
end
HW_DRAW --> FINISH[finishDrawing → WMS]
SW_DRAW --> FINISH
FINISH --> DONE[Frame complete]
Phase 1 — Measure:
performMeasure() calls mView.measure(widthSpec, heightSpec). The MeasureSpec encodes the
window’s available size (from relayout) as constraints. Each view in the hierarchy receives
a measure pass, storing results in mMeasuredWidth/mMeasuredHeight.
Phase 2 — Layout:
performLayout() calls host.layout(0, 0, measuredWidth, measuredHeight). Each ViewGroup
positions its children using their measured sizes. If any view calls requestLayout()
during this phase, ViewRootImpl detects this and performs a second measure+layout pass.
Phase 3 — Draw:
The draw() method computes the dirty region and delegates to either:
- Hardware:
ThreadedRenderer.draw()records the RenderNode tree and enqueues to RenderThread. The UI thread is immediately freed. - Software:
drawSoftware()locks the Surface canvas, callsmView.draw(canvas), and unlocks.
After the first successful draw, ViewRootImpl calls mWindowSession.finishDrawing() to
signal WMS that content is ready for display. This triggers SurfaceFlinger to start
compositing the window.
View.invalidate() → draw:
View.invalidate() → ViewParent.invalidateChild() → ... → ViewRootImpl.invalidate()
→ scheduleTraversals() → Choreographer CALLBACK_TRAVERSAL → performTraversals()
→ performDraw() → draw()
View.requestLayout() → measure + layout:
View.requestLayout() → ViewParent.requestLayout() → ... → ViewRootImpl.requestLayout()
→ scheduleTraversals() → Choreographer CALLBACK_TRAVERSAL → performTraversals()
→ performMeasure() → performLayout() → performDraw()
73.9 Key Source Files
| File | Path | Lines | Description |
|---|---|---|---|
| ViewRootImpl | frameworks/base/core/java/android/view/ViewRootImpl.java |
13,827 | Central app↔WMS bridge, traversal engine, input dispatch |
| IWindowSession.aidl | frameworks/base/core/java/android/view/IWindowSession.aidl |
377 | Per-app Binder to WMS: relayout, addToDisplay, finishDrawing |
| IWindow.aidl | frameworks/base/core/java/android/view/IWindow.aidl |
— | WMS → app callback: resized, insetsChanged |
| View | frameworks/base/core/java/android/view/View.java |
~33,000 | Base UI component: measure/layout/draw, input dispatch |
| SurfaceView | frameworks/base/core/java/android/view/SurfaceView.java |
2,510 | Sub-surface for off-thread rendering, BLASTBufferQueue |
| Choreographer | frameworks/base/core/java/android/view/Choreographer.java |
1,714 | VSYNC-driven frame scheduling, callback ordering |
| ThreadedRenderer | frameworks/base/core/java/android/view/ThreadedRenderer.java |
— | Hardware rendering bridge to RenderThread |
| HardwareRenderer | frameworks/base/graphics/java/android/graphics/HardwareRenderer.java |
— | Low-level GPU rendering API |
| Surface | frameworks/base/core/java/android/view/Surface.java |
— | Drawing target wrapping native ANativeWindow |
| SurfaceControl | frameworks/base/core/java/android/view/SurfaceControl.java |
— | Compositor surface handle and Transaction API |
| DecorView | frameworks/base/core/java/com/android/internal/policy/DecorView.java |
— | Activity root view, window attribute management |
| WindowManagerGlobal | frameworks/base/core/java/android/view/WindowManagerGlobal.java |
— | Process-singleton managing ViewRootImpl instances |
| BLASTBufferQueue | frameworks/base/graphics/java/android/graphics/BLASTBufferQueue.java |
— | Buffer lifecycle management for BLAST sync |
Part XVI: System Configuration and Multi-User
74. Configuration Change Propagation
74.1 Overview
Android’s window manager propagates Configuration objects through a strict parent→child
hierarchy. Every node in the tree—from the root all the way down to each
ActivityRecord—inherits its parent’s configuration, applies local overrides, and
forwards the merged result to its children. The base machinery lives in
ConfigurationContainer, the abstract class that underpins the entire WM container
hierarchy.
RootWindowContainer
└─ DisplayContent
└─ TaskDisplayArea
└─ Task
└─ ActivityRecord
When any configuration attribute changes (rotation, density, locale, dark-mode, font
scale, display metrics), the change enters the tree at the appropriate level and
cascades downward, eventually reaching every affected ActivityRecord which must then
decide: deliver via callback or relaunch.
74.2 ConfigurationContainer — The Base Class
ConfigurationContainer<E extends ConfigurationContainer> (1,007 lines) is the
generic base that every WM container extends, either directly or through its
subclass WindowContainer.
classDiagram
class ConfigurationContainer~E~ {
-Configuration mRequestedOverrideConfiguration
-Configuration mResolvedOverrideConfiguration
-Configuration mFullConfiguration
-Configuration mMergedOverrideConfiguration
+getConfiguration() Configuration
+onConfigurationChanged(Configuration newParentConfig)
+resolveOverrideConfiguration(Configuration newParentConfig)
+onMergedOverrideConfigurationChanged()
+onRequestedOverrideConfigurationChanged(Configuration)
+getRequestedOverrideConfiguration() Configuration
+getResolvedOverrideConfiguration() Configuration
+getMergedOverrideConfiguration() Configuration
}
class WindowContainer~E~ {
+onConfigurationChanged(Configuration)
}
class DisplayContent
class TaskDisplayArea
class Task
class ActivityRecord
ConfigurationContainer <|-- WindowContainer
WindowContainer <|-- DisplayContent
WindowContainer <|-- TaskDisplayArea
WindowContainer <|-- Task
WindowContainer <|-- ActivityRecord
Class declaration (line 71):
public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
The generic parameter E constrains children to be configuration containers themselves,
ensuring the cascade operates on a homogeneous hierarchy.
74.3 The Three-Tier Configuration System
Every ConfigurationContainer node maintains three distinct Configuration objects:
| Field | Line | Purpose |
|---|---|---|
mRequestedOverrideConfiguration |
83 | Local overrides requested by this container (e.g., fixed bounds, windowing mode). Acts as the “input” layer. |
mResolvedOverrideConfiguration |
90 | Result of resolveOverrideConfiguration() — the requested override with policy adjustments (aspect-ratio clamping, size-compat scaling, orientation corrections). |
mFullConfiguration |
100 | The complete configuration: parent’s full config merged with the resolved override. This is what getConfiguration() returns and what children use as their parent config. |
A fourth field, mMergedOverrideConfiguration (used by
getMergedOverrideConfiguration()), accumulates all override layers from the root down
to the current node. This is what gets sent to activity clients.
flowchart TB
subgraph Parent["Parent Container"]
PFull["mFullConfiguration"]
end
subgraph Current["Current Container"]
Req["mRequestedOverrideConfiguration<br/>(local overrides)"]
Resolve["resolveOverrideConfiguration()"]
Resolved["mResolvedOverrideConfiguration<br/>(policy-adjusted overrides)"]
Full["mFullConfiguration<br/>(parent + resolved override)"]
end
PFull -->|"newParentConfig"| Resolve
Req -->|"input"| Resolve
Resolve --> Resolved
PFull -->|"setTo()"| Full
Resolved -->|"updateFrom()"| Full
Full -->|"dispatched to children"| C1["Child.onConfigurationChanged()"]
The cascade in onConfigurationChanged() (line 151):
public void onConfigurationChanged(Configuration newParentConfig) {
mResolvedTmpConfig.setTo(mResolvedOverrideConfiguration);
resolveOverrideConfiguration(newParentConfig);
mFullConfiguration.setTo(newParentConfig);
mFullConfiguration.windowConfiguration.unsetAlwaysOnTop();
mFullConfiguration.updateFrom(mResolvedOverrideConfiguration);
onMergedOverrideConfigurationChanged();
// Notify listeners and dispatch to children
for (int i = getChildCount() - 1; i >= 0; --i) {
dispatchConfigurationToChild(getChildAt(i), mFullConfiguration);
}
}
The key sequence is:
- Resolve — transform requested overrides into resolved overrides
- Merge — full config = parent config + resolved overrides
- Notify — change listeners receive
onMergedOverrideConfigurationChanged - Dispatch — each child’s
onConfigurationChanged()is called with the new full config
74.4 The Configuration Cascade: Root → Display → Task → Activity
sequenceDiagram
participant DC as DisplayContent
participant TDA as TaskDisplayArea
participant T as Task
participant AR as ActivityRecord
Note over DC: Rotation / density change detected
DC->>DC: computeScreenConfiguration(values)
DC->>DC: updateDisplayOverrideConfigurationLocked(values)
DC->>DC: onRequestedOverrideConfigurationChanged()
DC->>DC: onConfigurationChanged(parentConfig)
DC->>TDA: dispatchConfigurationToChild(fullConfig)
TDA->>TDA: onConfigurationChanged(displayFullConfig)
TDA->>T: dispatchConfigurationToChild(tdaFullConfig)
T->>T: onConfigurationChanged(tdaFullConfig)
T->>T: computeConfigResourceOverrides()
Note over T: Defined in TaskFragment.java (line 2527),<br/>inherited by Task
T->>AR: dispatchConfigurationToChild(taskFullConfig)
AR->>AR: onConfigurationChanged(taskFullConfig)
AR->>AR: resolveOverrideConfiguration()
AR->>AR: ensureActivityConfiguration()
alt App handles change
AR->>AR: scheduleConfigurationChanged()
Note over AR: ActivityConfigurationChangeItem → onConfigurationChanged()
else App does NOT handle change
AR->>AR: shouldRelaunchLocked() → true
AR->>AR: relaunchActivityLocked()
Note over AR: ActivityRelaunchItem → destroy + recreate
end
Level-by-level details:
RootWindowContainer — Holds the global configuration. When
ActivityTaskManagerService.updateGlobalConfigurationLocked() fires (e.g., locale or
font-scale change), every WindowProcessController and every display receives the
update.
DisplayContent — The computeScreenConfiguration() method (line 2549) computes a
full Configuration from display metrics: screen width/height dp, orientation,
screenLayout, densityDpi, uiMode, colorMode, and rotation. It then calls
updateDisplayOverrideConfigurationLocked() (line 6311) which, for the default display,
delegates to updateGlobalConfigurationLocked(). For secondary displays, it calls
performDisplayOverrideConfigUpdate() and triggers
onRequestedOverrideConfigurationChanged().
TaskDisplayArea — Passes the display’s full configuration to child Tasks without adding its own override logic in the common case.
TaskFragment — computeConfigResourceOverrides() (line 2527 of TaskFragment.java), inherited by Task, translates task bounds
into screenWidthDp, screenHeightDp, smallestScreenWidthDp, and orientation
values. This is critical for split-screen and freeform windowing where task bounds
differ from display bounds.
ActivityRecord — resolveOverrideConfiguration() (line 7585) applies the most
complex set of adjustments: size-compat mode, fixed-orientation letterboxing, aspect
ratio restrictions, safe-region bounds, and PiP transition fixups. Then
ensureActivityConfiguration() (line 8475) compares the new config with what was
last reported to the app and decides the delivery path.
74.5 What Triggers Configuration Changes
| Trigger | Entry Point | Config Flags Affected |
|---|---|---|
| Screen rotation | DisplayRotation.updateRotationUnchecked() → DisplayContent.sendNewConfiguration() |
CONFIG_ORIENTATION, CONFIG_SCREEN_SIZE, CONFIG_SCREEN_LAYOUT, CONFIG_WINDOW_CONFIGURATION |
| Locale change | ATMS.updateGlobalConfigurationLocked() |
CONFIG_LOCALE |
| Font scale | ATMS.updateFontScaleIfNeeded(userId) → updatePersistentConfiguration() |
CONFIG_FONT_SCALE |
| Display density | WMS.setForcedDisplayDensityForUser() → DisplayContent.setForcedDensity() → reconfigureDisplayLocked() |
CONFIG_DENSITY, CONFIG_SCREEN_SIZE |
| Dark mode / UI mode | UiModeManagerService → ATMS.updateGlobalConfigurationLocked() |
CONFIG_UI_MODE |
| Display connected/disconnected | DisplayManagerService → DisplayContent.onDisplayChanged() |
CONFIG_SCREEN_SIZE, CONFIG_DENSITY |
| Task resize (freeform / split) | Shell / TaskOrganizer → Task.setBounds() |
CONFIG_SCREEN_SIZE, CONFIG_SMALLEST_SCREEN_SIZE, CONFIG_ORIENTATION |
| Multi-window mode change | Task.setWindowingMode() |
CONFIG_WINDOW_CONFIGURATION |
| Assets sequence bump | ATMS.updateGlobalConfigurationLocked() |
CONFIG_ASSETS_PATHS |
The rotation path is particularly interesting: DisplayRotation.updateRotationUnchecked()
(line 505) computes the new rotation from sensor data and policy, then calls
DisplayContent.sendNewConfiguration() (line 1688) which recomputes the screen
configuration and pushes it through the hierarchy.
74.6 How Activities Receive Changes — Callback vs. Restart
ActivityRecord.ensureActivityConfiguration() (line 8475) is the decision point:
flowchart TD
Start["ensureActivityConfiguration()"] --> Check1{"Activity finishing<br/>or destroyed?"}
Check1 -- Yes --> Skip["Return true (skip)"]
Check1 -- No --> Check2{"Visible or<br/>ignoreVisibility?"}
Check2 -- No --> Skip
Check2 -- Yes --> Compute["Compute config changes<br/>between last-reported and current"]
Compute --> Check3{"changes == 0?"}
Check3 -- Yes --> Deliver["scheduleConfigurationChanged()<br/>(deliver minor update)"]
Check3 -- No --> ShouldRelaunch{"shouldRelaunchLocked()?"}
ShouldRelaunch -- No --> Callback["scheduleConfigurationChanged()<br/>→ Activity.onConfigurationChanged()"]
ShouldRelaunch -- Yes --> Relaunch["relaunchActivityLocked()<br/>→ destroy + recreate"]
shouldRelaunchLocked() (line 8670) performs:
int configChanged = info.getRealConfigChanged(); // manifest configChanges
return (changes & (~configChanged)) != 0;
If all changed config bits are declared in the app’s manifest configChanges
attribute, the activity can handle the change itself and receives an
onConfigurationChanged() callback. Otherwise, the system destroys and recreates the
activity.
Delivery mechanisms:
| Path | Transaction Item | Effect on Activity |
|---|---|---|
| Callback only | ActivityConfigurationChangeItem |
Activity.onConfigurationChanged(newConfig) is called; no lifecycle change |
| Full relaunch | ActivityRelaunchItem + ResumeActivityItem or PauseActivityItem |
onSaveInstanceState → onDestroy → onCreate with saved state |
relaunchActivityLocked() (line 8781) sends a ClientTransaction containing
an ActivityRelaunchItem with the new MergedConfiguration (process-global config +
merged override config), pending results, and new intents. If the activity was
resumed, a ResumeActivityItem follows; otherwise a PauseActivityItem is sent.
74.7 Per-User Display Configuration
Android supports per-user display settings, most notably forced display density.
WindowManagerService.setForcedDisplayDensityForUser() (line 6556) accepts a
userId parameter and delegates to DisplayContent.setForcedDensity(density, userId):
void setForcedDensity(int density, int userId) {
mIsDensityForced = density != getInitialDisplayDensity();
final boolean updateCurrent = userId == UserHandle.USER_CURRENT;
if (mWmService.mCurrentUserId == userId || updateCurrent) {
mBaseDisplayDensity = density;
reconfigureDisplayLocked();
}
// Persist to DisplayWindowSettings keyed by userId
mWmService.mDisplayWindowSettings.setForcedDensity(
getDisplayInfo(), density, userId);
}
When a different user becomes current, their stored density preference is loaded and
applied, triggering a full configuration cascade. Font scale follows a similar pattern
via ATMS.updateFontScaleIfNeeded(userId) which reads
Settings.System.FONT_SCALE for the given user.
74.8 SizeCompatMode and Config-Based Letterboxing
When an activity’s requested configuration (orientation, min/max aspect ratio, or fixed size) cannot be satisfied by the current display/task bounds, the system enters size-compatibility mode or applies letterboxing.
Key class: AppCompatSizeCompatModePolicy (managed via
ActivityRecord.mAppCompatController.getSizeCompatModePolicy()).
Entry conditions:
-
Fixed orientation on mismatched display — Activity declares
screenOrientation="portrait"but the display is landscape.resolveFixedOrientationConfiguration()applies letterboxing. -
Non-resizable activity in multi-window — Activity has
resizeableActivity=false.AppCompatDisplayInsetscaptures the activity’s preferred display metrics at launch time and freezes them. -
Aspect ratio violation — Activity declares
maxAspectRatioorminAspectRatioand the task bounds exceed them.AppCompatAspectRatioPolicy.resolveAspectRatioRestrictionIfNeeded()constrains the resolved bounds.
Lifecycle:
shouldCreateAppCompatDisplayInsets()(line 7555) — checks if compat insets are neededupdateAppCompatDisplayInsets()— called fromensureActivityConfiguration()when visibleresolveSizeCompatModeConfiguration()— applies the frozen insets to the resolved configinSizeCompatMode()(line 7541) — returnstruewhen the activity is operating in scaled modehasSizeCompatBounds()(line 7560) — returnstruewhen bounds differ from container
When a background activity in size-compat mode detects a display density or non-orientation
size change, restartProcessIfVisible() kills the process to force a clean restart
with fresh display insets (lines 8365–8380 of onConfigurationChanged()).
74.9 Fixed Rotation Transform
When the top activity requests an orientation that differs from the display’s current
rotation, DisplayContent can apply a fixed rotation transform instead of
physically rotating the display.
DisplayContent.rotationForActivityInDifferentOrientation() (line 1937) checks if the
activity needs a different rotation. If so, startFixedRotationTransform() applies a
rotation transformation, allowing the activity to render in its preferred orientation
while the physical display remains unchanged. The actual rotation may follow later as
an animated transition when the fixed rotation is “finished.”
74.10 The Global Configuration Broadcast
When ATMS.updateGlobalConfigurationLocked() (line 5194) fires, the following
fan-out occurs:
- System resources —
mSystemThread.applyConfigurationToResources(tempConfig) - All processes — Every
WindowProcessControllerreceivesonConfigurationChanged(tempConfig)(iterating the pid map) - Default display — The display configuration is recomputed and cascaded
- UsageStats —
reportConfigurationChange()is called for telemetry - AttributeCache — Theme/style attribute cache is invalidated
For non-default displays, performDisplayOverrideConfigUpdate() directly applies the
change to the display’s override configuration and triggers
onRequestedOverrideConfigurationChanged(), which cascades downward.
74.11 Key Source Files
| File | Lines | Role |
|---|---|---|
frameworks/base/services/core/java/com/android/server/wm/ConfigurationContainer.java |
1,007 | Base class — three-tier config, cascade logic |
frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java |
3,803 | Extends ConfigurationContainer; adds child management |
frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java |
7,311 | Display config computation, sendNewConfiguration(), computeScreenConfiguration() |
frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java |
2,255 | updateRotationUnchecked() — rotation change trigger |
frameworks/base/services/core/java/com/android/server/wm/TaskDisplayArea.java |
1,861 | Intermediate container between display and tasks |
frameworks/base/services/core/java/com/android/server/wm/Task.java |
7,190 | computeConfigResourceOverrides() — bounds → dp translation |
frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java |
9,788 | ensureActivityConfiguration(), shouldRelaunchLocked(), SizeCompat |
frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java |
8,130 | updateGlobalConfigurationLocked(), updateFontScaleIfNeeded() |
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java |
— | setForcedDisplayDensityForUser() — per-user density |
frameworks/base/services/core/java/com/android/server/wm/WindowProcessController.java |
2,404 | Per-process configuration delivery |
75. Multi-User Window Isolation
75.1 Overview
Android is a multi-user operating system. Each human user (primary, secondary, or
managed profile) is assigned a userId, and the window manager must ensure strict
isolation: User A’s windows, tasks, and activities must not be visible to User B,
except for explicitly shared system surfaces. This isolation spans the entire WM
hierarchy—from RootWindowContainer at the top, through Task and ActivityRecord,
down to individual WindowState objects.
The core principles are:
- Every Task has an owner —
Task.mUserIdrecords which user created it. - Visibility is gated on userId —
showToCurrentUser()is the universal filter. - User switching saves and restores task state — each user’s front-task stack is preserved across switches.
- Per-user display configuration — density and font scale can vary per user.
- Multi-display can host different users — secondary displays may be assigned to a different user than the primary display.
75.2 User State Tracking in RootWindowContainer
RootWindowContainer maintains three critical user-tracking fields:
// Line 246-249 of RootWindowContainer.java
int mCurrentUser;
SparseIntArray mUserRootTaskInFront = new SparseIntArray(2);
SparseArray<IntArray> mUserVisibleRootTasks = new SparseArray<>();
| Field | Purpose |
|---|---|
mCurrentUser |
The userId of the currently active (foreground) user. Updated during switchUser(). |
mUserRootTaskInFront |
Maps each userId to the root-task ID that was in the foreground when the user was last active. Used to restore focus on user switch. (Legacy path.) |
mUserVisibleRootTasks |
Maps each userId to an IntArray of all visible root-task IDs when that user was last active. Enables restoring multiple visible tasks (e.g., split-screen). (New path, gated by ENABLE_TOP_VISIBLE_ROOT_TASK_PER_USER_TRACKING.) |
classDiagram
class RootWindowContainer {
+int mCurrentUser
+SparseIntArray mUserRootTaskInFront
+SparseArray~IntArray~ mUserVisibleRootTasks
+switchUser(userId, UserState) boolean
+removeUser(userId)
+updateUserRootTask(userId, Task)
+startHomeOnAllDisplays(userId, reason)
+startHomeOnDisplay(userId, reason, displayId)
}
class Task {
+int mUserId
+int mCurrentUser
+switchUser(userId)
+showForAllUsers() boolean
+showToCurrentUser() boolean
}
class ActivityRecord {
+int mUserId
+boolean mShowForAllUsers
+boolean mIsUserAlwaysVisible
+showToCurrentUser() boolean
}
RootWindowContainer "1" --> "*" Task : contains
Task "1" --> "*" ActivityRecord : contains
75.3 The User Switch Protocol
When the system switches from User A to User B, two separate paths are involved:
Path 1 — Prepare (called first, from ActivityManagerService):
WindowManagerService.prepareUserStart(newUserId)
→ mCurrentUserId = newUserId
→ mDisplayWindowSettingsProvider.setOverrideSettingsForUser(newUserId)
→ mPolicy.setCurrentUserLw(newUserId)
Path 2 — Transition (called separately):
WindowManagerService.startUserSwitchTransition(oldUserId, newUserId, uss)
→ switchUserInternal(newUserId)
→ moveUserToForeground(newUserId, uss)
→ ActivityTaskManagerService.switchUser(userId, userState)
→ RootWindowContainer.switchUser(userId, userState)
sequenceDiagram
participant WMS as WindowManagerService
participant ATMS as ActivityTaskManagerService
participant RWC as RootWindowContainer
participant T as Task
participant TDA as TaskDisplayArea
WMS->>WMS: prepareUserStart(newUserId)
Note over WMS: mCurrentUserId = newUserId<br/>Load display settings for user
WMS->>ATMS: switchUser(userId, userState)
ATMS->>RWC: switchUser(userId, userState)
RWC->>RWC: removeRootTasksInWindowingModes(PINNED)
Note over RWC: Save current user's visible root tasks<br/>mUserVisibleRootTasks.put(oldUser, visibleTasks)
RWC->>RWC: mCurrentUser = userId
loop For each root task
RWC->>T: switchUser(userId)
T->>T: mCurrentUser = userId
Note over T: Re-position if showToCurrentUser()
end
RWC->>RWC: Restore new user's saved root tasks
alt Saved tasks exist
RWC->>RWC: handleRootTaskLaunchOnUserSwitch(taskId)
else No saved tasks
RWC->>TDA: getOrCreateRootHomeTask()
RWC->>RWC: Launch home activity
end
RootWindowContainer.switchUser() (line 1864) performs the following sequence:
-
Remove PiP root tasks — PiP mode is dismissed on user switch since PiP activities belong to the previous user.
-
Save current user’s visible tasks — Iterates all root tasks, records which ones are visible. Tasks qualify if they belong to the current user (or
showForAllUsers()is true) and are visible. -
Update
mCurrentUser— Sets the new active user ID. -
Propagate to all root tasks — Calls
rootTask.switchUser(userId)on every root task, which setsmCurrentUseron the task and re-positions tasks owned by the new user to the top. -
Handle always-visible users — If the previously focused task belongs to an “always-visible” user (e.g., communal profile), it is preserved in the new user’s task list.
-
Restore new user’s tasks — Retrieves the saved task list for the new user. If no saved state exists, the home task is created/launched. Otherwise, each saved root task is moved to the front.
-
Return
homeInFront— Returns whether the home activity is the topmost task, which the caller uses to decide whether to show the home screen.
75.4 Per-Container User Visibility
Visibility filtering is the enforcement mechanism for user isolation. The
showToCurrentUser() method is overridden at multiple levels:
WindowContainer (base, line 1715):
boolean showToCurrentUser() {
return true; // Default: visible to all users
}
Task (line 3061):
boolean showForAllUsers() {
if (mChildren.isEmpty()) return false;
final ActivityRecord r = getTopNonFinishingActivity();
return r != null && r.mShowForAllUsers;
}
boolean showToCurrentUser() {
return mForceShowForAllUsers || showForAllUsers()
|| mWmService.isUserVisible(getTopMostTask().mUserId);
}
A task is visible to the current user if:
- It is forced show-for-all (system UI tasks), OR
- Its top activity has
FLAG_SHOW_FOR_ALL_USERS(e.g., the keyguard, system dialogs), OR - Its userId is visible per
UserManagerInternal.isUserVisible()
ActivityRecord (line 9265):
boolean showToCurrentUser() {
return mShowForAllUsers || mWmService.isUserVisible(mUserId);
}
Where mShowForAllUsers is set at construction (line 1895):
mShowForAllUsers = (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0
|| mIsUserAlwaysVisible;
mIsUserAlwaysVisible comes from UserProperties.getAlwaysVisible() — used for
communal profiles that should remain visible across user switches.
WindowState (line 3466):
boolean showToCurrentUser() {
// Child windows are evaluated based on their parent window.
final WindowState win = getTopParentWindow();
if (win.mAttrs.type < FIRST_SYSTEM_WINDOW
&& win.mActivityRecord != null && win.mActivityRecord.mShowForAllUsers) {
if (win.getFrame().left <= win.getDisplayFrame().left
&& win.getFrame().top <= win.getDisplayFrame().top
&& win.getFrame().right >= win.getDisplayFrame().right
&& win.getFrame().bottom >= win.getDisplayFrame().bottom) {
// Is a fullscreen window, like the clock alarm. Show to everyone.
return true;
}
}
return win.showForAllUsers()
|| mWmService.isUserVisible(win.mShowUserId);
}
Window-level filtering checks: if the window is a fullscreen app window whose
activity has mShowForAllUsers, it is visible. Otherwise, it delegates to
showForAllUsers() or checks whether the window’s mShowUserId is visible.
flowchart TD
Start["showToCurrentUser()?"] --> Check1{"mShowForAllUsers<br/>or FLAG_SHOW_FOR_ALL_USERS?"}
Check1 -- Yes --> Visible["✅ Visible"]
Check1 -- No --> Check2{"mIsUserAlwaysVisible?<br/>(communal profile)"}
Check2 -- Yes --> Visible
Check2 -- No --> Check3{"WMS.isUserVisible(mUserId)?"}
Check3 -- Yes --> Visible
Check3 -- No --> Hidden["❌ Hidden"]
style Visible fill:#d4edda
style Hidden fill:#f8d7da
75.5 Task Filtering by userId
Tasks carry their owner’s userId in Task.mUserId (line 439), set from the
applicationInfo.uid of the task’s root activity:
mUserId = UserHandle.getUserId(info.applicationInfo.uid);
This userId is used throughout the system for filtering:
Activity history search — Task.findActivityInHistory() (line 1987) matches both
component name and userId:
private static boolean matchesActivity(
ActivityRecord r, ComponentName activityComponent, int userId) {
return r.mActivityComponent.equals(activityComponent)
&& r.mUserId == userId;
}
Dumping — Task.getDumpActivitiesLocked() (line 6129) filters activities by
userId when userId != UserHandle.USER_ALL.
Task positioning — During switchUser(), tasks belonging to the new current user
are repositioned to the top if they pass showToCurrentUser():
// Task.switchUser() — line 5049
void switchUser(int userId) {
if (mCurrentUser == userId) return;
mCurrentUser = userId;
super.switchUser(userId);
if (!isRootTask() && showToCurrentUser()) {
getParent().positionChildAt(POSITION_TOP, this, false);
}
}
75.6 Per-User Display Configuration
Android supports per-user display settings, enabling different users to have different display densities and font scales on the same physical display.
Density per user — WindowManagerService.setForcedDisplayDensityForUser()
(line 6556) accepts a (displayId, density, userId) triple:
public void setForcedDisplayDensityForUser(int displayId, int density, int userId) {
final int targetUserId = ActivityManager.handleIncomingUser(...);
setForcedDensityLockedInternal(displayId, density, targetUserId);
}
This delegates to DisplayContent.setForcedDensity(density, userId) which:
- Only applies immediately if
userIdmatches the current user - Always persists via
DisplayWindowSettings.setForcedDensity(info, density, userId) - On subsequent user switch, the stored density is loaded and applied
Font scale per user — ATMS.updateFontScaleIfNeeded(userId) reads
Settings.System.FONT_SCALE for the specific user:
void updateFontScaleIfNeeded(@UserIdInt int userId) {
if (userId != getCurrentUserId()) return;
final float scaleFactor = Settings.System.getFloatForUser(
mContext.getContentResolver(), FONT_SCALE, 1.0f, userId);
// ... update global config with new font scale
}
Both density and font scale changes trigger a full configuration cascade through the display hierarchy (§74), ensuring all activities receive the new metrics.
flowchart LR
subgraph UserA["User A Preferences"]
DA["density = 480dpi"]
FA["fontScale = 1.0"]
end
subgraph UserB["User B Preferences"]
DB["density = 320dpi"]
FB["fontScale = 1.3"]
end
subgraph Display["DisplayContent"]
BD["mBaseDisplayDensity"]
SC["Configuration.fontScale"]
end
UserA -->|"on user switch to A"| Display
UserB -->|"on user switch to B"| Display
Display -->|"reconfigureDisplayLocked()"| Cascade["Config cascade<br/>to all activities"]
75.7 Multi-User on Multi-Display
Android’s multi-display support intersects with multi-user through display-to-user assignment.
WMS.getUserAssignedToDisplay(displayId) (line 4093) queries
UserManagerInternal to determine which user owns a particular display:
@UserIdInt int getUserAssignedToDisplay(int displayId) {
return mUmInternal.getUserAssignedToDisplay(displayId);
}
This is used in home-activity launching on secondary displays:
// RootWindowContainer.startHomeOnEmptyDisplays() — line 1268
void startHomeOnEmptyDisplays(String reason) {
forAllTaskDisplayAreas(taskDisplayArea -> {
if (taskDisplayArea.topRunningActivity() == null) {
int userId = mWmService.getUserAssignedToDisplay(
taskDisplayArea.getDisplayId());
startHomeOnTaskDisplayArea(userId, reason, taskDisplayArea, ...);
}
});
}
This enables scenarios where:
- The primary display is assigned to User 0 (owner)
- A secondary display is assigned to User 10 (guest)
- Each display shows that user’s home screen and apps
- Tasks on each display are filtered by the assigned user
Home resolution for secondary displays uses a different intent resolution path.
RootWindowContainer.resolveSecondaryHomeActivity() (line 1410) looks for activities
with CATEGORY_SECONDARY_HOME capability, falling back to the configured secondary
home component.
flowchart TD
subgraph PrimaryDisplay["Primary Display (displayId=0)"]
PU["Assigned: User 0"]
PT1["Task A (userId=0) ✅"]
PT2["Task B (userId=10) ❌"]
PT3["System UI (showForAll) ✅"]
end
subgraph SecondaryDisplay["Secondary Display (displayId=2)"]
SU["Assigned: User 10"]
ST1["Task C (userId=10) ✅"]
ST2["Task D (userId=0) ❌"]
ST3["Settings (showForAll) ✅"]
end
WMS["WMS.getUserAssignedToDisplay()"]
WMS -->|"displayId=0 → userId=0"| PU
WMS -->|"displayId=2 → userId=10"| SU
style PT1 fill:#d4edda
style PT2 fill:#f8d7da
style PT3 fill:#d4edda
style ST1 fill:#d4edda
style ST2 fill:#f8d7da
style ST3 fill:#d4edda
75.8 Work Profile Windows and Managed Profile Isolation
Work profiles (managed profiles) are a special form of multi-user. A managed profile has its own userId but runs concurrently with the parent user—unlike a full secondary user which requires a user switch.
Visibility model — Work profile activities are visible to the parent user because
UserManagerInternal.isUserVisible() returns true for profile users when their parent
is the current foreground user. This means:
ActivityRecord.showToCurrentUser()returnstruefor work-profile activities when the parent user is active- Work-profile tasks appear in the same task list as personal tasks
- The system distinguishes them via the cross-profile app transition animation
(
ANIM_OPEN_CROSS_PROFILE_APPS, referenced at line 24 ofActivityRecord.java)
Process-level isolation — WindowProcessController.mUserId (line 157) carries the
userId of the process owner. Work-profile apps run in separate processes with a
different userId from personal apps, even though they share the same display.
Task boundaries — Each work-profile activity creates tasks with the profile’s
userId. The Task.findActivityInHistory() method matches both component and userId,
preventing a personal-user intent from accidentally landing in a work-profile task
(or vice versa).
Always-visible profiles — The UserProperties.getAlwaysVisible() flag
(checked in ActivityRecord at line 1893) enables communal profiles whose activities
remain visible across user switches. This is distinct from FLAG_SHOW_FOR_ALL_USERS
which is a per-activity manifest flag.
flowchart TD
subgraph ParentUser["User 0 (Personal)"]
PA["Personal App<br/>userId=0"]
SA["System App<br/>showForAllUsers=true"]
end
subgraph WorkProfile["User 10 (Work Profile)"]
WA["Work Email<br/>userId=10"]
WB["Work Slack<br/>userId=10"]
end
subgraph Display["Default Display"]
TaskStack["Task Stack (z-order)"]
end
PA --> TaskStack
SA --> TaskStack
WA --> TaskStack
WB --> TaskStack
Note1["All visible because isUserVisible(10)=true<br/>when parent user 0 is current"]
TaskStack --- Note1
style WA fill:#dbeafe
style WB fill:#dbeafe
style PA fill:#d4edda
style SA fill:#fef3c7
75.9 User Switching — Window State Transitions
During a user switch, windows undergo specific state transitions:
Before switch (saving state):
- PiP windows are dismissed (
removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED)) - All visible root-task IDs for the outgoing user are saved in
mUserVisibleRootTasks - The focused root-task ID is saved in
mUserRootTaskInFront
During switch:
mCurrentUseris updated at bothWMSandRootWindowContainerlevelDisplayWindowSettingsProvider.setOverrideSettingsForUser(newUserId)loads the new user’s display preferences (density, size overrides)mPolicy.setCurrentUserLw(newUserId)updates the window policy- Every root task receives
switchUser(userId)— tasks belonging to the new user are repositioned to the top
After switch (restoring state):
- Saved visible root tasks for the new user are restored to the front
- If no saved state exists, the home activity is launched
ensureActivitiesVisible()runs, which evaluatesshowToCurrentUser()on every window container — windows belonging to the old user become invisible
Per-user display settings restoration:
When WMS.prepareUserStart() calls
mDisplayWindowSettingsProvider.setOverrideSettingsForUser(newUserId), stored display
preferences (forced density, forced size) for the new user are loaded. This triggers
DisplayContent.reconfigureDisplayLocked(), which recomputes the screen configuration
and cascades it through the entire hierarchy.
75.10 Security Boundaries
The multi-user window isolation enforces several security properties:
| Property | Enforcement Point |
|---|---|
| User A cannot see User B’s windows | showToCurrentUser() at WindowState, ActivityRecord, and Task levels |
| User A cannot interact with User B’s tasks | Task.mUserId filtering in task lookup and history search |
| Process isolation | Each user’s apps run with distinct UIDs (userId * 100000 + appId) |
| Display assignment | getUserAssignedToDisplay() restricts which user’s apps appear on which display |
| Keyguard enforcement | canShowWhenLocked() prevents unauthorized access during device lock |
| Work profile boundaries | Separate userId per profile; visibility delegated to UserManagerInternal.isUserVisible() |
| Cross-profile intent guards | ActivityRecord.mUserId matching in findActivityInHistory() |
The WindowManagerService.isUserVisible() call (line 4089) ultimately delegates to
UserManagerInternal.isUserVisible(), which is the single source of truth for user
visibility across the framework. This method accounts for:
- The current foreground user
- Running profiles of the foreground user
- Always-visible (communal) users
- Users assigned to secondary displays
75.11 Key Source Files
| File | Lines | Role |
|---|---|---|
frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java |
3,972 | mCurrentUser, switchUser(), mUserVisibleRootTasks, home launching per user |
frameworks/base/services/core/java/com/android/server/wm/Task.java |
7,190 | mUserId, showForAllUsers(), showToCurrentUser(), switchUser() |
frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java |
9,788 | mUserId, mShowForAllUsers, mIsUserAlwaysVisible, showToCurrentUser() |
frameworks/base/services/core/java/com/android/server/wm/WindowState.java |
— | showToCurrentUser() with fullscreen and system-window checks |
frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java |
3,803 | Base showToCurrentUser() (returns true), switchUser() cascade |
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java |
— | mCurrentUserId, isUserVisible(), getUserAssignedToDisplay(), setForcedDisplayDensityForUser() |
frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java |
8,130 | switchUser() entry point, font-scale per user |
frameworks/base/services/core/java/com/android/server/wm/WindowProcessController.java |
2,404 | mUserId — process-level user association |
frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java |
7,311 | setForcedDensity(density, userId) — per-user display density |
Part XVII: Task Lifecycle and Snapshots
76. Recent Tasks and Task Snapshots
76.1 Overview
The Recent Tasks and Task Snapshot subsystem manages the ordered list of recently used tasks and captures visual snapshots of task content for the Recents UI and starting windows. This subsystem spans from the core window manager service (RecentTasks, SnapshotController) through to the Shell layer (RecentsTransitionHandler) and the persistence layer (TaskSnapshotPersister, SnapshotPersistQueue).
graph TD
subgraph "Shell Layer"
RTH[RecentsTransitionHandler]
RTC[RecentTasksController]
end
subgraph "WM Core"
RT[RecentTasks]
SC[SnapshotController]
TSC[TaskSnapshotController]
ASC[ActivitySnapshotController]
end
subgraph "Capture & Persistence"
SCI[ScreenCaptureInternal]
HWB[HardwareBuffer]
TSP[TaskSnapshotPersister]
SPQ[SnapshotPersistQueue]
CACHE[TaskSnapshotCache]
end
subgraph "Storage"
DISK["/data/system_ce/{userId}/snapshots/"]
end
RTH -->|transition events| SC
RTC -->|task queries| RT
SC -->|coordinate| TSC
SC -->|coordinate| ASC
TSC -->|capture| SCI
SCI -->|returns| HWB
TSC -->|store| CACHE
TSC -->|persist| TSP
TSP -->|enqueue| SPQ
SPQ -->|write| DISK
76.2 RecentTasks — Ordered List Management
RecentTasks maintains the master ordered list of all recently used tasks. The list is ordered by most recent (index 0) to least recent, stored in an ArrayList<Task> field mTasks.
Key fields:
| Field | Type | Purpose |
|---|---|---|
mTasks |
ArrayList<Task> |
Primary ordered recent tasks list |
mHiddenTasks |
ArrayList<Task> |
Hidden tasks (max 10) not shown in Recents |
mHasVisibleRecentTasks |
boolean |
Whether device supports visible recents |
mGlobalMaxNumTasks |
int |
Absolute cap on total tasks |
mMinNumVisibleTasks |
int |
Minimum tasks to show (config-dependent) |
mMaxNumVisibleTasks |
int |
Maximum visible tasks in Recents UI |
mActiveTasksSessionDurationMs |
long |
Inactivity timeout for task trimming |
mFreezeTaskListReordering |
boolean |
Freezes list order during quick-switch |
DEFAULT_INITIAL_CAPACITY |
int (5) |
Initial ArrayList capacity |
FREEZE_TASK_LIST_TIMEOUT_MS |
long (5s) |
Timeout for frozen list reordering |
Adding tasks — The add(Task) method handles multiple scenarios:
- Voice sessions are never added
- Already at top — no-op if the task is already at index 0
- Affiliated tasks — groups of affiliated tasks move together
- Existing task repositioning — if
task.inRecentsis true, the task is moved to its correct z-order position viafindIndexToAdd() - New task insertion —
removeForAddTask()removes any duplicate, then the task is inserted at index 0 (or at the removed index if the list is frozen)
Trimming — trimInactiveRecentTasks() runs after unfreezing or when tasks change:
flowchart TD
START[trimInactiveRecentTasks] --> FROZEN{List frozen?}
FROZEN -->|Yes| DEFER[Defer trimming]
FROZEN -->|No| GLOBAL[Remove tasks > mGlobalMaxNumTasks]
GLOBAL --> QUIET[Identify quiet profile users]
QUIET --> LOOP[Iterate mTasks]
LOOP --> ACTIVE{isActiveRecentTask?}
ACTIVE -->|No| TRIM[Remove from list]
ACTIVE -->|Yes| VISIBLE{isVisibleRecentTask?}
VISIBLE -->|No| KEEP1[Keep — not visible]
VISIBLE -->|Yes| RANGE{isInVisibleRange?}
RANGE -->|Yes| KEEP2[Keep — in range]
RANGE -->|No| TRIMMABLE{isTrimmable?}
TRIMMABLE -->|Yes| TRIM
TRIMMABLE -->|No| KEEP3[Keep — not trimmable]
TRIM --> NOTIFY[notifyTaskRemoved]
Filtering with isVisibleRecentTask() — Excludes:
- Home, Recents, and Dream activity types
- Assistants with
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS - Pinned (PiP) windowing mode tasks
- Always-on-top multi-window tasks
- Lock-task root tasks
- Tasks on displays that disallow recents display
Freeze/unfreeze — During quick-switch gestures, setFreezeTaskListReordering() prevents list reordering. A 5-second timeout (FREEZE_TASK_LIST_TIMEOUT_MS) or user interaction in the foreground app triggers resetFreezeTaskListReordering(topTask), which moves the top task to index 0 and resumes trimming.
76.3 Persistence and Loading
RecentTasks delegates persistence to TaskPersister (separate from snapshot persistence):
notifyTaskPersisterLocked(task, flush)— signals that a task’s state has changedloadRecentTasksIfNeeded(userId)— lazy-loads persisted task metadata from diskmUsersWithRecentsLoaded(SparseArray<AtomicBoolean>) tracks which users are loadedmPersistedTaskIds(SparseArray<SparseBooleanArray>) tracks task IDs on disk per user
Listener notifications — The Callbacks interface provides:
onRecentTaskAdded(Task)— new task added to recentsonRecentTaskRemoved(Task, wasTrimmed, killProcess)— task removed
Additionally, TaskChangeNotificationController fires:
notifyTaskListUpdated()— list content changednotifyTaskStackChanged()— stack order changednotifyTaskListFrozen(boolean)— freeze/unfreeze eventsnotifyRecentTaskRemovedForAddTask(taskId)— dedup removal notification
76.4 SnapshotController — Coordination Facade
SnapshotController is the central coordinator that integrates TaskSnapshotController and ActivitySnapshotController. It responds to transition lifecycle events.
Key fields:
mTaskSnapshotController— handles task-level snapshotsmActivitySnapshotController— handles activity-level snapshotsmSnapshotPersistQueue— shared persistence queue
Transition hooks:
| Method | Trigger | Action |
|---|---|---|
onTransactionReady(type, targets) |
Transition begins | For closing tasks: recordSnapshot(task); for activities: recordSnapshot(closeActivities) |
onTransitionFinish(type, targets) |
Transition ends | For closing tasks: remove/delete obsolete snapshots |
onAppRemoved(activity) |
Activity removed | Delegates to both controllers |
onAppDied(activity) |
Process died | Delegates to both controllers |
notifyAppVisibilityChanged(activity, visible) |
Visibility change | Delegates to activity controller |
The ActivitiesByTask inner class groups ActivityRecord targets by their parent task, pairing opening and closing activities for accurate snapshot decisions.
Snapshot retrieval — getTaskSnapshotInner() checks the cache first, optionally converting resolution:
flowchart TD
REQ[getTaskSnapshotInner] --> LOW{Request low-res?}
LOW -->|Yes| CACHELOW[mCache.getSnapshot LOW]
LOW -->|No| CACHEANY[mCache.getSnapshot ANY]
CACHELOW --> HIT1{Cache hit?}
CACHEANY --> HIT2{Cache hit?}
HIT1 -->|Yes| RETURN1[Return snapshot]
HIT2 -->|Yes| NEED_CONVERT{Need low-res convert?}
HIT1 -->|No| CACHEANY
HIT2 -->|No| RETURN_NULL[Return null]
NEED_CONVERT -->|Yes| CONVERT[convertSnapshotToLowRes]
NEED_CONVERT -->|No| RETURN2[Return snapshot]
CONVERT --> RETURN3[Return converted]
76.5 Capture Pipeline
The snapshot capture process lives in AbsAppSnapshotController, the abstract base class for both TaskSnapshotController and ActivitySnapshotController.
recordSnapshotInner(source, allowAppTheme, consumer) orchestrates the capture:
- Preparation —
prepareTaskSnapshot()computes the crop rectangle and populates aTaskSnapshot.Builderwith metadata (orientation, rotation, task size, insets, appearance, windowing mode, etc.) - Screen capture —
createSnapshot()callsScreenCaptureInternal.captureLayersExcluding()with the source’sSurfaceControl, the computed crop, and a scale factor - Buffer validation —
isInvalidHardwareBuffer()checks the returnedHardwareBufferis non-null and larger than 1×1 - Snapshot construction — The
HardwareBufferis set on the builder viabuilder.setSnapshot(buffer), producing aTaskSnapshot - Consumer callback — The snapshot is passed to the provided consumer for caching/persistence
Scale factors are configured via resources:
config_highResTaskSnapshotScale— e.g., 1.0 for full resolutionconfig_lowResTaskSnapshotScale— e.g., 0.5 for reduced resolutionconfig_use16BitTaskSnapshotPixelFormat— RGBA_8888 vs RGB_565
Task vs. Activity snapshots:
TaskSnapshotControllercaptures the entire task surface — used for Recents thumbnails and starting windows. Snapshots are keyed by task ID and stored to/data/system_ce/{userId}/snapshots/.ActivitySnapshotControllercaptures individual activity surfaces — used for predictive back animations. Only one activity snapshot per visible task is kept. Has a maximum persisted count on disk and stores toactivity_snapshots/directory. It usesmOnBackPressedActivitiesto track activities participating in the predictive back gesture.
ActivitySnapshotController lifecycle:
beginSnapshotProcess()— clears temporary fields before a transition batchendSnapshotProcess()— processes all accumulated changespostProcess()— loads snapshots to cache, removes obsolete snapshots from cache/diskrecordSnapshot(ArrayList<ActivityRecord>)— captures one or more activities as a single snapshotloadActivitySnapshot()— enqueuesLoadActivitySnapshotItemto load from disk to cache on the persist thread- Activities that opt in to
onBackInvokeddispatch have their snapshots taken when they become invisible during transitions
76.6 TaskSnapshotPersister — Async Disk Persistence
TaskSnapshotPersister extends BaseAppSnapshotPersister and manages async writes to /data/system_ce/{userId}/snapshots/.
Key methods:
persistSnapshot(taskId, userId, snapshot)— enqueues a write operationpersistSnapshotAndConvert(taskId, userId, snapshot, consumer)— enqueues write + low-res conversionremoveSnapshot(taskId, userId)— enqueues a delete operationremoveObsoleteFiles(persistentTaskIds, runningUserIds)— cleanup on idle
File format per snapshot:
| File | Format | Example |
|---|---|---|
{taskId}.proto |
Protocol Buffers | 42.proto — metadata (orientation, rotation, insets, etc.) |
{taskId}.jpg |
JPEG/PNG bitmap | 42.jpg — high-resolution screenshot |
{taskId}_reduced.jpg |
JPEG/PNG bitmap | 42_reduced.jpg — low-resolution screenshot |
The StoreWriteQueueItem.writeProto() method populates a TaskSnapshotProto with orientation, rotation, task dimensions, content insets, letterbox insets, windowing mode, appearance, translucency, top activity component, UI mode, density DPI, and a unique snapshot ID.
The writeBuffer() method converts the HardwareBuffer to a software Bitmap, compresses to JPEG (or PNG when respectRequestedTaskSnapshotResolution flag is on) at COMPRESS_QUALITY, and optionally generates a scaled-down low-resolution variant.
76.7 SnapshotPersistQueue — Threading Model
SnapshotPersistQueue manages a single background thread that processes an ArrayDeque<WriteQueueItem> write queue.
flowchart LR
subgraph "Main Thread (WM Lock)"
A[recordSnapshot] --> B[sendToQueueLocked]
B --> C[addToQueueInternal]
C --> D[ensureStoreQueueDepthLocked]
end
subgraph "Persister Thread (Background)"
E[poll from mWriteQueue] --> F{isReady?}
F -->|Yes| G[item.write]
F -->|No| H[re-add to queue + wait]
G --> I{queue empty?}
I -->|No| E
I -->|Yes| J[wait / mQueueIdling=true]
end
D -->|notify| E
Key details:
- Thread runs at
THREAD_PRIORITY_BACKGROUND mStoreQueueItems(ArrayDeque<StoreWriteQueueItem>) tracks pending stores for depth limitingensureStoreQueueDepthLocked()evicts oldest store items when queue gets too deepWriteQueueItem.isReady()checks if the user is unlocked viaUserManagerInternalisDuplicateOrExclusiveItem()prevents redundant writes for the same task IDsetPaused(boolean)temporarily halts processing (used during system events)prepareShutdown()flushes the queue before system shutdown
76.8 Snapshot Cache
TaskSnapshotCache extends SnapshotCache<Task>, using an ArrayMap<Integer, CacheEntry> keyed by task ID (mRunningCache). Each CacheEntry holds the TaskSnapshot and the top ActivityRecord.
Cache operations:
putSnapshot(task, snapshot)— stores or replaces the entry, releasing any previously heldHardwareBuffergetSnapshot(taskId, resolution, usage)— returns cached snapshot, supports resolution filtering (high, low, any)getSnapshotFromDisk(taskId, userId, isLowRes, usage)— fallback to disk viaAppSnapshotLoaderremoveRunningEntry(id)— evicts entry and safely releases theHardwareBuffervia a releaser callbackonAppRemoved(activity)/onAppDied(activity)— removes associated cache entries
ActivitySnapshotCache works similarly but keys entries by a mixed hash code of activity IDs.
76.9 Snapshots Feeding Starting Windows
When a task returns to the foreground, the snapshot serves as a starting window to eliminate visual gaps. The connection (see also §61) works as follows:
ActivityRecordcreates aSnapshotStartingDatacontaining theTaskSnapshotStartingSurfaceController.createTaskSnapshotSurface()is called- The Shell-side
StartingWindowControllerreceives the snapshot and renders it as aTaskSnapshotSurface— a window whose content is the capturedHardwareBuffer - This surface is displayed immediately while the app draws its first frame
- Once the app’s real content is drawn, the snapshot surface is removed via a cross-fade transition
As noted in TaskSnapshotController: “When a task becomes visible again, we show a starting window with the snapshot as the content.”
76.10 Shell RecentsTransitionHandler
RecentsTransitionHandler implements Transitions.TransitionHandler and Transitions.TransitionObserver in the Shell layer. It orchestrates the visual animations when the user opens the Recents (overview) screen.
Transition lifecycle:
sequenceDiagram
participant Launcher
participant RTH as RecentsTransitionHandler
participant Trans as Transitions
participant WMCore as WM Core
participant RC as RecentsController
Launcher->>RTH: startRecentsTransition(intent, listener)
RTH->>Trans: startTransition(TRANSIT_START_RECENTS_TRANSITION)
Trans->>WMCore: prepare transition
WMCore-->>RTH: handleRequest(transition, request)
RTH->>RC: new RecentsController(listener, displayId)
WMCore-->>RTH: startAnimation(transition, info, t, finishCb)
RTH->>RC: accept transition info
RC->>Launcher: IRecentsAnimationRunner.onAnimationStart(targets)
Note over Launcher: User interacts with Recents UI
Launcher->>RC: finish(toHome) / setFinishTaskTransaction(overlay)
RC->>Trans: TRANSIT_END_RECENTS_TRANSITION
RTH->>RTH: mergeAnimation if needed
RC->>RC: finishInner(toHome)
Key inner class RecentsController:
- Extends
IRecentsAnimationController.Stub— the binder interface Launcher uses - Tracks
mOpeningTasks,mClosingTasks,mPausingTasks— categorized transition participants - Manages
mPendingFinishTransaction— snapshot-based finish transaction applied on cancel CANCEL_WITH_SNAPSHOTS_FINISH_TIMEOUT_MS = 200ms— timeout for snapshot-based cancel completion- Uses a
mDeathHandlerfield/lambda to handle Launcher crashes during recents
Mixer support — RecentsMixedHandler interface allows other transition handlers (e.g., PiP, split-screen) to participate in recents transitions. Mixers are checked in registration order.
State listeners — RecentsTransitionStateListener receives state changes:
TRANSITION_STATE_NOT_RUNNINGTRANSITION_STATE_REQUESTEDTRANSITION_STATE_ANIMATING
76.11 Key Source Files
| File | Path | Lines | Role |
|---|---|---|---|
| RecentTasks | frameworks/base/services/core/java/com/android/server/wm/RecentTasks.java |
2,076 | Ordered list management, filtering, persistence |
| SnapshotController | frameworks/base/services/core/java/com/android/server/wm/SnapshotController.java |
498 | Facade coordinating task and activity snapshots |
| TaskSnapshotController | frameworks/base/services/core/java/com/android/server/wm/TaskSnapshotController.java |
512 | Task-level snapshot capture and cache management |
| ActivitySnapshotController | frameworks/base/services/core/java/com/android/server/wm/ActivitySnapshotController.java |
817 | Activity-level snapshots for predictive back |
| AbsAppSnapshotController | frameworks/base/services/core/java/com/android/server/wm/AbsAppSnapshotController.java |
~600 | Base capture pipeline with ScreenCaptureInternal |
| TaskSnapshotPersister | frameworks/base/services/core/java/com/android/server/wm/TaskSnapshotPersister.java |
192 | Async write/delete/cleanup of snapshot files |
| SnapshotPersistQueue | frameworks/base/services/core/java/com/android/server/wm/SnapshotPersistQueue.java |
645 | Background thread, write queue, store/delete items |
| BaseAppSnapshotPersister | frameworks/base/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java |
~230 | PersistInfoProvider, directory resolution |
| TaskSnapshotCache | frameworks/base/services/core/java/com/android/server/wm/TaskSnapshotCache.java |
~100 | In-memory cache keyed by task ID |
| SnapshotCache | frameworks/base/services/core/java/com/android/server/wm/SnapshotCache.java |
~130 | Base cache with ArrayMap<Integer, CacheEntry> |
| SnapshotStartingData | frameworks/base/services/core/java/com/android/server/wm/SnapshotStartingData.java |
~40 | Starting window data wrapping TaskSnapshot |
| RecentsTransitionHandler | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java |
~1000 | Shell-side recents animation transitions |
| RecentTasksController | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java |
~300 | Shell-side recent tasks query interface |
| RecentsTransitionStateListener | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java |
~30 | Transition state change callback |
76.12 RecentsController — Binder Animation Interface
The RecentsController inner class of RecentsTransitionHandler is the primary binder interface that Launcher uses to control the recents animation session. It extends IRecentsAnimationController.Stub.
Task categorization during transitions:
mOpeningTasks— tasks opening (becoming visible) during the recents transitionmClosingTasks— tasks closing (becoming invisible), typically the current foreground taskmPausingTasks— tasks that are paused but remain partially visible (e.g., translucent overlays)
Key binder methods exposed to Launcher:
setFinishTaskTransaction(taskId, transaction, overlay)— sets a pending transaction to apply on finish for a specific task (typically snapshot placement)finish(toHome, sendUserLeaveHint, finishCb)— completes the recents animation, switching to home or the selected tasksetWillFinishToHome(toHome)— pre-declares intent so the controller can optimize
Snapshot-based cancel — When the animation is canceled (e.g., user lifts finger without selecting a task), the controller applies mPendingFinishTransaction — a pre-built surface transaction that places a snapshot surface behind the animating task — then waits up to CANCEL_WITH_SNAPSHOTS_FINISH_TIMEOUT_MS (200ms) for the snapshot surface to draw before finishing the transition. This prevents visual glitches during rapid cancel scenarios.
76.13 Summary
The Recent Tasks and Task Snapshot subsystem forms a tightly integrated pipeline: RecentTasks manages the ordered task list with sophisticated filtering and trimming logic; SnapshotController coordinates capture timing based on transition events; the capture pipeline uses ScreenCaptureInternal to grab HardwareBuffer snapshots that are cached in-memory and persisted asynchronously to credential-encrypted storage as proto+bitmap pairs; and the Shell-layer RecentsTransitionHandler manages the visual transition animations that bring the Recents UI to life using these captured snapshots. The snapshot cache also feeds starting windows (§61), providing immediate visual content when tasks resume.
Part XVIII: Security and Privacy
77. Window Privacy and Security
77.1 Overview
Android’s Window Manager enforces a multi-layered privacy and security model that prevents unauthorized capture of window content. This system spans from per-window FLAG_SECURE enforcement through enterprise MDM policies to newer runtime sensitive-content protection and cryptographic display hashing. The architecture ensures that secure content is protected at the SurfaceFlinger composition layer, making it impossible to bypass via screen capture, screen recording, or virtual display mirroring.
graph TD
subgraph "Application Layer"
APP[App Window]
FLAG[FLAG_SECURE attribute]
SCP_API[SensitiveContent API]
CB[ScreenRecordingCallback]
end
subgraph "Window Manager Service"
WS[WindowState]
ISL[isSecureLocked]
SCPKG[SensitiveContentPackages]
DPC[DevicePolicyCache]
DHC[DisplayHashController]
SRCC[ScreenRecordingCallbackController]
CRC[ContentRecordingController]
end
subgraph "SurfaceFlinger"
SC[SurfaceControl]
SECURE_FLAG[setSecure on layer]
COMP[Composition]
BLACK[Black/blank for secure layers]
end
APP --> FLAG
APP --> SCP_API
FLAG --> WS
SCP_API --> SCPKG
WS --> ISL
SCPKG --> ISL
DPC --> ISL
ISL -->|true| SC
SC --> SECURE_FLAG
SECURE_FLAG --> COMP
COMP --> BLACK
APP --> CB
CB --> SRCC
CRC --> SRCC
DHC --> SC
77.2 FLAG_SECURE — Per-Window Screenshot Prevention
FLAG_SECURE (WindowManager.LayoutParams.FLAG_SECURE) is the fundamental mechanism for preventing window content capture. It is checked through WindowState.isSecureLocked():
boolean isSecureLocked() {
if (mWmService.getDisableSecureWindows()) {
return false; // Debug override
}
if ((mAttrs.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
return true; // Explicit app opt-in
}
if (mWmService.mSensitiveContentPackages.shouldBlockScreenCaptureForApp(
getOwningPackage(), getOwningUid(), getWindowToken())) {
return true; // Runtime sensitive content protection
}
return !DevicePolicyCache.getInstance().isScreenCaptureAllowed(mShowUserId);
}
Three-tier security check:
- App-level —
FLAG_SECUREset in window layout params (banking apps, password fields) - System-level —
SensitiveContentPackagesruntime blocking (sensitive notifications, OTP screens) - Enterprise-level —
DevicePolicyCache.isScreenCaptureAllowed()— MDM policy blocks all capture for managed users
SurfaceControl enforcement — When isSecureLocked() returns true, the secure flag is propagated to the SurfaceFlinger layer:
// In WindowState — called during surface creation and updates
getPendingTransaction().setSecure(mSurfaceControl, isSecureLocked());
The setSecureLocked(boolean) method explicitly sets or clears the secure flag:
void setSecureLocked(boolean isSecure) {
if (mSurfaceControl == null) return;
getPendingTransaction().setSecure(mSurfaceControl, isSecure);
if (mDisplayContent != null) {
mDisplayContent.refreshImeSecureFlag(getSyncTransaction());
}
mWmService.scheduleAnimationLocked();
}
Note the IME security propagation: when a secure window has focus, the IME (Input Method Editor) surface also gets its secure flag refreshed to prevent keyboard content leakage.
Screenshot gate — canScreenshotIme() returns !isSecureLocked(), directly controlling whether the IME surface can be included in screenshots.
flowchart TD
CHECK[isSecureLocked called] --> DEBUG{Debug override?}
DEBUG -->|Yes, disabled| NOSECURE[Return false]
DEBUG -->|No| FLAGCHECK{FLAG_SECURE set?}
FLAGCHECK -->|Yes| SECURE[Return true]
FLAGCHECK -->|No| SENSITIVE{SensitiveContentPackages<br/>blocks this app?}
SENSITIVE -->|Yes| SECURE
SENSITIVE -->|No| MDM{DevicePolicyCache<br/>allows capture?}
MDM -->|No, blocked| SECURE
MDM -->|Yes, allowed| NOSECURE
SECURE --> SURFACE[setSecure on SurfaceControl]
SURFACE --> SF[SurfaceFlinger renders black<br/>for capture/recording]
77.3 SensitiveContentPackages — Dynamic Runtime Protection
SensitiveContentPackages is a newer (2023+) mechanism that provides runtime screenshot blocking without requiring apps to set FLAG_SECURE. It maintains an ArraySet<PackageInfo> of package/UID/window-token triples that should be blocked.
Two protection modes:
sensitiveContentAppProtection— blocks a specific window where sensitive content is rendered (keyed by window token)sensitiveNotificationAppProtection— blocks the entire package if it has a sensitive notification (window token is null)
Key API:
| Method | Purpose |
|---|---|
shouldBlockScreenCaptureForApp(pkg, uid, windowToken) |
Checks if a specific window should be blocked |
addBlockScreenCaptureForApps(packageInfos) |
Adds packages to the protection set |
removeBlockScreenCaptureForApps(packageInfos) |
Removes packages from the protection set |
clearBlockedApps() |
Clears all protection |
PackageInfo inner class tracks:
mPkg— package namemUid— process UIDmWindowToken— optionalIBinderfor window-level targeting (null for package-level)
This mechanism is gated behind two feature flags:
sensitiveContentAppProtection()— fromandroid.view.flagssensitiveNotificationAppProtection()— fromandroid.permission.flags
When the system detects sensitive content (e.g., an OTP notification, a password autofill), it registers the relevant packages via addBlockScreenCaptureForApps(). The isSecureLocked() check in WindowState then automatically blocks capture for those windows without the app needing to set FLAG_SECURE.
sequenceDiagram
participant System as System Service
participant SCP as SensitiveContentPackages
participant WS as WindowState
participant SF as SurfaceFlinger
System->>SCP: addBlockScreenCaptureForApps({pkg, uid, token})
Note over SCP: Added to mProtectedPackages
Note over WS: Screen capture attempted
WS->>WS: isSecureLocked()
WS->>SCP: shouldBlockScreenCaptureForApp(pkg, uid, windowToken)
SCP-->>WS: true (package is protected)
WS->>SF: setSecure(surfaceControl, true)
SF-->>SF: Renders black for capture
System->>SCP: removeBlockScreenCaptureForApps({pkg, uid, token})
Note over SCP: Removed from mProtectedPackages
77.4 DisplayHashController — Cryptographic Display Verification
DisplayHashController provides a mechanism to generate and verify cryptographic hashes of display content. This is used for DRM verification, payment confirmation, and content authenticity checks.
Architecture:
- Communicates with a
DisplayHashingService(a bound system service implementingIDisplayHashingService) - Uses a random
mSalt(UUID.randomUUID().toString().getBytes()) generated per WMS lifetime - Supports multiple hash algorithms via
DisplayHashParams - Rate-limits hash generation to prevent abuse (
mDisplayHashThrottlingEnabled)
Hash generation flow:
sequenceDiagram
participant App
participant DHC as DisplayHashController
participant SCI as ScreenCaptureInternal
participant DHS as DisplayHashingService
App->>DHC: generateDisplayHash(captureArgs, bounds, algorithm, uid, callback)
DHC->>DHC: Validate algorithm against supported list
DHC->>DHC: Check throttle (DISPLAY_HASH_ERROR_TOO_MANY_REQUESTS)
DHC->>SCI: captureLayers(args)
SCI-->>DHC: ScreenshotHardwareBuffer
DHC->>DHC: Validate buffer is non-null
DHC->>DHS: generateDisplayHash(salt, buffer, bounds, algorithm, remoteCallback)
DHS-->>DHC: DisplayHash result
DHC-->>App: DisplayHashResultCallback
Note over App: Later verification
App->>DHC: verifyDisplayHash(displayHash)
DHC->>DHS: verifyDisplayHash(salt, displayHash, remoteCallback)
DHS-->>DHC: VerifiedDisplayHash
DHC-->>App: VerifiedDisplayHash result
Key methods:
| Method | Purpose |
|---|---|
getSupportedHashAlgorithms() |
Returns array of supported algorithm names |
generateDisplayHash(args, bounds, algorithm, callback) |
Captures and hashes display region |
verifyDisplayHash(displayHash) |
Verifies a previously generated hash |
setDisplayHashThrottlingEnabled(enable) |
Enables/disables rate limiting |
Error codes:
DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM— unsupported algorithmDISPLAY_HASH_ERROR_TOO_MANY_REQUESTS— rate limitedDISPLAY_HASH_ERROR_UNKNOWN— capture or hash failure
The service connection (DisplayHashingServiceConnection) is lazily established and automatically reconnects. The mDisplayHashAlgorithms map is cached after first retrieval.
77.5 Screen Recording Prevention Pipeline
ScreenRecordingCallbackController (2024+) provides a mechanism for apps to be notified when they are being screen-recorded, enabling them to take protective action.
Architecture:
The controller integrates with MediaProjectionManager via IMediaProjectionWatcherCallback to detect recording start/stop events.
Registration flow:
- App registers via
register(IScreenRecordingCallback)— returns current recording state - Controller ensures
MediaProjectionWatcherCallbackis registered withMediaProjectionManager - Callbacks are stored in
ArrayMap<IBinder, Callback>with UID tracking DeathRecipientauto-cleans up when the client process dies
Notification flow:
sequenceDiagram
participant MP as MediaProjectionManager
participant SRCC as ScreenRecordingCallbackController
participant WMS as WindowManagerService
participant App as App (IScreenRecordingCallback)
MP->>SRCC: onStart(mediaProjectionInfo)
SRCC->>SRCC: setRecordedWindowContainer(info)
Note over SRCC: Determines scope via launchCookie:<br/>null → whole display, else → specific task
SRCC->>SRCC: getRecordedUids() — find visible UIDs
SRCC->>App: onScreenRecordingStateChanged(true)
Note over SRCC: Activity visibility changes during recording
WMS->>SRCC: onProcessActivityVisibilityChanged(uid, visible)
SRCC->>SRCC: Check uidHasRecordedActivity(uid)
SRCC->>App: onScreenRecordingStateChanged(visible)
MP->>SRCC: onStop(mediaProjectionInfo)
SRCC->>App: onScreenRecordingStateChanged(false)
SRCC->>SRCC: mRecordedWC = null
Recording scope determination:
- If
mediaProjectionInfo.getLaunchCookie()is null → recording the entire default display (mRecordedWC = defaultDisplay) - If a launch cookie is provided → recording a specific task identified by matching
ActivityRecord.mLaunchCookie
State deduplication — mLastInvokedStateByUid (ArrayMap<Integer, Boolean>) tracks the last dispatched state per UID to avoid duplicate callbacks.
77.6 Enterprise MDM Integration
The enterprise device policy layer provides organization-wide screenshot blocking through DevicePolicyCache:
return !DevicePolicyCache.getInstance().isScreenCaptureAllowed(mShowUserId);
This is the final check in isSecureLocked(). When a device admin sets a screen capture restriction via DevicePolicyManager.setScreenCaptureDisabled(admin, true), the cached policy returns false from isScreenCaptureAllowed(), causing ALL windows for that managed user to be treated as secure.
Key characteristics:
- Works at the user level, not per-window or per-app
- Affects both personal and work profile windows
- Takes precedence only if neither
FLAG_SECUREnorSensitiveContentPackageshas already blocked capture - Policy is cached in
DevicePolicyCachesingleton for fast lock-free reads - MDM changes propagate via
DevicePolicyManagerServiceupdating the cache
flowchart TD
subgraph "Admin Console"
ADMIN[IT Admin]
end
subgraph "Device Policy"
DPM[DevicePolicyManager]
DPMS[DevicePolicyManagerService]
DPC[DevicePolicyCache]
end
subgraph "Window Manager"
WS[WindowState.isSecureLocked]
end
subgraph "SurfaceFlinger"
SECURE[Secure layer composition]
end
ADMIN -->|setScreenCaptureDisabled| DPM
DPM -->|update| DPMS
DPMS -->|cache| DPC
WS -->|isScreenCaptureAllowed?| DPC
DPC -->|false| WS
WS -->|setSecure true| SECURE
SECURE -->|black output| SECURE
77.7 Secure Display Composition in SurfaceFlinger
When a layer has its secure flag set via SurfaceControl.Transaction.setSecure(), SurfaceFlinger enforces content protection at the composition level:
Secure vs. non-secure displays:
- Secure displays (physical displays with HDCP or trusted displays) — can render secure layers normally
- Non-secure displays (virtual displays, screen capture surfaces, cast/mirror targets) — secure layers are rendered as black/blank
Enforcement points:
WindowState.setSecureLocked()→getPendingTransaction().setSecure(mSurfaceControl, isSecure)— sets the secure flag on the window’sSurfaceControllayer- SurfaceFlinger’s composition pipeline checks each layer’s secure flag during frame composition
- When compositing for a non-secure output (screen capture, virtual display), layers marked secure are excluded or rendered as opaque black
- The IME window’s secure flag is refreshed when the focused window’s security state changes:
mDisplayContent.refreshImeSecureFlag(getSyncTransaction())
This means FLAG_SECURE content appears normally on the physical screen but is replaced with black in:
- Screenshots (
SurfaceControl.screenshot()) - Screen recordings (
MediaProjection) - Display mirroring/casting to external displays without HDCP
- Virtual display captures
77.8 Content Recording Controller and Virtual Display Interaction
ContentRecordingController manages the lifecycle of content recording sessions, specifically coordinating which DisplayContent hosts the recording virtual display.
Session states:
| Scenario | Description |
|---|---|
| Start | New ContentRecordingSession arrives; recording begins on the target virtual display |
| Takeover | A new session replaces an existing one; the old display is paused, new display starts |
| Stop | Null session arrives; recording stops on the current display |
| Update | Same display, consent updated from waiting to granted |
| Invalid | Malformed session is ignored |
| Duplicate | Identical session to current is ignored |
Key interactions:
sequenceDiagram
participant MPS as MediaProjectionService
participant CRC as ContentRecordingController
participant DC_Old as DisplayContent (old)
participant DC_New as DisplayContent (new)
participant CR as ContentRecorder
MPS->>CRC: setContentRecordingSessionLocked(newSession)
CRC->>CRC: Validate session (ContentRecordingSession.isValid)
CRC->>CRC: Check if same display (isProjectionOnSameDisplay)
alt Takeover — different display
CRC->>DC_Old: pauseRecording()
CRC->>DC_Old: setContentRecordingSession(null)
end
CRC->>DC_New: wmService.mRoot.getDisplayContentOrCreate(virtualDisplayId)
CRC->>DC_New: setContentRecordingSession(newSession)
CRC->>DC_New: updateRecording()
DC_New->>CR: Start content recording
Note over CRC: Cache updated state
CRC->>CRC: mSession = newSession
CRC->>CRC: mDisplayContent = DC_New
Single-session constraint — Only one content recording session is supported device-wide. The controller’s mSession and mDisplayContent fields enforce this singleton behavior.
Consent flow — The isWaitingForConsent() check allows sessions to be updated when user consent is granted, without being treated as duplicate sessions.
Integration with secure windows — When content is recorded via a virtual display, FLAG_SECURE windows within the recorded WindowContainer (display or task) are automatically blanked by SurfaceFlinger’s composition pipeline, as the virtual display is a non-secure output surface.
77.9 Privacy Model Integration
The various privacy components work together in a layered defense model:
| Layer | Component | Scope | Mechanism |
|---|---|---|---|
| App opt-in | FLAG_SECURE |
Per-window | Static layout param |
| System runtime | SensitiveContentPackages |
Per-window or per-package | Dynamic add/remove |
| Enterprise | DevicePolicyCache |
Per-user | MDM policy |
| Notification | ScreenRecordingCallbackController |
Per-UID | Callback notification |
| Verification | DisplayHashController |
Per-region | Cryptographic hash |
| Session mgmt | ContentRecordingController |
Per-display | Virtual display lifecycle |
| Composition | SurfaceFlinger secure flag | Per-layer | Hardware composition |
All blocking mechanisms ultimately converge at WindowState.isSecureLocked(), which gates the SurfaceControl.setSecure() call to SurfaceFlinger. The only exception is ScreenRecordingCallbackController, which notifies apps but does not directly block content — apps must take their own protective action (e.g., setting FLAG_SECURE or hiding sensitive content).
77.10 Key Source Files
| File | Path | Lines | Role |
|---|---|---|---|
| WindowState | frameworks/base/services/core/java/com/android/server/wm/WindowState.java |
~6,200 | isSecureLocked(), setSecureLocked(), FLAG_SECURE enforcement |
| SensitiveContentPackages | frameworks/base/services/core/java/com/android/server/wm/SensitiveContentPackages.java |
174 | Dynamic runtime capture blocking |
| DisplayHashController | frameworks/base/services/core/java/com/android/server/wm/DisplayHashController.java |
582 | Cryptographic display content hashing |
| ScreenRecordingCallbackController | frameworks/base/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java |
302 | App notification of screen recording |
| ContentRecordingController | frameworks/base/services/core/java/com/android/server/wm/ContentRecordingController.java |
138 | Virtual display recording session management |
| DevicePolicyCache | frameworks/base/core/java/android/app/admin/DevicePolicyCache.java |
~100 | Cached MDM screen capture policy |
| SurfaceControl | frameworks/base/core/java/android/view/SurfaceControl.java |
~5,635 | setSecure() transaction to SurfaceFlinger |
| DisplayContent | frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java |
~7,000 | refreshImeSecureFlag(), recording session host |
| ContentRecorder | frameworks/base/services/core/java/com/android/server/wm/ContentRecorder.java |
~840 | Actual recording logic on DisplayContent |
| WindowManagerService | frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java |
~10,983 | Hosts mSensitiveContentPackages, security policy |
77.11 Summary
Android’s window privacy and security model provides defense-in-depth through multiple complementary mechanisms. FLAG_SECURE gives apps direct control over their window security; SensitiveContentPackages extends this with system-driven runtime protection for sensitive notifications and autofill content; DevicePolicyCache enables enterprise-wide screenshot prevention; ScreenRecordingCallbackController gives apps awareness of recording state for reactive protection; DisplayHashController provides cryptographic verification of display content for DRM and payment use cases; and ContentRecordingController manages the lifecycle of virtual display recording sessions. All blocking mechanisms converge through WindowState.isSecureLocked() to set the secure flag on SurfaceControl layers, where SurfaceFlinger’s hardware composition ensures secure content is never leaked to non-secure output surfaces.
Part XIX: System UI Windows
78. Notification Shade and Status Bar Windows
The Android system UI manages two critical window layers for user notifications and
status information: the notification shade (TYPE_NOTIFICATION_SHADE) and the
status bar (TYPE_STATUS_BAR). These windows are created and managed by SystemUI
through dedicated controller classes that orchestrate window attributes, visibility,
focusability, and input routing based on a rich state machine.
78.1 NotificationShadeWindowControllerImpl: TYPE_NOTIFICATION_SHADE Window Management
NotificationShadeWindowControllerImpl is the central class responsible for managing the
notification shade window. Annotated @SysUISingleton, it implements
NotificationShadeWindowController and tracks all shade state through a
NotificationShadeWindowState object.
Window Creation and Initial Layout Parameters
The shade window is created in ShadeWindowLayoutParams.create() with these attributes:
// frameworks/base/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLayoutParams.kt
LayoutParams(
MATCH_PARENT, MATCH_PARENT,
LayoutParams.TYPE_NOTIFICATION_SHADE, // Window type = FIRST_SYSTEM_WINDOW + 40
FLAG_NOT_FOCUSABLE | FLAG_TOUCHABLE_WHEN_WAKING
| FLAG_WATCH_OUTSIDE_TOUCH | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
PixelFormat.TRANSLUCENT
)
Additional attributes configured at creation:
gravity = Gravity.TOP— anchored to top of displayfitInsetsTypes = 0— no automatic inset fittinglayoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYSprivateFlags |= PRIVATE_FLAG_OPTIMIZE_MEASUREtitle = "NotificationShade"
Window Attachment
The attach() method adds the shade view to the window manager and configures transient
bar behavior:
// frameworks/base/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
mLp = mShadeWindowLayoutParams;
mWindowManager.addView(mWindowRootView, mLp);
mWindowRootView.getWindowInsetsController()
.setSystemBarsBehavior(BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
The shade window also attaches itself as a parent window so that sub-windows
(e.g., TYPE_APPLICATION_ATTACHED_DIALOG) can be spawned from it. After attachment,
setFallbackWindowType(TYPE_APPLICATION_ATTACHED_DIALOG) is called on the window context.
sequenceDiagram
participant SysUI as SystemUI Init
participant NSWC as NotificationShadeWindowControllerImpl
participant WM as WindowManager
participant WMS as WindowManagerService
SysUI->>NSWC: fetchWindowRootView()
NSWC->>NSWC: Create WindowRootView via Factory
NSWC->>NSWC: Collect flows (expansion, QS, communal)
SysUI->>NSWC: attach()
NSWC->>NSWC: mLp = mShadeWindowLayoutParams
NSWC->>WM: addView(windowRootView, lp)
WM->>WMS: addWindow(TYPE_NOTIFICATION_SHADE)
NSWC->>NSWC: Set BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
NSWC->>NSWC: setFallbackWindowType(ATTACHED_DIALOG)
78.2 Window State Tracking: Visibility, Focusability, Expansion
The shade window’s behavior is driven by NotificationShadeWindowState, a Kotlin data class
with over 30 boolean and integer fields. Each state change triggers the apply() pipeline,
which recalculates all window flags atomically.
Key State Fields
| Field | Type | Purpose |
|---|---|---|
keyguardShowing |
boolean | Lock screen is displayed |
keyguardOccluded |
boolean | Activity is covering the keyguard |
panelVisible |
boolean | Shade panel open/visible |
shadeOrQsExpanded |
boolean | Shade or QS expansion fraction > 0 |
notificationShadeFocusable |
boolean | Whether shade can receive focus |
bouncerShowing |
boolean | Keyguard bouncer is displayed |
qsExpanded |
boolean | Quick Settings fully expanded |
headsUpNotificationShowing |
boolean | Heads-up notification visible |
dozing |
boolean | Device in doze/AOD mode |
forceWindowCollapsed |
boolean | Force window to collapsed state |
windowNotTouchable |
boolean | Make window non-touchable |
statusBarState |
int | SHADE, KEYGUARD, or SHADE_LOCKED |
remoteInputActive |
boolean | Remote input (e.g., reply) is active |
scrimsVisibility |
int | Scrim transparency state |
communalVisible |
boolean | Communal (glanceable hub) visible |
The apply() Pipeline
Every state mutation triggers apply(NotificationShadeWindowState), which runs a
sequence of flag-computation methods:
flowchart TD
A[State Change] --> B[apply]
B --> C[applyKeyguardFlags]
B --> D[applyFocusableFlag]
B --> E[applyForceShowNavigationFlag]
B --> F[adjustScreenOrientation]
B --> G[applyVisibility]
B --> H[applyUserActivityTimeout]
B --> I[applyInputFeatures]
B --> J[applyFitsSystemWindows]
B --> K[applyModalFlag]
B --> L[applyBrightness]
B --> M[applyHasTopUi]
B --> N[applyNotTouchable]
B --> O[applyStatusBarColorSpaceAgnosticFlag]
B --> P[applyWindowLayoutParams]
P --> Q[WindowManager.updateViewLayout]
B --> R[notifyStateChangedCallbacks]
Visibility Determination
The isExpanded() method determines when the shade window root view is VISIBLE vs
INVISIBLE:
boolean isExpanded = (!state.forceWindowCollapsed
&& (state.isKeyguardShowingAndNotOccluded()
|| state.panelVisible || state.keyguardFadingAway
|| state.bouncerShowing || state.headsUpNotificationShowing
|| state.scrimsVisibility != ScrimController.TRANSPARENT))
|| state.launchingActivityFromNotification;
When not expanded, the root view is set to View.INVISIBLE, preventing it from consuming
touch events while remaining attached to the window manager for fast re-display.
78.3 NotificationPanelViewController: Gesture Handling and Keyguard
NotificationPanelViewController (4329 lines) manages all physical interaction with the
notification shade panel. It handles touch events, fling gestures, expansion tracking,
and coordinates with both the keyguard and Quick Settings subsystems.
Expansion Tracking
The panel tracks its state through several key fields:
mExpandedFraction(float, 0.0–1.0) — current expansion progressmTracking(boolean) — whether a user touch is actively draggingmCollapsedOnDown— panel was fully collapsed when touch started
Gesture Flow
Touch events flow through a multi-stage pipeline:
initDownStates()— captures initial state on ACTION_DOWNflingExpands()/flingExpandsQs()— determines if fling velocity opens or closesfling()— triggers animated open/close withFlingAnimationUtilsonFlingEnd()— resets tracking state, notifies expansion listeners
The expansion threshold for flinging is 0.5f — if the panel is more than half open and
the fling velocity is below minVelocityPxPerSecond, the panel expands fully. Input focus
transfer from the launcher uses synthetic DOWN events:
// Launcher → Shade input focus transfer
public void startInputFocusTransfer() {
mExpectingSynthesizedDown = true;
onTrackingStarted();
updatePanelExpanded();
}
public void finishInputFocusTransfer(float velocity) {
if (mExpectingSynthesizedDown) {
fling(velocity > 1f ? 1000f * velocity : 0);
onTrackingStopped(false);
}
}
Keyguard Integration
The panel controller integrates deeply with the keyguard system:
- Listens to
KeyguardStateControllerfor lock state changes - Coordinates with
KeyguardTransitionInteractorfor animation states - Uses
FalsingManagerto classify touch interactions (QUICK_SETTINGS, UNLOCK, BOUNCER_UNLOCK) - Collapses panel behavior differs between
StatusBarState.SHADEandKEYGUARD
78.4 StatusBar Window Chain: TYPE_STATUS_BAR Management
StatusBarWindowControllerImpl manages the status bar window separately from the shade.
The status bar occupies a thin strip at the top of the screen.
Layout Parameters
// frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
new LayoutParams(
MATCH_PARENT,
statusBarHeight,
TYPE_STATUS_BAR, // Window type = FIRST_SYSTEM_WINDOW (2000)
FLAG_NOT_FOCUSABLE | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
PixelFormat.TRANSLUCENT
)
Key differences from the shade window:
- Fixed height — uses
SystemBarUtils.getStatusBarHeight(), not MATCH_PARENT - Always not focusable — uses
FLAG_NOT_FOCUSABLEwithout conditional override - Insets provider — provides
statusBars(),tappableElement(), andmandatorySystemGestures()insets to the system - Per-rotation params —
paramsForRotation[4]stores layout params for all rotations - Color space agnostic —
PRIVATE_FLAG_COLOR_SPACE_AGNOSTICalways set
State Management
The status bar window has a simpler state model with only three fields:
| Field | Purpose |
|---|---|
mForceStatusBarVisible |
Force visibility regardless of system state |
mIsLaunchAnimationRunning |
Launch animation expands window to full screen |
mOngoingProcessRequiresStatusBarVisible |
Ongoing process needs status bar |
When any of these is true, forciblyShownTypes |= WindowInsets.Type.statusBars() is set
to ensure the status bar remains visible even when apps request immersive mode.
78.5 Z-Order Behavior and System Window Interaction
The window type determines Z-order within the window manager’s layer assignment system.
flowchart TB
subgraph "Z-Order Stack (higher = on top)"
direction TB
N["TYPE_NOTIFICATION_SHADE<br/>FIRST_SYSTEM_WINDOW + 40 = 2040"]
S["TYPE_STATUS_BAR_ADDITIONAL<br/>FIRST_SYSTEM_WINDOW + 41 = 2041"]
P["TYPE_STATUS_BAR_SUB_PANEL<br/>FIRST_SYSTEM_WINDOW + 17 = 2017"]
B["TYPE_STATUS_BAR_PANEL<br/>FIRST_SYSTEM_WINDOW + 14 = 2014"]
K["TYPE_KEYGUARD_DIALOG<br/>FIRST_SYSTEM_WINDOW + 9 = 2009"]
SB["TYPE_STATUS_BAR<br/>FIRST_SYSTEM_WINDOW + 0 = 2000"]
end
SB --- K --- B --- P --- N --- S
The notification shade (type 2040) sits above most system windows, ensuring that when expanded, it covers navigation elements and application content. The status bar (type 2000) is the lowest system window, providing a stable anchor beneath all other system UI.
Interaction Patterns:
- When the shade expands,
FLAG_SHOW_WALLPAPERis set if on keyguard/AOD - During doze,
SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWSprevents overlays - The bouncer activates
FLAG_SECUREto prevent screenshots - Heads-up notifications set
FLAG_NOT_TOUCH_MODALfor pass-through touches
78.6 Input Region Management During Shade Expansion
The shade window manages touch regions dynamically based on expansion state:
Focusability Control
applyFocusableFlag() conditionally removes FLAG_NOT_FOCUSABLE based on:
// Focusable when: bouncer showing, keyguard showing, panel expanded, or remote input
boolean panelFocusable = state.notificationShadeFocusable && state.shadeOrQsExpanded;
if (state.bouncerShowing && (state.keyguardOccluded || state.keyguardNeedsInput)
|| state.remoteInputActive || state.glanceableHubShowing) {
// Fully focusable — remove NOT_FOCUSABLE, remove ALT_FOCUSABLE_IM
} else if (state.isKeyguardShowingAndNotOccluded() || panelFocusable) {
// Focusable but with ALT_FOCUSABLE_IM (blocks soft keyboard unless needed)
} else {
// Not focusable — set FLAG_NOT_FOCUSABLE
}
Touch Exclusion Regions
setTouchExclusionRegion(Region) communicates tap exclusion areas to the window session:
IWindowSession session = WindowManagerGlobal.getWindowSession();
session.updateTapExcludeRegion(
IWindow.Stub.asInterface(getWindowRootView().getWindowToken()),
region);
When the shade notifies WMS of expansion changes, it calls
WindowManagerGlobal.getWindowManagerService().onNotificationShadeExpanded(token, isExpanded)
on a background thread, allowing the window manager to adjust input routing for all windows.
Navigation Bar Forcing
When the shade or QS is expanded, or the bouncer is showing, the navigation bar is
forcibly shown: forciblyShownTypes |= WindowInsets.Type.navigationBars().
78.7 Quick Settings Window Integration
Quick Settings does not use a separate window. Instead, QS is embedded as a child
view hierarchy within the notification shade window, managed by QuickSettingsControllerImpl.
QS Expansion Coordination
QuickSettingsControllerImpl tracks its own expansion state:
setExpansionHeight(float)— sets the current QS expansion height in pixelscomputeExpansionFraction()— returns 0.0–1.0 QS expansion fractiongetMinExpansionHeight()— minimum height (collapsed QS quick tiles)
The QS controller communicates with the panel controller through listeners:
flowchart LR
subgraph "Shade Window (TYPE_NOTIFICATION_SHADE)"
NPVC[NotificationPanelViewController]
QSC[QuickSettingsControllerImpl]
NSSLL[NotificationStackScrollLayout]
QSFrag[QS Fragment]
NPVC -->|"setExpansionHeight()"| QSC
QSC -->|"setQsExpansion(fraction)"| QSFrag
QSC -->|"setQsExpansionFraction()"| NSSLL
NPVC -->|"flingExpandsQs(vel)"| QSC
end
subgraph "Separate Window"
SB[StatusBar Window<br/>TYPE_STATUS_BAR]
end
SB -.->|"touch drag down"| NPVC
Parallax Effect: During shade expansion, QS moves at a reduced rate
(QS_PARALLAX_AMOUNT = 0.175f), creating a depth illusion where the QS panel appears
to be behind the notification list.
ShadeControllerImpl Coordination
ShadeControllerImpl bridges between system events and the panel:
makeExpandedVisible(force)— setspanelVisible = trueon the window controllermakeExpandedInvisible()— collapses panel, resets visibility, trims memoryanimateCollapseShade()— releases focus first, then collapses with animation- Listens for
OpenCloseListenercallbacks from the panel controller
78.8 Key Source Files
| File | Description |
|---|---|
frameworks/base/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java |
Central shade window state manager (1131 lines) |
frameworks/base/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt |
Shade window state data class with 30+ fields |
frameworks/base/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLayoutParams.kt |
Shade window LayoutParams factory |
frameworks/base/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java |
Gesture handling, expansion tracking (4329 lines) |
frameworks/base/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java |
Shade open/close coordinator (407 lines) |
frameworks/base/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java |
QS expansion within shade window (2479 lines) |
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java |
TYPE_STATUS_BAR window manager (483 lines) |
frameworks/base/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java |
Root view of shade window |
frameworks/base/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java |
View controller for shade window |
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java |
Root view of status bar window |
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt |
Status bar window state tracking |
frameworks/base/core/java/android/view/WindowManager.java |
Window type constants: TYPE_STATUS_BAR (2000), TYPE_NOTIFICATION_SHADE (2040) |
Part XX: Power and Performance
79. Power Management and Window System
The Android power management system orchestrates a complex interaction between device
wakefulness states, display hardware, and window visibility. Power state transitions
propagate through PowerManagerService → DisplayPowerController → DisplayPolicy,
ultimately determining which windows can be drawn and which must be hidden. This section
traces the full path from power button press to window visibility change.
79.1 Power State Machine and Window Visibility
PowerManagerService manages device wakefulness through four states defined in
PowerManagerInternal:
| Wakefulness State | Value | Description |
|---|---|---|
WAKEFULNESS_ASLEEP |
0 | Device fully asleep, display off |
WAKEFULNESS_AWAKE |
1 | Device awake, display on, user active |
WAKEFULNESS_DREAMING |
2 | Dream/screensaver active |
WAKEFULNESS_DOZING |
3 | Low-power doze mode (AOD possible) |
The global wakefulness is the highest wakefulness across all PowerGroup instances,
ordered: AWAKE > DREAMING > DOZING > ASLEEP. This allows multi-display devices to
have independent power states per display group.
State Transition Flow
stateDiagram-v2
[*] --> AWAKE : Boot / Wake
AWAKE --> DREAMING : Idle timeout + dream enabled
AWAKE --> DOZING : goToSleep() + doze enabled
AWAKE --> ASLEEP : goToSleep() without doze
DREAMING --> DOZING : Dream ends + doze enabled
DREAMING --> AWAKE : User interaction
DREAMING --> ASLEEP : Dream ends without doze
DOZING --> ASLEEP : Doze finishes
DOZING --> AWAKE : wakeUp()
ASLEEP --> AWAKE : wakeUp()
Wakefulness Change Processing
When wakefulness changes, PowerManagerService.updateGlobalWakefulnessLocked() executes a
three-phase update:
// Phase 1: Update the PowerGroup wakefulness
powerGroup.setWakefulnessLocked(wakefulness, eventTime, uid, reason, ...);
// Phase 2: Notify attention detector
mAttentionDetector.onWakefulnessChangeStarted(newWakefulness);
// Phase 3: Post-change bookkeeping
switch (newWakefulness) {
case WAKEFULNESS_AWAKE:
mNotifier.onWakeUp(reason, details, uid, opPackageName, opUid);
break;
case WAKEFULNESS_ASLEEP:
case WAKEFULNESS_DOZING:
// Count and report wake locks to be cleared
EventLogTags.writePowerSleepRequested(numWakeLocksCleared);
break;
}
Each wakefulness change sets mDirty |= DIRTY_WAKEFULNESS, triggering
updatePowerStateLocked() which propagates the change to the display and window systems.
79.2 Dream Windows: ACTIVITY_TYPE_DREAM Lifecycle
Dreams (screensavers) are managed by DreamManagerService and displayed as activities
with ACTIVITY_TYPE_DREAM. The dream lifecycle involves three layers:
DreamManagerService — System service that manages dream selection and lifecycle:
// frameworks/base/services/core/java/com/android/server/dreams/DreamManagerService.java
private void startDreamLocked(ComponentName name, boolean isPreviewMode,
boolean canDoze, int userId, String reason) {
mController.startDream(dreamToken, name, isPreviewMode, canDoze, userId,
wakeLock, overlayComponentName, reason);
}
DreamController — Manages the actual binding to the dream service:
// frameworks/base/services/core/java/com/android/server/dreams/DreamController.java
public void startDream(Binder token, ComponentName name, ...) {
Intent intent = new Intent(DreamService.SERVICE_INTERFACE);
// Binds to the dream service, calls attach() when connected
}
private void attach(IDreamService service) {
service.attach(token, canDoze, dozeScreenState, dozeScreenBrightness, ...);
}
Dream Activity Launch — Dreams are started as activities through
ActivityTaskManagerInternal.startDreamActivity(), which creates a task with
ACTIVITY_TYPE_DREAM:
sequenceDiagram
participant PMS as PowerManagerService
participant DMS as DreamManagerService
participant DC as DreamController
participant DS as DreamService
participant ATM as ActivityTaskManager
PMS->>DMS: startDream(doze=false)
DMS->>DMS: startDreamLocked(component)
DMS->>DC: startDream(token, name, ...)
DC->>DS: bind + attach(token, canDoze, ...)
DS->>DMS: startDreamActivity(intent)
DMS->>ATM: startDreamActivity(intent, uid)
ATM->>ATM: Create task with ACTIVITY_TYPE_DREAM
Note over ATM: Dream window visible<br/>in dream task stack
Dream Window Behavior:
- Dreams run as full-screen activities in their own task
- The dream task uses
ACTIVITY_TYPE_DREAM, which receives special treatment in the window manager for Z-ordering (above regular activities, below keyguard) - When the user interacts with the device during a dream, the system transitions
to
WAKEFULNESS_AWAKEand the dream is stopped viastopDreamLocked() - Dream activities can be doze-capable (
canDoze = true), allowing them to transition the device into doze mode when the dream ends
79.3 Always-On Display: Window Management in Low-Power Mode
AOD (Always-On Display) is implemented through the DozeMachine state machine in
SystemUI’s doze package. Unlike regular dreams, AOD keeps minimal UI visible while the
display operates in a low-power state.
DozeMachine States
DozeMachine defines a comprehensive set of states that map to display hardware states:
| DozeMachine State | Display State | Description |
|---|---|---|
DOZE |
STATE_OFF |
Screen off, listening for triggers |
DOZE_SUSPEND_TRIGGERS |
STATE_OFF |
Deep doze, no trigger listening |
DOZE_AOD |
STATE_DOZE_SUSPEND |
AOD visible, low-power display mode |
DOZE_AOD_PAUSED |
STATE_OFF |
AOD temporarily off (e.g., pocket) |
DOZE_AOD_PAUSING |
STATE_DOZE_SUSPEND |
Transitioning to paused |
DOZE_PULSING |
STATE_ON |
Brief pulse wakeup for notification |
DOZE_PULSING_BRIGHT |
STATE_ON |
Pulse with bright wallpaper |
DOZE_AOD_DOCKED |
STATE_ON |
Docked AOD (charging dock) |
DOZE_AOD_MINMODE |
STATE_ON |
Minimal UI mode |
AOD Window Management
During AOD, the notification shade window remains attached but operates in doze mode.
NotificationShadeWindowControllerImpl applies special flags:
// applyKeyguardFlags() during doze
if (state.dozing) {
mLpChanged.privateFlags |= SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
}
// applyBrightness() during doze
if (state.forceDozeBrightness) {
mLpChanged.screenBrightness = mScreenBrightnessDoze;
}
The shade window is the host for AOD UI content. When dozing, the window controller identifies the shade’s process as the “visible doze UI process” to ensure it receives elevated scheduling priority:
// DisplayPolicy.setAwake() — when not awake but screen still on
if (!awake && mScreenOnFully && mNotificationShade != null) {
WindowProcessController visibleDozeUiProcess = mNotificationShade.getProcess();
mAtmService.mVisibleDozeUiProcess = visibleDozeUiProcess;
}
79.4 DisplayPowerController: Display Power Coordination
DisplayPowerController (3507 lines) bridges between the power manager’s policy
requests and the actual display hardware state. It manages brightness, screen
animations, and display state transitions.
Power Request to Display State
The DisplayPowerRequest carries a policy that maps to display states:
| Policy | Display State | Description |
|---|---|---|
POLICY_OFF |
STATE_OFF |
Display completely off |
POLICY_DOZE |
STATE_DOZE or STATE_DOZE_SUSPEND |
Low-power doze |
POLICY_DIM |
STATE_ON (dimmed) |
Screen on but dimmed |
POLICY_BRIGHT |
STATE_ON (full) |
Screen on at normal brightness |
updatePowerState() Flow
updatePowerState() is the central method that processes power requests:
flowchart TD
A[updatePowerState] --> B[Read pending PowerRequest]
B --> C[updateDisplayState]
C --> D[animateScreenStateChange]
D --> E{Target State?}
E -->|STATE_ON| F[setScreenState ON<br/>dismissColorFade]
E -->|STATE_DOZE| G[Wait for brightness<br/>anim to complete<br/>setScreenState DOZE]
E -->|STATE_DOZE_SUSPEND| H[Transition through<br/>DOZE then SUSPEND]
E -->|STATE_OFF| I[Start ColorFade<br/>off animation<br/>setScreenState OFF]
F --> J[Update brightness]
G --> J
H --> J
I --> J
J --> K[animateScreenBrightness]
K --> L[Report display ready]
The mDozing Flag: DisplayPowerController tracks whether it is in doze mode:
mDozing = state != Display.STATE_ON;
This flag affects brightness calculations, auto-brightness mode selection, and whether the screen-off brightness sensor is active.
79.5 Screen-Off Animations and Transitions
Screen-off transitions use the ColorFade animation system within DisplayPowerController.
The animation creates a full-screen surface that fades to black:
ColorFade Modes:
MODE_COOL_DOWN— electron beam-style animation (legacy)MODE_WARM_UP— reverse of cool-down for screen-onMODE_FADE— simple opacity fade
Transition Sequence for Screen Off:
// animateScreenStateChange() — target == STATE_OFF
mPendingScreenOff = true;
if (mPowerState.getColorFadeLevel() == 0.0f) {
// Already faded — turn off immediately
setScreenState(Display.STATE_OFF, reason);
} else if (performScreenOffTransition
&& mPowerState.prepareColorFade(context, MODE_COOL_DOWN)) {
// Start animated fade to black
mColorFadeOffAnimator.start();
} else {
// Skip animation — jump to black
mColorFadeOffAnimator.end();
}
Doze-to-Non-Doze Blanking: Some display hardware blanks briefly when transitioning
between doze and non-doze states. DisplayPowerController handles this with
mDisplayBlanksAfterDozeConfig:
if (mDisplayBlanksAfterDozeConfig
&& Display.isDozeState(mPowerState.getScreenState())
&& !Display.isDozeState(target)) {
// Insert brief STATE_OFF to mask hardware blank
setScreenState(Display.STATE_OFF, reason, true /*reportOnly*/);
}
79.6 Power State → Visibility Propagation
The chain from power state to window visibility passes through multiple layers:
Display State Constants (from android.view.Display):
| Constant | Value | Description |
|---|---|---|
STATE_OFF |
1 | Display is off |
STATE_ON |
2 | Display fully on |
STATE_DOZE |
3 | Low-power doze mode, can render |
STATE_DOZE_SUSPEND |
4 | Doze, display driver suspended |
STATE_VR |
5 | VR mode display |
STATE_ON_SUSPEND |
6 | On but driver suspended |
Helper methods on Display:
isOffState(state)— true forSTATE_OFFonlyisOnState(state)— true forSTATE_ON,STATE_VR,STATE_ON_SUSPENDisDozeState(state)— true forSTATE_DOZE,STATE_DOZE_SUSPENDisSuspendedState(state)— true forSTATE_OFF,STATE_DOZE_SUSPEND,STATE_ON_SUSPEND
DisplayPolicy Screen State Tracking
DisplayPolicy maintains three volatile flags that track screen state transitions:
// frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java
private volatile boolean mAwake; // Device is awake
private volatile boolean mScreenOnEarly; // Screen turning on started
private volatile boolean mScreenOnFully; // Screen turn-on complete
These flags are updated by callback methods from the power system:
sequenceDiagram
participant PMS as PowerManagerService
participant DPC as DisplayPowerController
participant DP as DisplayPolicy
participant DC as DisplayContent
PMS->>DP: setAwake(false)
DP->>DP: mAwake = false
DP->>DP: Track visible doze UI process
DP->>DC: addSleepToken("Display-off")
PMS->>DP: screenTurnedOff(acquireSleepToken)
DP->>DP: mScreenOnEarly = false
DP->>DP: mScreenOnFully = false
alt acquireSleepToken
DP->>DC: addSleepToken("Display-off")
end
Note over PMS: Later, wakeUp()
PMS->>DP: screenTurningOn(listener)
DP->>DP: mScreenOnEarly = true
DP->>DC: removeSleepToken("Display-off")
PMS->>DP: finishScreenTurningOn()
DP->>DP: mScreenOnFully = true
79.7 Doze Mode Window Restrictions
Doze mode imposes strict restrictions on which windows and activities can be visible. The primary mechanism is the SleepToken system.
SleepToken Mechanics
DisplayContent maintains a list of SleepToken objects. When sleep tokens are present,
activities are paused and cannot show windows:
// frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
static final String DISPLAY_OFF_SLEEP_TOKEN_TAG = "Display-off";
final ArrayList<RootWindowContainer.SleepToken> mAllSleepTokens = new ArrayList<>();
void addSleepToken(String tag) {
if (!addSleepTokenOnly(tag)) return;
// Triggers activity pause and window visibility recalculation
}
void removeSleepToken(String tag) {
mAllSleepTokens.remove(idx);
if (mAllSleepTokens.isEmpty()) {
// Wake activities — they can now show windows
}
}
// Used by isSleeping() check
boolean isSleeping() {
if (hasSleepToken(DISPLAY_OFF_SLEEP_TOKEN_TAG)) return true;
return !mAllSleepTokens.isEmpty() && !mDisplayPolicy.isAwake();
}
The "Display-off" sleep token is the primary mechanism:
- Acquired when
DisplayPolicy.screenTurnedOff(true)is called - Acquired when
DisplayPolicy.setAwake(false)and screen is already off - Released when
DisplayPolicy.screenTurningOn()is called
Activity Visibility During Doze
DisplayContent.shouldSleep() determines whether activities should be paused:
boolean shouldSleep() {
return (getRootTaskCount() == 0 || !mAllSleepTokens.isEmpty())
&& /* additional conditions */;
}
When sleeping, activities check canShowWindows() before drawing. Only specific
window types remain visible during doze:
| Window Category | Visible in Doze? | Mechanism |
|---|---|---|
| Notification shade (AOD UI) | Yes | mVisibleDozeUiProcess elevated priority |
| Keyguard window | Yes | Not subject to sleep tokens |
| System overlay windows | No | SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS |
| Application windows | No | Sleep tokens pause activities |
| Dream windows | Conditional | ACTIVITY_TYPE_DREAM only if canDoze |
| Status bar | Yes | System window, not activity-based |
Doze-Specific Window Flags in the Shade
During doze, the notification shade window applies several special configurations:
- Hide overlay windows:
SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWSprevents third-party overlays from drawing during doze - Screen brightness override:
screenBrightness = mScreenBrightnessDozeforces very low brightness appropriate for AOD - Wallpaper display:
FLAG_SHOW_WALLPAPERis set during doze on keyguard when AOD is enabled:state.dozing && mDozeParameters.getAlwaysOn() - Input feature:
INPUT_FEATURE_DISABLE_USER_ACTIVITYis set on keyguard to prevent accidental screen wakes - Refresh rate capping:
preferredMaxDisplayRefreshRatemay be set tomKeyguardMaxRefreshRateduring doze to save power - Screen orientation lock: Orientation is conditionally forced to
SCREEN_ORIENTATION_NOSENSORduring doze to prevent rotation
79.8 Key Source Files
| File | Description |
|---|---|
frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java |
Central power state machine (7905 lines) |
frameworks/base/core/java/android/os/PowerManager.java |
Power API: GO_TO_SLEEP_REASON_, WAKE_REASON_ constants |
frameworks/base/core/java/android/os/PowerManagerInternal.java |
WAKEFULNESS_* constants |
81.8 Key Source Files
| File | Path | Lines | Role |
|---|---|---|---|
| DisplayPolicy | frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java |
~3,338 | Toast timeout enforcement, overlay opacity limiting, window param adjustment |
| Session | frameworks/base/services/core/java/com/android/server/wm/Session.java |
~1,021 | Per-session overlay tracking, AlertWindowNotification lifecycle |
| AlertWindowNotification | frameworks/base/services/core/java/com/android/server/wm/AlertWindowNotification.java |
178 | Ongoing notification when overlays are active |
| WindowManagerService | frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java |
~10,983 | addWindow() toast handling, permission delegation, global notification control |
| PhoneWindowManager | frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java |
~6,954 | checkAddPermission() — permission/AppOps gate for all window types |
| WindowState | frameworks/base/services/core/java/com/android/server/wm/WindowState.java |
~6,191 | Per-window security state, trusted overlay checks |
82. Game Mode and Performance Hints
82.1 Overview
Android provides a multi-layered system for optimizing game performance that bridges the application framework, window manager, and hardware abstraction layers. GameManagerService manages per-app game mode selection (performance, battery, custom), while SystemPerformanceHinter provides a session-based mechanism for requesting low-level performance optimizations from SurfaceFlinger and the CPU/GPU subsystem. RefreshRatePolicy translates application preferences into display refresh rate decisions, and TaskFpsCallbackController enables per-task frame rate monitoring through native SurfaceFlinger hooks.
82.2 GameManagerService — Game Mode Management
GameManagerService (located in frameworks/base/services/core/java/com/android/server/app/)
is the central service for game-mode management. It implements IGameManagerService.Stub and
manages per-package, per-user game mode configurations.
82.2.1 Game Mode Constants
Game modes are defined in the public GameManager API:
| Mode | Constant | Behavior |
|---|---|---|
GAME_MODE_UNSUPPORTED |
0 | App is not recognized as a game |
GAME_MODE_STANDARD |
1 | Default mode, no interventions |
GAME_MODE_PERFORMANCE |
2 | Maximize performance: higher refresh rate, no downscaling |
GAME_MODE_BATTERY |
3 | Conserve battery: lower refresh rate, resolution downscaling |
GAME_MODE_CUSTOM |
4 | User-customizable interventions |
82.2.2 GamePackageConfiguration
Each game package has a GamePackageConfiguration (line 503) that holds mode-specific settings:
// frameworks/base/services/core/java/com/android/server/app/GameManagerService.java
public static class GamePackageConfiguration {
public static final String METADATA_GAME_MODE_CONFIG = "android.game_mode_config";
// Inner class for per-mode settings
public class GameModeConfiguration {
public static final String SCALING_KEY = "downscaleFactor";
private final @GameMode int mGameMode;
private float mScaling; // Resolution downscale factor
private String mFps; // FPS override (stored as String, parsed to int via Integer.parseInt when needed)
private boolean mUseAngle; // Use ANGLE graphics driver
private int mLoadingBoostDuration; // CPU boost during loading
}
}
The configuration determines whether the system applies window manager downscaling or
FPS capping based on the active game mode. A critical check is
willGamePerformOptimizations():
// Line 811
public boolean willGamePerformOptimizations(@GameMode int gameMode) {
// Returns true if the game handles optimizations itself,
// meaning the system should NOT intervene
}
When an app declares that it will handle optimizations for a given mode, the system skips its own downscaling and FPS interventions for that mode:
mScaling = !mAllowDownscale || willGamePerformOptimizations(mGameMode)
? DEFAULT_SCALING : mScaling;
mFps = mAllowFpsOverride && !willGamePerformOptimizations(mGameMode)
? mFps : 0;
82.2.3 setGameMode() Flow
When a game mode is changed (line 1173):
public void setGameMode(String packageName, @GameMode int gameMode, int userId) {
// 1. Update settings
userSettings.setGameModeLocked(packageName, gameMode);
// 2. Apply interventions (resolution, FPS, ANGLE)
updateInterventions(packageName, gameMode, userId);
// 3. Notify all registered listeners
for (IGameModeListener listener : mGameModeListeners.keySet()) {
listener.onGameModeChanged(packageName, fromGameMode, gameMode, userId);
}
// 4. Persist settings and update intervention list
sendUserMessage(userId, WRITE_SETTINGS, ...);
sendUserMessage(userId, WRITE_GAME_MODE_INTERVENTION_LIST_FILE, ...);
// 5. Log to StatsD
FrameworkStatsLog.write(GAME_MODE_CHANGED, gameUid, callingUid, fromMode, toMode);
}
flowchart TD
User["User selects game mode<br/>(Settings / Game Dashboard)"] --> GMS["GameManagerService.setGameMode()"]
GMS --> Settings["Update GameManagerSettings<br/>per-user, per-package"]
GMS --> Interventions["updateInterventions()"]
Interventions --> Scaling{"Downscale enabled<br/>for this mode?"}
Scaling -->|Yes| WMS["Window Manager<br/>CompatScale applied"]
Scaling -->|No| NoScale["No resolution change"]
Interventions --> FPS{"FPS override<br/>for this mode?"}
FPS -->|Yes| SF["SurfaceFlinger<br/>frame rate cap"]
FPS -->|No| NoFPS["No FPS change"]
Interventions --> Angle{"ANGLE enabled<br/>for this mode?"}
Angle -->|Yes| GPU["Switch to ANGLE<br/>OpenGL ES driver"]
Angle -->|No| NoAngle["Native GPU driver"]
GMS --> Listeners["Notify IGameModeListeners"]
GMS --> Persist["Write settings + intervention list"]
GMS --> Stats["FrameworkStatsLog.GAME_MODE_CHANGED"]
82.2.4 Resolution Downscaling via CompatScale
GameManagerService implements the CompatScaleProvider interface to supply resolution
scaling factors to the window system:
// Line 948-960
private final class LocalService extends GameManagerInternal implements CompatScaleProvider {
@Override
public CompatScale getCompatScale(@NonNull String packageName, int uid) {
float scalingFactor = getResolutionScalingFactor(packageName, userId);
// Returns a CompatScale that WMS uses to downscale the app's window
}
}
This integrates with the COMPAT_SCALE_MODE_GAME compatibility scale mode in the window
manager, allowing transparent resolution reduction without app cooperation.
82.3 SystemPerformanceHinter — Low-Level Performance Hints
SystemPerformanceHinter (416 lines) provides a session-based interface for requesting
performance optimizations from SurfaceFlinger and the ADPF subsystem.
82.3.1 Hint Flags
// frameworks/base/core/java/android/window/SystemPerformanceHinter.java
public static final int HINT_SF_EARLY_WAKEUP = 1 << 0; // Wake SF earlier for compositing
public static final int HINT_SF_FRAME_RATE = 1 << 1; // Force max refresh rate
public static final int HINT_ADPF = 1 << 2; // Boost CPU & GPU clocks
// Convenience combinations
public static final int HINT_SF = HINT_SF_EARLY_WAKEUP | HINT_SF_FRAME_RATE;
public static final int HINT_ALL = HINT_SF_EARLY_WAKEUP | HINT_SF_FRAME_RATE | HINT_ADPF;
| Hint Flag | Scope | Effect |
|---|---|---|
HINT_SF_EARLY_WAKEUP |
Global | Requests SurfaceFlinger to wake up earlier in the vsync cycle, giving more time for composition |
HINT_SF_FRAME_RATE |
Per-display | Sets FRAME_RATE_CATEGORY_HIGH on the display root surface, requesting maximum refresh rate |
HINT_ADPF |
Global | Sends CPU_LOAD_UP hint via Android Dynamic Performance Framework to boost CPU/GPU clocks |
Hints are categorized by scope:
private static final int HINT_PER_DISPLAY = HINT_SF_FRAME_RATE;
private static final int HINT_GLOBAL = HINT_SF_EARLY_WAKEUP | HINT_ADPF;
82.3.2 HighPerfSession Lifecycle
Performance hints are managed through HighPerfSession objects that follow a session pattern:
public class HighPerfSession implements AutoCloseable {
private final @HintFlags int hintFlags;
private final String reason;
private final int displayId;
public void start() {
if (!mActiveSessions.contains(this)) {
startSession(this);
}
}
@Override
public void close() {
endSession(this);
}
}
82.3.3 Session Start — Applying Hints
When a session starts, the hinter applies the appropriate SurfaceControl transactions and ADPF hints:
private void startSession(HighPerfSession session) {
// Per-display: Force high frame rate
if (nowEnabled(oldPerDisplayFlags, newPerDisplayFlags, HINT_SF_FRAME_RATE)) {
SurfaceControl displaySurfaceControl = mDisplayRootProvider
.getRootForDisplay(session.displayId);
mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl,
FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
mTransaction.setFrameRateCategory(displaySurfaceControl,
FRAME_RATE_CATEGORY_HIGH, /* smoothSwitchOnly= */ false);
}
// Global: Request early SurfaceFlinger wakeup
if (nowEnabled(oldGlobalFlags, newGlobalFlags, HINT_SF_EARLY_WAKEUP)) {
mTransaction.setEarlyWakeupStart(mEarlyWakeupInfo);
}
// Global: Boost CPU/GPU via ADPF
if (nowEnabled(oldGlobalFlags, newGlobalFlags, HINT_ADPF)) {
mAdpfSession.sendHint(PerformanceHintManager.Session.CPU_LOAD_UP);
}
mTransaction.applyAsyncUnsafe();
}
82.3.4 Session End — Reverting Hints
private void endSession(HighPerfSession session) {
// Revert frame rate to default
if (nowDisabled(oldPerDisplayFlags, newPerDisplayFlags, HINT_SF_FRAME_RATE)) {
mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl,
FRAME_RATE_SELECTION_STRATEGY_PROPAGATE);
mTransaction.setFrameRateCategory(displaySurfaceControl,
FRAME_RATE_CATEGORY_DEFAULT, /* smoothSwitchOnly= */ false);
}
// Revert early wakeup
if (nowDisabled(oldGlobalFlags, newGlobalFlags, HINT_SF_EARLY_WAKEUP)) {
mTransaction.setEarlyWakeupEnd(mEarlyWakeupInfo);
}
// Reset CPU/GPU boost
if (nowDisabled(oldGlobalFlags, newGlobalFlags, HINT_ADPF)) {
mAdpfSession.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET);
}
mTransaction.applyAsyncUnsafe();
}
sequenceDiagram
participant WM as WindowManager
participant SPH as SystemPerformanceHinter
participant TX as SurfaceControl.Transaction
participant SF as SurfaceFlinger
participant ADPF as ADPF (HAL)
WM->>SPH: startSession(HINT_ALL, displayId, "animation")
SPH->>SPH: Create HighPerfSession
SPH->>SPH: calculateActiveHintFlags()
SPH->>TX: setFrameRateSelectionStrategy(OVERRIDE_CHILDREN)
SPH->>TX: setFrameRateCategory(HIGH)
SPH->>TX: setEarlyWakeupStart(token)
SPH->>ADPF: sendHint(CPU_LOAD_UP)
SPH->>TX: applyAsyncUnsafe()
TX->>SF: Apply all transactions atomically
Note over WM: Animation/operation completes
WM->>SPH: session.close()
SPH->>TX: setFrameRateSelectionStrategy(PROPAGATE)
SPH->>TX: setFrameRateCategory(DEFAULT)
SPH->>TX: setEarlyWakeupEnd(token)
SPH->>ADPF: sendHint(CPU_LOAD_RESET)
SPH->>TX: applyAsyncUnsafe()
TX->>SF: Revert to normal operation
82.4 ADPF (Android Dynamic Performance Framework) Integration
ADPF provides a standardized interface between the framework and hardware-specific performance controllers. The integration in the window system works through two paths:
82.4.1 Direct ADPF via SystemPerformanceHinter
The HINT_ADPF flag uses PerformanceHintManager.Session to communicate with the HAL:
// SystemPerformanceHinter.java
private @Nullable PerformanceHintManager.Session mAdpfSession;
public void setAdpfSession(PerformanceHintManager.Session adpfSession) {
mAdpfSession = adpfSession;
}
The ADPF session is externally managed — the caller is responsible for creating and
destroying it. SystemPerformanceHinter only sends CPU_LOAD_UP and CPU_LOAD_RESET hints
to boost and release performance during active sessions.
82.4.2 Game Mode ADPF Integration
GameManagerService supports loading boost duration as a per-mode configuration:
// GameModeConfiguration
private int mLoadingBoostDuration;
mLoadingBoostDuration = willGamePerformOptimizations(mGameMode)
? -1 : mLoadingBoostDuration;
When a game enters a loading screen, the system can provide a CPU/GPU boost for the configured duration, helping games load faster without requiring app-specific optimization.
graph TB
subgraph "Application Layer"
Game[Game App]
GD[Game Dashboard UI]
end
subgraph "Framework Layer"
GM[GameManager API]
GMS[GameManagerService]
SPH[SystemPerformanceHinter]
PHM[PerformanceHintManager]
end
subgraph "Native Layer"
SF[SurfaceFlinger]
ADPF_HAL[ADPF Power HAL]
end
subgraph "Hardware"
CPU[CPU Governor]
GPU[GPU Frequency]
Display[Display Controller]
end
Game --> GM
GD --> GMS
GM --> GMS
GMS -->|"Resolution scaling"| CSP[CompatScaleProvider]
CSP -->|"CompatScale"| WMS[Window Manager]
GMS -->|"FPS override"| SF
GMS -->|"Loading boost"| PHM
SPH -->|"SurfaceControl TX"| SF
SPH -->|"CPU_LOAD_UP"| PHM
PHM --> ADPF_HAL
ADPF_HAL --> CPU
ADPF_HAL --> GPU
SF --> Display
82.5 RefreshRatePolicy — Display Refresh Rate Selection
RefreshRatePolicy (321 lines) determines the optimal refresh rate for each window based
on application preferences, focus state, and system-level policies.
82.5.1 Layer Priority System
SurfaceFlinger uses a priority system to resolve conflicts when multiple windows request different refresh rates:
// frameworks/base/services/core/java/com/android/server/wm/RefreshRatePolicy.java
static final int LAYER_PRIORITY_UNSET = -1;
static final int LAYER_PRIORITY_FOCUSED_WITH_MODE = 0; // Highest priority
static final int LAYER_PRIORITY_FOCUSED_WITHOUT_MODE = 1;
static final int LAYER_PRIORITY_NOT_FOCUSED_WITH_MODE = 2; // Lowest active priority
Priority calculation:
int calculatePriority(WindowState w) {
boolean isFocused = w.isFocused();
int preferredModeId = getPreferredModeId(w);
if (isFocused && preferredModeId > 0) return LAYER_PRIORITY_FOCUSED_WITH_MODE;
if (isFocused && preferredModeId == 0) return LAYER_PRIORITY_FOCUSED_WITHOUT_MODE;
if (!isFocused && preferredModeId > 0) return LAYER_PRIORITY_NOT_FOCUSED_WITH_MODE;
return LAYER_PRIORITY_UNSET;
}
82.5.2 Frame Rate Vote Mechanism
Each window maintains a FrameRateVote that is communicated to SurfaceFlinger:
public static class FrameRateVote {
float mRefreshRate;
@Surface.FrameRateCompatibility int mCompatibility;
@SurfaceControl.FrameRateSelectionStrategy int mSelectionStrategy;
}
The updateFrameRateVote() method applies the following decision cascade:
- Switching disabled → Reset (no preference)
- Insets animation running → Reset (let VRI control)
- App set preferredDisplayModeId → Exact match with
OVERRIDE_CHILDRENstrategy - App set preferredRefreshRate → Default compatibility with
OVERRIDE_CHILDREN - App on high-refresh-rate denylist → Force low refresh rate mode
- None of the above → Reset to default
flowchart TD
Start["updateFrameRateVote(window)"] --> SW{"Refresh rate switching<br/>enabled?"}
SW -->|NONE| Reset1["Reset vote"]
SW -->|Enabled| Insets{"Insets animation<br/>running?"}
Insets -->|Yes| Reset2["Reset (let VRI control)"]
Insets -->|No| ModeId{"preferredDisplayModeId > 0?"}
ModeId -->|Yes| Exact["Vote: exact mode refresh rate<br/>OVERRIDE_CHILDREN"]
ModeId -->|No| PrefRate{"preferredRefreshRate > 0?"}
PrefRate -->|Yes| Default["Vote: preferred rate<br/>OVERRIDE_CHILDREN"]
PrefRate -->|No| Deny{"On HighRefreshRate<br/>Denylist?"}
Deny -->|Yes| Low["Vote: low refresh rate mode<br/>EXACT + OVERRIDE_CHILDREN"]
Deny -->|No| Reset3["Reset (no preference)"]
82.5.3 HighRefreshRateDenylist
HighRefreshRateDenylist (103 lines) maintains a list of packages that should be restricted
to the low refresh rate. This is loaded from device configuration and DeviceConfig:
class HighRefreshRateDenylist {
static HighRefreshRateDenylist create(@NonNull Resources r) {
return new HighRefreshRateDenylist(r, DeviceConfigInterface.REAL);
}
boolean isDenylisted(String packageName) { ... }
}
82.5.4 Camera-Aware Refresh Rate
When apps use the camera, RefreshRatePolicy supports clamping both min and max refresh rates
to match the camera capture rate:
float getPreferredMinRefreshRate(WindowState w) {
String packageName = w.getOwningPackage();
RefreshRateRange range = mNonHighRefreshRatePackages.get(packageName);
if (range != null) return range.min;
return 0;
}
float getPreferredMaxRefreshRate(WindowState w) {
RefreshRateRange range = mNonHighRefreshRatePackages.get(packageName);
if (range != null) return range.max;
return 0;
}
82.6 TaskFpsCallbackController — Per-Task FPS Monitoring
TaskFpsCallbackController (76 lines) provides a mechanism for clients to monitor the
actual frame rate of a specific task by registering native callbacks with SurfaceFlinger.
82.6.1 Architecture
// frameworks/base/services/core/java/com/android/server/wm/TaskFpsCallbackController.java
final class TaskFpsCallbackController {
private final HashMap<IBinder, Long> mTaskFpsCallbacks; // binder → native ptr
private final HashMap<IBinder, IBinder.DeathRecipient> mDeathRecipients;
void registerListener(int taskId, ITaskFpsCallback callback) {
final long nativeListener = nativeRegister(callback, taskId);
mTaskFpsCallbacks.put(binder, nativeListener);
// Link to death for automatic cleanup
binder.linkToDeath(deathRecipient, 0);
}
void unregisterListener(ITaskFpsCallback callback) {
nativeUnregister(mTaskFpsCallbacks.get(binder));
mTaskFpsCallbacks.remove(binder);
}
private static native long nativeRegister(ITaskFpsCallback callback, int taskId);
private static native void nativeUnregister(long ptr);
}
82.6.2 Native Integration
The JNI methods register a callback with SurfaceFlinger’s frame timing infrastructure:
nativeRegister()— Creates a native listener that hooks into SF’s per-layer frame stats, filtered by the task’s layer ID- SurfaceFlinger reports frame completion times for surfaces belonging to the specified task
- The native listener invokes
ITaskFpsCallback.onFpsReported()via JNI back to Java nativeUnregister()— Removes the native listener and stops frame stat collection
This enables the Game Dashboard and other system components to display real-time FPS for the currently running game without requiring app cooperation.
sequenceDiagram
participant GD as Game Dashboard
participant TFCC as TaskFpsCallbackController
participant JNI as JNI Layer
participant SF as SurfaceFlinger
GD->>TFCC: registerListener(taskId, callback)
TFCC->>JNI: nativeRegister(callback, taskId)
JNI->>SF: Register frame timing listener for task layers
loop Every frame
SF->>SF: Composite frame for task
SF->>JNI: Report frame timing data
JNI->>GD: callback.onFpsReported(fps)
end
GD->>TFCC: unregisterListener(callback)
TFCC->>JNI: nativeUnregister(ptr)
JNI->>SF: Remove frame timing listener
82.6.3 Death Recipient Cleanup
The controller uses IBinder.DeathRecipient to handle client process death:
final IBinder.DeathRecipient deathRecipient = () -> unregisterListener(callback);
binder.linkToDeath(deathRecipient, 0);
If the Game Dashboard or monitoring client crashes, the native SurfaceFlinger listener is automatically cleaned up, preventing resource leaks.
82.7 End-to-End: Game Mode Activation
When a user switches to Performance mode for a game:
- GameManagerService updates the game mode setting and applies interventions
- Resolution downscaling is applied via
CompatScalethrough the window manager - FPS override is communicated to SurfaceFlinger
- RefreshRatePolicy picks up the app’s preferred refresh rate and votes for high mode
- SystemPerformanceHinter may be used for additional SF early wakeup and ADPF boosts
- TaskFpsCallbackController allows Game Dashboard to show real-time FPS to confirm the mode is active
frameworks/base/services/core/java/com/android/server/display/DisplayPowerController.java |
Display power coordination, brightness, ColorFade (3507 lines) |
frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java |
Screen state tracking, sleep token management (3338 lines) |
frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java |
SleepToken list, activity sleep management |
frameworks/base/core/java/android/view/Display.java |
STATE_ON/OFF/DOZE/DOZE_SUSPEND constants |
frameworks/base/services/core/java/com/android/server/dreams/DreamManagerService.java |
Dream lifecycle management |
frameworks/base/services/core/java/com/android/server/dreams/DreamController.java |
Dream service binding and attach |
frameworks/base/core/java/android/service/dreams/DreamService.java |
Dream service base class |
frameworks/base/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java |
DozeMachine state machine for AOD |
frameworks/base/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java |
Doze display state management |
frameworks/base/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java |
AOD configuration policy |
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java |
Doze behavior parameters |
Part XXI: Task Management and System Alerts
80. Task Affinity and Launch Modes
80.1 Overview
Android’s activity launch-mode system determines how activities are placed into tasks and how
tasks are created, reused, or brought to the foreground. This mechanism sits at the intersection
of the Activity Manager and the Window Manager: every task-routing decision directly affects
window Z-ordering, focus assignment, and transition animations. The primary orchestrator is
ActivityStarter, which evaluates launch modes, intent flags, and task affinity to decide the
target task for each new activity launch.
80.2 Launch Mode Definitions
Android defines five launch modes via android:launchMode in the manifest, mapped to constants
in ActivityInfo:
| Launch Mode | Constant | Behavior |
|---|---|---|
standard |
LAUNCH_MULTIPLE |
Default. A new instance is created every time, placed in the caller’s task. |
singleTop |
LAUNCH_SINGLE_TOP |
If the same activity is already at the top of the task, onNewIntent() is called instead of creating a new instance. |
singleTask |
LAUNCH_SINGLE_TASK |
Only one instance exists per task affinity. Always forces FLAG_ACTIVITY_NEW_TASK. Clears activities above it when re-activated. |
singleInstance |
LAUNCH_SINGLE_INSTANCE |
The activity is the sole member of its task. No other activity can be launched into the same task. |
singleInstancePerTask |
LAUNCH_SINGLE_INSTANCE_PER_TASK |
One instance per task, but unlike singleInstance, allows multiple tasks. Automatically adds FLAG_ACTIVITY_NEW_TASK. |
The ActivityRecord stores the resolved launch mode:
// frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
int launchMode; // the launch mode activity attribute (line 517)
This field is populated from the ActivityInfo during activity creation:
launchMode = aInfo.launchMode; // line 2004
80.3 Task Affinity Mechanism
Task affinity determines which task an activity prefers to belong to. The Task class
maintains three affinity-related fields:
// frameworks/base/services/core/java/com/android/server/wm/Task.java
String affinity; // Current affinity name; may change (line 316)
String rootAffinity; // Initial base affinity; immutable after first set (line 317)
String mWindowLayoutAffinity; // Launch-param affinity for saving layout (line 318)
Intent affinityIntent; // Intent of affinity-moved activity that started this task (line 324)
When Task.setIntent() is called, affinity is assigned from the activity’s taskAffinity:
// Task.java line 986-995
private void setIntent(Intent _intent, ActivityInfo info) {
affinity = info.taskAffinity;
// rootAffinity is locked at first assignment
rootAffinity = affinity;
}
The rootAffinity is set only once — when the task’s root activity is first established — and
never changes. This allows the system to match tasks by their original affinity even if later
activities have different affinities.
graph TD
subgraph "Task Affinity Resolution"
A[Activity Launch Request] --> B{Has taskAffinity<br/>in manifest?}
B -->|Yes| C[Use declared<br/>taskAffinity]
B -->|No| D[Default: package name]
C --> E{Existing task with<br/>matching affinity?}
D --> E
E -->|Yes| F[Route to existing task]
E -->|No| G[Create new Task]
F --> H[Task.affinity updated<br/>rootAffinity preserved]
G --> I[Task.affinity = taskAffinity<br/>rootAffinity = taskAffinity]
end
80.4 ActivityStarter — The Task Routing Engine
ActivityStarter (3,788 lines) is the central class that orchestrates activity launch decisions.
The launch pipeline proceeds through several key methods:
80.4.1 startActivityUnchecked()
This is the top-level entry point for task-routing decisions (line 1686):
private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
int startFlags, ActivityOptions options, Task inTask,
TaskFragment inTaskFragment, BalVerdict balVerdict,
NeededUriGrants intentGrants, int realCallingUid,
Transition transition, boolean isIndependentLaunch)
It defers window layout (mService.deferWindowLayout()), collects the activity into the
current transition, then delegates to startActivityInner().
80.4.2 startActivityInner()
The core decision engine (line 1934). The sequence is:
setInitialState()— Initializes launch parameters from the requestcomputeLaunchingTaskFlags()— Resolves final launch flags based on mode and sourceresolveReusableTask()— Finds an existing task to reusecomputeTargetTask()— Falls back to computing a target if no reuse foundrecycleTask()— Reuses an existing task, clearing/updating as neededdeliverToCurrentTopIfNeeded()— Handles singleTop deliverysetNewTask()oraddOrReparentStartingActivity()— Places the activity
flowchart TD
Start["startActivityInner()"] --> Init["setInitialState()"]
Init --> Flags["computeLaunchingTaskFlags()"]
Flags --> Resolve["resolveReusableTask()"]
Resolve --> Target{"reusedTask != null?"}
Target -->|Yes| Recycle["recycleTask()<br/>Clear/update existing task"]
Target -->|No| Compute["computeTargetTask()"]
Compute --> NewCheck{"targetTask == null?"}
NewCheck -->|Yes| NewTask["setNewTask()<br/>Create Task + WindowContainer"]
NewCheck -->|No| AddTo["addOrReparentStartingActivity()<br/>Insert into existing task"]
Recycle --> Comply["complyActivityFlags()<br/>Handle CLEAR_TOP, etc."]
Comply --> Deliver["deliverToCurrentTopIfNeeded()"]
Deliver --> Resume["Resume target root task"]
NewTask --> Resume
AddTo --> Resume
Resume --> Window["moveToFront() → Window Z-order update"]
80.4.3 computeLaunchingTaskFlags()
This method (line 2897) resolves the final set of intent flags based on launch mode and calling context. Key rules:
IF no source activity AND no explicit inTask:
→ Force FLAG_ACTIVITY_NEW_TASK (non-Activity context)
IF source activity has launchMode == LAUNCH_SINGLE_INSTANCE:
→ Force FLAG_ACTIVITY_NEW_TASK (child must be in own task)
IF target activity is LAUNCH_SINGLE_INSTANCE or LAUNCH_SINGLE_TASK:
→ Force FLAG_ACTIVITY_NEW_TASK (must be in own task)
IF target is LAUNCH_SINGLE_INSTANCE_PER_TASK:
→ Force FLAG_ACTIVITY_NEW_TASK
80.4.4 resolveReusableTask()
This method (line 3002) determines whether to place the activity in an existing task:
boolean putIntoExistingTask = ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0
&& (mLaunchFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
|| isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK);
For LAUNCH_SINGLE_INSTANCE, it does a global search — there can only be one instance system-wide.
For LAUNCH_SINGLE_TASK, it searches by affinity using RootWindowContainer.findTask().
80.5 Intent Flag Effects on the Window System
| Flag | Effect on Task/Window Routing |
|---|---|
FLAG_ACTIVITY_NEW_TASK |
Creates or reuses a task with matching affinity. Creates a new Task WindowContainer if none exists. |
FLAG_ACTIVITY_CLEAR_TASK |
Combined with NEW_TASK: calls performClearTaskForReuse() to destroy all existing activities in the target task. |
FLAG_ACTIVITY_CLEAR_TOP |
Calls Task.performClearTop() to finish activities above the target, then delivers onNewIntent(). |
FLAG_ACTIVITY_SINGLE_TOP |
If top of task matches, delivers onNewIntent() instead of creating a new instance. |
FLAG_ACTIVITY_MULTIPLE_TASK |
With NEW_TASK: always creates a new task even if affinity matches an existing one. |
FLAG_ACTIVITY_NEW_DOCUMENT |
Creates a new task in the document-centric recents view. |
FLAG_ACTIVITY_REORDER_TO_FRONT |
Moves an existing instance of the activity to the top of its task without creating a new one. |
FLAG_ACTIVITY_TASK_ON_HOME |
When the task finishes, the user returns to the home screen. |
FLAG_ACTIVITY_LAUNCH_ADJACENT |
In multi-window: launches into an adjacent split. Requires NEW_TASK and a valid source. |
80.6 Window System Impact: Task Routing → Window Z-Order
Every task-routing decision has direct window system consequences:
sequenceDiagram
participant App as Application
participant AS as ActivityStarter
participant Task as Task (WindowContainer)
participant RootTask as RootTask
participant TDA as TaskDisplayArea
participant DC as DisplayContent
App->>AS: startActivity(intent)
AS->>AS: computeLaunchingTaskFlags()
AS->>AS: resolveReusableTask()
alt New Task Required
AS->>RootTask: reuseOrCreateTask()
RootTask->>Task: new Task(affinity, intent)
Task->>TDA: addChild(task, toTop)
TDA->>DC: Update Z-order hierarchy
else Existing Task Found
AS->>Task: recycleTask() / addOrReparentStartingActivity()
Task->>RootTask: moveToFront("reuseOrNewTask")
RootTask->>TDA: positionChildAt(TOP)
end
AS->>DC: Resume → requestTraversal()
DC->>DC: Assign layers, update focus, start transition
When setNewTask() creates a new Task, it becomes a child of the RootTask WindowContainer:
// ActivityStarter.java line 3207
private void setNewTask(Task taskToAffiliate) {
final boolean toTop = !mLaunchTaskBehind && !avoidMoveToFront();
final Task task = mTargetRootTask.reuseOrCreateTask(
mStartActivity.info, mIntent, mVoiceSession,
mVoiceInteractor, toTop, mStartActivity, mSourceRecord, mOptions);
addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask");
}
The toTop parameter determines whether the new task is placed at the top of the Z-order,
directly affecting which window is visible and receives input focus.
80.7 Task Reparenting and Affinity Resolution
Task reparenting occurs when an activity needs to move between tasks based on affinity. The
Task.reparent() method (line 878) handles moving a task between root tasks:
boolean reparent(Task preferredRootTask, boolean toTop,
@ReparentMoveRootTaskMode int moveRootTaskMode, boolean animate, ...)
Reparenting modes control how the root task is moved:
| Mode | Constant | Behavior |
|---|---|---|
| Move to front | REPARENT_MOVE_ROOT_TASK_TO_FRONT |
Root task moves to front of display |
| Keep in place | REPARENT_KEEP_ROOT_TASK_AT_FRONT |
Root task stays at current position |
| Do not move | REPARENT_LEAVE_ROOT_TASK_IN_PLACE |
No root task movement during reparent |
The complyActivityFlags() method (line 2529) handles the complex flag interactions that can
trigger activity clearing and reparenting. For FLAG_ACTIVITY_CLEAR_TOP combined with
singleTask or singleInstance:
if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
|| isDocumentLaunchesIntoExisting(mLaunchFlags)
|| isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK,
LAUNCH_SINGLE_INSTANCE_PER_TASK)) {
final ActivityRecord clearTop = targetTask.performClearTop(mStartActivity,
mLaunchFlags, finishCount);
if (clearTop != null && !clearTop.finishing) {
deliverNewIntent(clearTop, intentGrants);
}
}
80.8 The recycleTask() Flow
When an existing task is found for reuse, recycleTask() (line 2384) orchestrates the update:
- Validates user ID matches (cross-user tasks cannot be recycled)
- Updates the task’s base intent if it was affinity-created
- Calls
setTargetRootTaskIfNeeded()to ensure proper root task positioning - Delegates to
complyActivityFlags()for clearing/reordering - Handles screen-on flags for visibility transitions
int recycleTask(Task targetTask, ActivityRecord targetTaskTop,
Task reusedTask, NeededUriGrants intentGrants) {
if (targetTask.mUserId != mStartActivity.mUserId) {
mAddingToTask = true;
return START_SUCCESS;
}
if (reusedTask != null && targetTask.intent == null) {
// Task was created by affinity movement — assign base intent now
targetTask.setIntent(mStartActivity);
}
complyActivityFlags(targetTask, reusedActivity, intentGrants);
...
}
80.9 singleInstancePerTask: The Hybrid Mode
Introduced in Android 12, LAUNCH_SINGLE_INSTANCE_PER_TASK provides a middle ground between
singleTask and singleInstance. Key behaviors in ActivityStarter:
// Line 2742-2745
if (mLaunchMode == LAUNCH_SINGLE_INSTANCE_PER_TASK) {
mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
}
During resolveReusableTask(), it validates that the found task’s root activity matches:
if (intentActivity != null && mLaunchMode == LAUNCH_SINGLE_INSTANCE_PER_TASK
&& !intentActivity.getTask().getRootActivity().mActivityComponent.equals(
mStartActivity.mActivityComponent)) {
intentActivity = null; // Don't reuse — different root activity
}
This allows multiple tasks with the same singleInstancePerTask activity as root, unlike
singleInstance which enforces a global singleton.
graph LR
subgraph "singleInstance"
SI_T1["Task A<br/>Activity X only"]
end
subgraph "singleTask"
ST_T1["Task B<br/>Activity Y (root)<br/>+ Activity Z<br/>+ Activity W"]
end
subgraph "singleInstancePerTask"
SIPT_T1["Task C<br/>Activity Q (root)<br/>+ Activity R"]
SIPT_T2["Task D<br/>Activity Q (root)<br/>+ Activity S"]
end
80.10 Key Source Files
| File | Path | Lines | Role |
|---|---|---|---|
| ActivityStarter | frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java |
3,788 | Central launch-mode and task-routing engine |
| Task | frameworks/base/services/core/java/com/android/server/wm/Task.java |
~7,190 | Task container with affinity fields, reparent logic |
| ActivityRecord | frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java |
~9,000 | Per-activity state including launchMode field |
| RootWindowContainer | frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java |
~3,972 | findActivity() / findTask() for task resolution |
| TaskDisplayArea | frameworks/base/services/core/java/com/android/server/wm/TaskDisplayArea.java |
~1,800 | Manages root task ordering within a display |
| ActivityInfo | frameworks/base/core/java/android/content/pm/ActivityInfo.java |
~2,587 | LAUNCH_SINGLE_TASK, LAUNCH_SINGLE_INSTANCE constants |
81. Toast and System Alert Windows
81.1 Overview
Toast windows and system alert windows (overlays) occupy a sensitive position in the Android
window system. They float above application windows, making them powerful for notifications but
also potential vectors for overlay attacks (tapjacking). Android enforces a layered security
model: toasts are constrained with timeouts and input-blocking, while system alert windows
require explicit SYSTEM_ALERT_WINDOW permission, AppOps tracking, and user-visible
notifications. This section covers how the Window Manager enforces these policies at the
framework level.
81.2 TYPE_TOAST — Special Window Handling
Toast windows (TYPE_TOAST, value 2005) receive unique treatment in DisplayPolicy.adjustWindowParamsLw():
// frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java line 1011-1034
case TYPE_TOAST:
// Enforce timeout limits
if (attrs.hideTimeoutMilliseconds < 0
|| attrs.hideTimeoutMilliseconds > TOAST_WINDOW_TIMEOUT) {
attrs.hideTimeoutMilliseconds = TOAST_WINDOW_TIMEOUT;
}
// Accessibility-aware timeout extension
attrs.hideTimeoutMilliseconds = mAccessibilityManager.getRecommendedTimeoutMillis(
(int) attrs.hideTimeoutMilliseconds,
AccessibilityManager.FLAG_CONTENT_TEXT);
// Toasts must not be clickable
attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
// Untrusted apps cannot customize toast animation
if (!win.mSession.mCanAddInternalSystemWindow) {
attrs.windowAnimations = R.style.Animation_Toast;
}
break;
81.2.1 Toast Security Constraints
| Constraint | Mechanism | Purpose |
|---|---|---|
| Timeout cap | TOAST_WINDOW_TIMEOUT (from PhoneWindowManager) |
Prevents indefinite toast display |
| Not touchable | FLAG_NOT_TOUCHABLE forced on |
Prevents toast windows from intercepting user taps |
| Animation lock | R.style.Animation_Toast for untrusted apps |
Prevents deceptive custom animations |
| Input drop | DropInputMode.ALL via setDropInputModePolicy() |
Blocks all input to toast hierarchy |
| Duplicate limit | canAddToastWindowForUid() check |
Only one toast window per UID at a time |
| Token required | Apps targeting > Android N MR1 must use token | Prevents arbitrary toast window creation |
The input-blocking is additionally enforced at the SurfaceControl level:
// DisplayPolicy.java setDropInputModePolicy()
public void setDropInputModePolicy(WindowState win, LayoutParams attrs) {
if (attrs.type == TYPE_TOAST
&& (attrs.privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) == 0) {
mService.mTransactionFactory.get()
.setDropInputMode(win.getSurfaceControl(), DropInputMode.ALL).apply();
}
}
81.2.2 Toast Lifecycle in WindowManagerService
When a toast window is added via addWindow() (WMS line 1877), additional scheduling occurs:
if (type == TYPE_TOAST) {
if (!displayContent.canAddToastWindowForUid(callingUid)) {
return WindowManagerGlobal.ADD_DUPLICATE_ADD;
}
// Schedule hiding based on focus state
if (addToastWindowRequiresToken
|| (attrs.flags & FLAG_NOT_FOCUSABLE) == 0
|| displayContent.mCurrentFocus == null
|| displayContent.mCurrentFocus.mOwnerUid != callingUid) {
// Schedule toast timeout
}
}
flowchart TD
App[App calls Toast.show] --> NMS[NotificationManagerService<br/>Creates token]
NMS --> WMS["WMS.addWindow(TYPE_TOAST)"]
WMS --> Check1{"Target > N MR1?"}
Check1 -->|Yes| TokenCheck{"Token matches<br/>TYPE_TOAST?"}
Check1 -->|No| Legacy[Legacy path:<br/>no token required]
TokenCheck -->|Yes| DupCheck{"canAddToastWindowForUid()?"}
TokenCheck -->|No| Reject[ADD_BAD_APP_TOKEN]
DupCheck -->|Yes| Create[Create WindowState]
DupCheck -->|No| RejectDup[ADD_DUPLICATE_ADD]
Create --> Policy["adjustWindowParamsLw()<br/>Force timeout, FLAG_NOT_TOUCHABLE"]
Policy --> Input["setDropInputModePolicy()<br/>DropInputMode.ALL"]
Input --> Schedule["Schedule hide after timeout"]
Legacy --> DupCheck
81.3 SYSTEM_ALERT_WINDOW — Permission Gate
System alert windows (overlays) include types like TYPE_APPLICATION_OVERLAY (2038),
TYPE_SYSTEM_ALERT (2003), and other system window types identified by
LayoutParams.isSystemAlertWindowType().
81.3.1 Permission Check Flow
The permission check is performed in PhoneWindowManager.checkAddPermission() (line 3092),
called from WMS.addWindow():
// PhoneWindowManager.java line 3092
public int checkAddPermission(int type, boolean isRoundedCornerOverlay,
String packageName, int[] outAppOp, int displayId) {
// Non-system-alert types have simpler checks
if (!isSystemAlertWindowType(type)) {
switch (type) {
case TYPE_TOAST:
outAppOp[0] = OP_TOAST_WINDOW;
return ADD_OKAY;
// ... other non-alert types
}
// Requires INTERNAL_SYSTEM_WINDOW for other system types
return (checkPermission(INTERNAL_SYSTEM_WINDOW) == GRANTED)
? ADD_OKAY : ADD_PERMISSION_DENIED;
}
// System alert window types require OP_SYSTEM_ALERT_WINDOW
outAppOp[0] = OP_SYSTEM_ALERT_WINDOW;
// System UID gets automatic approval
if (UserHandle.getAppId(callingUid) == Process.SYSTEM_UID) {
return ADD_OKAY;
}
// Further app-level checks...
}
81.3.2 Window Type Permission Requirements
| Window Type | Required Permission | AppOp |
|---|---|---|
TYPE_TOAST |
None (token-gated post-N MR1) | OP_TOAST_WINDOW |
TYPE_APPLICATION_OVERLAY |
SYSTEM_ALERT_WINDOW |
OP_SYSTEM_ALERT_WINDOW |
TYPE_SYSTEM_ALERT |
SYSTEM_ALERT_WINDOW |
OP_SYSTEM_ALERT_WINDOW |
TYPE_SYSTEM_ERROR |
SYSTEM_ALERT_WINDOW |
OP_SYSTEM_ALERT_WINDOW |
TYPE_PHONE |
SYSTEM_ALERT_WINDOW |
OP_SYSTEM_ALERT_WINDOW |
TYPE_SYSTEM_OVERLAY |
INTERNAL_SYSTEM_WINDOW |
None |
TYPE_STATUS_BAR |
INTERNAL_SYSTEM_WINDOW |
None |
TYPE_INPUT_METHOD |
Managed by system | None |
TYPE_ACCESSIBILITY_OVERLAY |
OP_CREATE_ACCESSIBILITY_OVERLAY |
AppOp |
flowchart TD
Add["WMS.addWindow(type, attrs)"] --> PWM["PhoneWindowManager.checkAddPermission()"]
PWM --> IsAlert{"isSystemAlertWindowType()?"}
IsAlert -->|No| TypeSwitch{"Window type?"}
TypeSwitch -->|TYPE_TOAST| Toast["AppOp: OP_TOAST_WINDOW<br/>→ ADD_OKAY"]
TypeSwitch -->|TYPE_INPUT_METHOD<br/>TYPE_WALLPAPER| System["System-managed<br/>→ ADD_OKAY"]
TypeSwitch -->|Other system| ISW{"INTERNAL_SYSTEM_WINDOW<br/>permission?"}
ISW -->|Granted| OK1[ADD_OKAY]
ISW -->|Denied| Deny1[ADD_PERMISSION_DENIED]
IsAlert -->|Yes| SAW["AppOp: OP_SYSTEM_ALERT_WINDOW"]
SAW --> SysUID{"System UID?"}
SysUID -->|Yes| OK2[ADD_OKAY]
SysUID -->|No| AppCheck{"App-level<br/>permission check"}
AppCheck -->|Granted| OK3[ADD_OKAY]
AppCheck -->|Denied| Deny2[ADD_PERMISSION_DENIED]
81.4 AlertWindowNotification — User Transparency
When a non-system app displays an overlay window, Android shows a persistent notification to
inform the user. This is managed by AlertWindowNotification (178 lines).
81.4.1 Notification Lifecycle
The Session class tracks visible alert windows per session:
// frameworks/base/services/core/java/com/android/server/wm/Session.java
/** Set of visible alert/app-overlay windows connected to this session. */
private final ArraySet<WindowState> mAlertWindows;
private AlertWindowNotification mAlertWindowNotification;
private boolean mShowingAlertWindowNotificationAllowed;
When overlay window visibility changes (onWindowSurfaceVisibilityChanged, line 780):
void onWindowSurfaceVisibilityChanged(WindowState window, boolean visible) {
if (!isSystemAlertWindowType(type) && type != TYPE_SYSTEM_DIALOG) return;
boolean noSystemOverlayPermission =
!mCanAddInternalSystemWindow && !mCanCreateSystemApplicationOverlay;
if (visible) {
changed = mAlertWindows.add(window);
} else {
changed = mAlertWindows.remove(window);
}
if (changed && noSystemOverlayPermission) {
if (mAlertWindows.isEmpty()) {
cancelAlertWindowNotification();
} else if (mAlertWindowNotification == null) {
mAlertWindowNotification = new AlertWindowNotification(mService, mPackageName);
mAlertWindowNotification.post();
}
}
// Notify AMS for process importance adjustment
if (changed) setHasOverlayUi(!mAlertWindows.isEmpty());
}
81.4.2 Notification Details
AlertWindowNotification creates a minimum-importance ongoing notification with a link
to the overlay permission settings:
// frameworks/base/services/core/java/com/android/server/wm/AlertWindowNotification.java
private void onPostNotification() {
final Notification.Builder builder = new Notification.Builder(context, mNotificationTag)
.setOngoing(true)
.setContentTitle(getString(R.string.alert_windows_notification_title, appName))
.setContentText(message)
.setSmallIcon(R.drawable.alert_window_layer)
.setContentIntent(getContentIntent(context, mPackageName));
mNotificationManager.notify(mNotificationTag, NOTIFICATION_ID, builder.build());
}
private PendingIntent getContentIntent(Context context, String packageName) {
final Intent intent = new Intent(ACTION_MANAGE_APP_OVERLAY_PERMISSION,
Uri.fromParts("package", packageName, null));
intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
return PendingIntent.getActivity(context, mRequestCode, intent,
FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE);
}
Key notification properties:
| Property | Value | Purpose |
|---|---|---|
| Importance | IMPORTANCE_MIN |
Non-intrusive but visible in shade |
| Ongoing | true |
Cannot be swiped away while overlay active |
| Blockable | true |
User can disable per-app notification channel |
| Bypass DnD | true |
Always visible for security |
| Content intent | ACTION_MANAGE_APP_OVERLAY_PERMISSION |
Direct link to revoke overlay permission |
| Channel group | Per-WMS group | All overlay notifications grouped together |
sequenceDiagram
participant App as App Process
participant WMS as WindowManagerService
participant Sess as Session
participant AWN as AlertWindowNotification
participant NMS as NotificationManager
participant User as User
App->>WMS: addWindow(TYPE_APPLICATION_OVERLAY)
WMS->>WMS: checkAddPermission() → OP_SYSTEM_ALERT_WINDOW
WMS->>Sess: Track window in session
Note over WMS: Window surface becomes visible
WMS->>Sess: onWindowSurfaceVisibilityChanged(visible=true)
Sess->>Sess: mAlertWindows.add(window)
Sess->>AWN: new AlertWindowNotification(packageName)
AWN->>NMS: post ongoing notification
Sess->>Sess: setHasOverlayUi(true) → AMS importance boost
User->>NMS: Tap notification
NMS->>User: Open overlay permission settings
User->>User: Revoke SYSTEM_ALERT_WINDOW
Note over WMS: Window removed
WMS->>Sess: onWindowSurfaceVisibilityChanged(visible=false)
Sess->>Sess: mAlertWindows.remove(window)
Sess->>AWN: cancel(deleteChannel=true)
AWN->>NMS: Cancel notification + delete channel
81.5 Overlay Attack Prevention Mechanisms
Android implements several layers of defense against overlay-based attacks (tapjacking):
81.5.1 Opacity Limiting for Pass-Through Overlays
DisplayPolicy.adjustWindowParamsLw() enforces maximum opacity for system alert windows
that pass through touches:
// DisplayPolicy.java line 1053-1070
if (LayoutParams.isSystemAlertWindowType(attrs.type)) {
float maxOpacity = mService.mMaximumObscuringOpacityForTouch;
if (attrs.alpha > maxOpacity
&& (attrs.flags & FLAG_NOT_TOUCHABLE) != 0
&& !win.isTrustedOverlay()) {
// Reduce opacity to allow touches to pass through safely
attrs.alpha = maxOpacity;
win.mWinAnimator.mAlpha = maxOpacity;
}
}
This prevents an attacker from creating a fully opaque FLAG_NOT_TOUCHABLE overlay that
obscures a security-sensitive UI underneath while still passing touches through.
81.5.2 Window Visibility Flags
| Flag | Description | Security Role |
|---|---|---|
FLAG_NOT_TOUCHABLE |
Window does not consume touch events | Must combine with opacity limit for overlays |
FLAG_NOT_FOCUSABLE |
Window cannot receive key events | Prevents overlay from stealing input focus |
FLAG_WATCH_OUTSIDE_TOUCH |
Receive ACTION_OUTSIDE for touches outside window |
Stripped from TYPE_SYSTEM_OVERLAY |
HIDE_OVERLAY_WINDOWS |
App requests overlay windows be hidden | Apps can protect sensitive UI |
FLAG_WINDOW_IS_OBSCURED |
Injected into MotionEvent when touch is obscured |
Apps can reject obscured touches |
81.5.3 TYPE_SYSTEM_OVERLAY Restrictions
System overlay windows receive the strongest restrictions:
// DisplayPolicy.java line 998-1004
case TYPE_SYSTEM_OVERLAY:
case TYPE_SECURE_SYSTEM_OVERLAY:
attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
attrs.flags &= ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
break;
These windows are forced to be non-focusable, non-touchable, and cannot watch outside touches, ensuring they are purely visual and cannot intercept any user interaction.
81.5.4 Multi-Layer Defense Model
graph TB
subgraph "Layer 1: Permission Gate"
P1[SYSTEM_ALERT_WINDOW permission]
P2[AppOps OP_SYSTEM_ALERT_WINDOW]
P3[Runtime permission check]
end
subgraph "Layer 2: Window Constraints"
W1[Opacity limiting for SAW]
W2[FLAG_NOT_TOUCHABLE enforcement]
W3[TYPE_SYSTEM_OVERLAY locked down]
W4[Toast: DropInputMode.ALL]
end
subgraph "Layer 3: User Awareness"
U1[AlertWindowNotification posted]
U2[Direct link to revoke permission]
U3[AMS overlay UI importance tracking]
end
subgraph "Layer 4: App Self-Defense"
A1[HIDE_OVERLAY_WINDOWS flag]
A2[FLAG_WINDOW_IS_OBSCURED in events]
A3[filterTouchesWhenObscured attribute]
end
P1 --> W1
W1 --> U1
U1 --> A1
81.6 Process Importance and Overlay UI
When a session has active alert windows, it notifies the Activity Manager to adjust the process importance score:
// Session.java line 868
void setHasOverlayUi(boolean hasOverlayUi) {
mService.mH.obtainMessage(H.SET_HAS_OVERLAY_UI, mPid,
hasOverlayUi ? 1 : 0).sendToTarget();
}
This ensures that processes displaying overlays receive an importance boost, preventing them from being killed by the low-memory killer while their overlay is visible. This is important for both functionality (overlay stays visible) and security (the notification remains accurate).
81.7 Global Alert Notification Control
The system can globally enable or disable alert window notifications:
// WindowManagerService.java line 9215
boolean mShowAlertWindowNotifications = true;
// When changed, propagates to all sessions:
mShowAlertWindowNotifications = showAlertWindowNotifications;
for (Session s : mSessions) {
s.setShowingAlertWindowNotificationAllowed(mShowAlertWindowNotifications);
}
This allows system-level control (e.g., during setup wizards or enterprise management) to suppress overlay notifications when appropriate.
82.8 Key Source Files
| File | Path | Lines | Role |
|---|---|---|---|
| GameManagerService | frameworks/base/services/core/java/com/android/server/app/GameManagerService.java |
~2,367 | Game mode management, resolution/FPS interventions, CompatScaleProvider |
| SystemPerformanceHinter | frameworks/base/core/java/android/window/SystemPerformanceHinter.java |
416 | Session-based performance hints to SurfaceFlinger and ADPF |
| RefreshRatePolicy | frameworks/base/services/core/java/com/android/server/wm/RefreshRatePolicy.java |
321 | Per-window refresh rate voting, denylist, camera-aware rate clamping |
| TaskFpsCallbackController | frameworks/base/services/core/java/com/android/server/wm/TaskFpsCallbackController.java |
76 | Per-task FPS monitoring via native SurfaceFlinger hooks |
| HighRefreshRateDenylist | frameworks/base/services/core/java/com/android/server/wm/HighRefreshRateDenylist.java |
103 | Package denylist for high refresh rate restriction |
| GameManager | frameworks/base/core/java/android/app/GameManager.java |
~322 | Public API for game mode constants and client access |
| CompatScaleProvider | frameworks/base/services/core/java/com/android/server/wm/CompatScaleProvider.java |
~86 | Interface for resolution scaling integration with WMS |
Part XXII: Accessibility and Input Features
83. One-Handed Mode
83.1 Architecture Overview — DisplayAreaOrganizer-Based Display Scaling
One-handed mode allows users to shift the entire display content downward so that
the top of the screen becomes reachable with one hand. The implementation lives
entirely within the WM Shell module and is built on the
DisplayAreaOrganizer API, which gives Shell-side code a SurfaceControl leash
over a display area that it can reposition, crop, and animate freely without
modifying the server-side layout.
graph TD
subgraph "WM Shell Process"
OHC["OneHandedController<br/>(orchestrator)"]
OHA["OneHandedAnimationController<br/>(animation factory)"]
OHDAO["OneHandedDisplayAreaOrganizer<br/>(extends DisplayAreaOrganizer)"]
OHT["OneHandedTouchHandler<br/>(outside-region tap)"]
OHTO["OneHandedTimeoutHandler<br/>(auto-dismiss timer)"]
OHS["OneHandedState<br/>(state machine)"]
OHSTU["OneHandedSettingsUtil"]
BGW["BackgroundWindowManager"]
end
subgraph "WindowManagerService"
DA["DisplayArea<br/>(FEATURE_ONE_HANDED)"]
WCT["WindowContainerTransaction"]
end
subgraph "User / System Events"
GES["Bottom-edge swipe gesture"]
SETTINGS["Settings toggle"]
ACC["Accessibility shortcut"]
TIMEOUT["Idle timeout"]
TAP["Tap outside region"]
end
GES -->|"startOneHanded()"| OHC
SETTINGS -->|ContentObserver| OHC
ACC -->|"onActivatedActionChanged()"| OHC
OHC -->|"scheduleOffset()"| OHDAO
OHDAO -->|"getAnimator()"| OHA
OHA -->|"Y-offset ValueAnimator"| OHDAO
OHDAO -->|"SurfaceControl.Transaction<br/>translate / crop / round"| DA
OHT -->|"onStop()"| OHC
OHTO -->|"onTimeout()"| OHC
TAP --> OHT
TIMEOUT --> OHTO
OHC --> OHS
The state machine tracks four states that gate all operations:
| State | Value | Meaning |
|---|---|---|
STATE_NONE |
0 | Idle — display at normal position |
STATE_ENTERING |
1 | Animating into one-handed offset |
STATE_ACTIVE |
2 | Display is shifted, user is interacting |
STATE_EXITING |
3 | Animating back to normal position |
83.2 OneHandedController — Feature Lifecycle and Gesture Activation
OneHandedController is the master orchestrator. It implements
RemoteCallable<OneHandedController>, DisplayChangeController.OnDisplayChangingListener,
ConfigurationChangeListener, KeyguardChangeListener, and UserChangeListener.
Feature initialization follows the Shell ShellInit pattern. The static
create() factory builds every dependency, and onInit() wires up four
ContentObserver instances monitoring Settings.Secure keys:
| Observer | Settings Key | Handler |
|---|---|---|
mActivatedObserver |
ONE_HANDED_MODE_ACTIVATED |
onActivatedActionChanged() |
mEnabledObserver |
ONE_HANDED_MODE_ENABLED |
onEnabledSettingChanged() |
mSwipeToNotificationEnabledObserver |
SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED |
onSwipeToNotificationEnabledChanged() |
mShortcutEnabledObserver |
ACCESSIBILITY_BUTTON_TARGETS |
onShortcutEnabledChanged() |
Activation flow — when the bottom-edge swipe is detected or the accessibility shortcut fires:
startOneHanded()checksisLockedDisabled(),mKeyguardShowing,mDisplayAreaOrganizer.isReady(),mState.isTransitioning(),mState.isInOneHanded(), andisLandscape(), rejecting if any fails.- State transitions to
STATE_ENTERING. - Offset fraction is read from
persist.debug.one_handed_offset_percentage(default ~0.5, meaning 50 % of screen height). OneHandedDisplayAreaOrganizer.scheduleOffset(0, yOffset)is called.- Timeout timer starts via
mTimeoutHandler.resetTimer().
Deactivation occurs via multiple paths — all converge on stopOneHanded():
flowchart LR
A["Tap outside<br/>region"] -->|"OneHandedTouchHandler<br/>.onStop()"| STOP["stopOneHanded()"]
B["Timeout<br/>expires"] -->|"TimeoutHandler<br/>.onTimeout()"| STOP
C["Task change<br/>(app switch)"] -->|"TaskStackListener<br/>.onTaskCreated()"| STOP
D["Rotation<br/>change"] -->|"onRotateDisplay()"| STOP
E["Keyguard<br/>shown"] -->|"onKeyguardVisibilityChanged()"| STOP
F["User switches"] -->|"onUserChanged()"| STOP
STOP -->|"setState(STATE_EXITING)<br/>scheduleOffset(0,0)"| EXIT["Animate to<br/>normal"]
Key runtime flags include mLockedDisabled (prevents activation during phone
calls) and mTaskChangeToExit (configures whether new tasks exit one-handed
mode, controlled via setTaskChangeToExit()).
83.3 OneHandedDisplayAreaOrganizer — Display Area Repositioning
OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer and registers for
the FEATURE_ONE_HANDED display area feature. Upon registration it receives
SurfaceControl leashes for every matching display area and stores them in
mDisplayAreaTokenMap (ArrayMap<WindowContainerToken, SurfaceControl>).
The core repositioning API is scheduleOffset(int xOffset, int yOffset):
- Determines the animation direction (
TRANSITION_DIRECTION_TRIGGERifyOffset > 0, elseTRANSITION_DIRECTION_EXIT). - Begins a CUJ trace via
InteractionJankMonitorfor eitherCUJ_ONE_HANDED_ENTER_TRANSITIONorCUJ_ONE_HANDED_EXIT_TRANSITION. - Iterates all entries in
mDisplayAreaTokenMapand callsanimateWindows()for each. animateWindows()obtains an animator fromOneHandedAnimationController, registersmOneHandedAnimationCallback, sets the animation duration (read from the system propertypersist.debug.one_handed_translate_animation_duration), and starts playback.
Once all animators complete, finishOffset() is invoked:
- If direction is EXIT →
resetWindowsOffset()clears all transforms (crop, translate, corner radius) via a directSurfaceControl.Transaction. - Updates
mLastVisualOffsetandmLastVisualDisplayBounds. - Notifies every registered
OneHandedTransitionCallbackviaonStartFinished()oronStopFinished().
Rotation events call onRotateDisplay() which immediately cancels any
active animation and resets the offset to zero, updating DisplayLayout
dimensions for the new orientation.
83.4 Animation — Enter / Exit One-Handed Mode
OneHandedAnimationController manages a map of per-display-area animators
(HashMap<WindowContainerToken, OneHandedTransitionAnimator>). The inner
class OneHandedTransitionAnimator extends ValueAnimator drives each frame.
sequenceDiagram
participant DAO as DisplayAreaOrganizer
participant AC as AnimationController
participant AN as TransitionAnimator
participant SC as SurfaceControl
DAO->>AC: getAnimator(token, leash, startPos, endPos, bounds)
AC->>AN: ofYOffset(token, leash, 0, yOffset, displayBounds)
AC->>AN: setSurfaceTransactionHelper(helper)
AC->>AN: setInterpolator(OneHandedInterpolator)
AC->>AN: setFloatValues(FRACTION_START, FRACTION_END)
DAO->>AN: addOneHandedAnimationCallback(callback)
DAO->>AN: setDuration(durationMs)
DAO->>AN: start()
loop Every frame
AN->>AN: onAnimationUpdate()
AN->>AN: getCastedFractionValue() → lerp(start, end, fraction)
AN->>SC: Transaction.crop(rect).round().translate(0, yOffset)
AN->>SC: Transaction.apply()
end
AN->>DAO: onOneHandedAnimationEnd(tx, animator)
DAO->>DAO: finishOffset() → endCUJTracing()
The Y-offset animator is created by the ofYOffset() static factory. Each
frame it:
- Computes
currentValue = startValue * (1 - fraction) + endValue * fraction + 0.5f. - Updates a temporary
Rectwith the rounded offset. - Applies three surface operations via
OneHandedSurfaceTransactionHelper:crop()→round()→translate().
The custom OneHandedInterpolator implements a damped spring easing:
f(x) = 2^(-10x) · sin(π(x − 4) / 8) + 1
This produces a slight overshoot followed by a settle, giving the animation a natural, physical feel.
83.5 Touch and Timeout Handling
OneHandedTouchHandler implements OneHandedTransitionCallback and monitors
touch events via an InputMonitor / InputEventReceiver pair. It is
enabled/disabled through updateIsEnabled(), which creates or disposes the
input channel.
Touch classification is simple — a region test against
mLastUpdatedBounds.top:
| Condition | Action |
|---|---|
y < bounds.top |
Touch is in the outside region (exposed area above shifted content) |
ACTION_DOWN/MOVE inside region |
Reset timeout timer via mTimeoutHandler.resetTimer() |
ACTION_UP in outside region |
Call mTouchEventCallback.onStop() → triggers stopOneHanded() |
This means any tap on the empty area above the shifted content exits one-handed mode.
OneHandedTimeoutHandler schedules auto-dismiss using
ShellExecutor.executeDelayed(). Four configurable timeout values are
supported:
| Constant | Value | Behavior |
|---|---|---|
ONE_HANDED_TIMEOUT_NEVER |
0 s | No auto-exit |
ONE_HANDED_TIMEOUT_SHORT_IN_SECONDS |
4 s | Quick dismiss |
ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS |
8 s | Default |
ONE_HANDED_TIMEOUT_LONG_IN_SECONDS |
12 s | Extended |
resetTimer() removes any pending callback and reschedules. The timeout
multiplier can be adjusted by accessibility services via
mAccessibilityStateChangeListener, which queries the recommended timeout
multiplier for users who need more time.
Registered TimeoutListener instances receive onTimeout(int timeoutTime) and
typically call stopOneHanded().
83.6 Key Source Files
| File | Path | Purpose |
|---|---|---|
| OneHandedController | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java |
Master orchestrator — lifecycle, settings, IPC |
| OneHandedAnimationController | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java |
Animator factory, Y-offset ValueAnimator, spring interpolator |
| OneHandedDisplayAreaOrganizer | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java |
DisplayArea leash management, scheduleOffset(), CUJ tracing |
| OneHandedTouchHandler | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java |
Input monitoring, outside-region tap detection |
| OneHandedTimeoutHandler | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandler.java |
Auto-dismiss timer (4 / 8 / 12 s) |
| OneHandedState | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedState.java |
State machine: NONE → ENTERING → ACTIVE → EXITING |
| OneHandedTransitionCallback | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTransitionCallback.java |
Observer interface for transition events |
| OneHandedAnimationCallback | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationCallback.java |
Animation lifecycle observer |
| OneHandedSettingsUtil | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java |
Settings.Secure read/write helpers |
| OneHandedSurfaceTransactionHelper | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSurfaceTransactionHelper.java |
Surface crop / translate / round operations |
| BackgroundWindowManager | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java |
Manages background behind shifted content |
| IOneHanded.aidl | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/IOneHanded.aidl |
Binder interface for cross-process control |
| OneHandedTutorialHandler | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java |
First-time usage tutorial overlay |
| OneHandedUiEventLogger | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedUiEventLogger.java |
UI event telemetry |
85. System Gestures and Navigation Bar
85.1 SystemGesturesPointerEventListener — Edge Swipe Detection
SystemGesturesPointerEventListener is a server-side input listener registered
on every DisplayContent that detects edge swipes from all four display
edges. It implements WindowManagerPolicyConstants.PointerEventListener and
receives every pointer event system-wide before applications.
Swipe detection algorithm:
The listener tracks up to MAX_TRACKED_POINTERS (32) pointer contacts,
recording each pointer’s down position (mDownX[], mDownY[]) and timestamp
(mDownTime[]). On every ACTION_MOVE, the detectSwipe() method evaluates
all tracked pointers against configurable thresholds:
flowchart TD
DOWN["ACTION_DOWN<br/>captureDown(pointerId, x, y, time)"]
MOVE["ACTION_MOVE<br/>detectSwipe() for all pointers"]
CHECK{"Started in edge zone?<br/>Moved far enough?<br/>Within timeout?"}
TOP["SWIPE_FROM_TOP<br/>→ callbacks.onSwipeFromTop()"]
BOT["SWIPE_FROM_BOTTOM<br/>→ callbacks.onSwipeFromBottom()"]
LEFT["SWIPE_FROM_LEFT<br/>→ callbacks.onSwipeFromLeft()"]
RIGHT["SWIPE_FROM_RIGHT<br/>→ callbacks.onSwipeFromRight()"]
NONE["SWIPE_NONE<br/>(keep tracking)"]
DOWN --> MOVE
MOVE --> CHECK
CHECK -->|"fromY ≤ threshold.top<br/>Δy > distThreshold<br/>elapsed < 500ms"| TOP
CHECK -->|"fromY ≥ screenH - threshold.bottom<br/>Δy > distThreshold"| BOT
CHECK -->|"fromX ≤ threshold.left<br/>Δx > distThreshold"| LEFT
CHECK -->|"fromX ≥ screenW - threshold.right<br/>Δx > distThreshold"| RIGHT
CHECK -->|"no match"| NONE
Threshold configuration:
| Parameter | Source | Purpose |
|---|---|---|
mSwipeStartThreshold |
R.dimen.system_gestures_start_threshold |
Per-edge pixel distance from display edge for swipe start zone |
mSwipeDistanceThreshold |
R.dimen.system_gestures_distance_threshold |
Minimum travel distance to recognize a swipe |
SWIPE_TIMEOUT_MS |
500 ms (constant) | Maximum time for swipe gesture |
The start threshold is dynamically expanded around display cutouts — if a
cutout exists on any edge, mSwipeStartThreshold for that edge is increased to
cutout_width + mDisplayCutoutTouchableRegionSize, ensuring swipes originating
just outside the notch area are still recognized.
Trackpad support — detectTrackpadThreeFingerSwipe() handles three-finger
swipe gestures from external trackpads by checking
MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE and
AXIS_GESTURE_SWIPE_FINGER_COUNT == 3. These are mapped to the same directional
swipe callbacks.
Fling detection — an inner FlingGestureDetector (extending
GestureDetector.SimpleOnGestureListener) uses an OverScroller to compute
fling duration and fires callbacks.onFling(duration) to power-boost the
CPU during the fling.
Mouse hover support — ACTION_HOVER_MOVE events from mouse sources trigger
edge-hover callbacks (onMouseHoverAtLeft/Top/Right/Bottom) when the cursor
reaches pixel 0 or screenWidth/Height - 1. These are used by DisplayPolicy
to trigger delayed transient bar reveals.
85.2 Gesture Exclusion Zones — App Control over Gesture Areas
Apps can declare gesture exclusion zones — screen regions where system edge gestures should be suppressed to avoid interfering with in-app gestures (e.g., a drawing canvas edge, a side-swipe navigation drawer).
The exclusion system is implemented across several classes:
graph TD
subgraph "App Process"
VIEW["View.setSystemGestureExclusionRects(List<Rect>)"]
end
subgraph "WindowManagerService"
WS["WindowState<br/>mExclusionRects[]<br/>EXCLUSION_LEFT / EXCLUSION_RIGHT"]
DC["DisplayContent<br/>calculateSystemGestureExclusion()"]
DP["DisplayPolicy<br/>gesture callback integration"]
end
subgraph "SystemUI / InputDispatcher"
LISTENER["ISystemGestureExclusionListener"]
INPUT["InputDispatcher<br/>gesture exclusion region"]
end
VIEW -->|"IPC via WindowSession"| WS
WS -->|"per-window rects"| DC
DC -->|"aggregate + limit"| DC
DC -->|"onSystemGestureExclusionChanged()"| LISTENER
DP -->|"currentGestureStartedInRegion()"| DC
Restriction enforcement:
DisplayContent.mSystemGestureExclusionLimit— a per-edge pixel limit computed frommSystemGestureExclusionLimitDp(a WM constant) × display density. This prevents apps from excluding the entire edge.PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION— a privileged window flag that bypasses the per-edge limit.DisplayPolicy.adjustWindowParamsLw()strips this flag if the caller’s session does not have themCanSetUnrestrictedGestureExclusionpermission.calculateSystemGestureExclusion(Region outExclusion, Region outUnrestricted)— aggregates all visible windows’ exclusion rects, applies the per-edge limit, and outputs both the restricted and unrestricted regions.
The restricted region is broadcast to ISystemGestureExclusionListener clients
(typically SystemUI’s navigation bar) and influences DisplayPolicy’s side
swipe logic — allowsSideSwipe() checks whether the current gesture started
inside the exclusion region via currentGestureStartedInRegion().
85.3 Navigation Bar Modes: 3-Button, 2-Button, Gesture Navigation
Android supports three navigation modes that change how the navigation bar appears and how system gestures are handled:
| Mode | Description | Navigation Bar | Back Gesture |
|---|---|---|---|
| 3-button | Classic: Back, Home, Recents buttons | Full-height opaque bar | Physical button |
| 2-button | Transitional: pill-shaped Home + Back | Slimmer bar with home pill | Swipe from bottom-left |
| Gesture | Full gesture: swipe from edges | Thin transparent bar or hidden | Swipe from left/right edge |
DisplayPolicy manages the navigation bar through several key mechanisms:
Navigation bar window management:
getNavigationBar()returns theWindowStateforTYPE_NAVIGATION_BAR.navigationBarCanMove()determines if the nav bar can relocate to the side on landscape displays (tablets vs. phones).- Window type
TYPE_NAVIGATION_BARreceives special treatment invalidateAddingWindowLw()— only one per display is allowed. TYPE_NAVIGATION_BAR_PANELwindows are allowed as overlays on the nav bar.
Nav bar opacity modes control translucency behavior:
| Mode | Constant | Behavior |
|---|---|---|
| Opaque when freeform/docked | NAV_BAR_OPAQUE_WHEN_FREEFORM_OR_DOCKED |
Opaque in multi-window, translucent otherwise |
| Translucent when freeform | NAV_BAR_TRANSLUCENT_WHEN_FREEFORM_OPAQUE_OTHERWISE |
Translucent in freeform mode |
| Force transparent | NAV_BAR_FORCE_TRANSPARENT |
Always transparent (gesture navigation mode) |
Appearance attributes — the InsetsFlags.appearance bitmask controls
nav bar appearance:
| Flag | Effect |
|---|---|
APPEARANCE_LIGHT_NAVIGATION_BARS |
Dark icons/buttons on light background |
APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS |
Force light appearance regardless of app request |
APPEARANCE_OPAQUE_NAVIGATION_BARS |
Fully opaque background |
APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS |
Semi-transparent background |
The appearance is determined by the top window that overlaps the navigation
bar area. DisplayPolicy caches candidate windows during layout traversal and
computes the final appearance in updateSystemBarAttributes().
85.4 NavBarFadeAnimationController — Transient Navigation Bar Visibility
NavBarFadeAnimationController extends FadeAnimationController manages
fade-in/fade-out animations of the navigation bar during app transitions,
specifically when config_attachNavBarToAppDuringTransition is true.
Animation parameters:
| Animation | Duration | Interpolator |
|---|---|---|
| Fade in | 266 ms | PathInterpolator(0, 0, 0, 1) — ease-out |
| Fade out | 133 ms | PathInterpolator(0.2, 0, 1, 1) — ease-in |
Key methods:
fadeWindowToken(boolean show)— triggers fade-in or fade-out on the navigation bar’sWindowToken. Coordinates withAsyncRotationControllerto defer fade-in if a rotation animation is in progress.fadeOutAndInSequentially(long totalDuration, SurfaceControl fadeOutParent, SurfaceControl fadeInParent)— plays fade-out followed by fade-in during app transitions. Durations are proportioned: 1/3 fade-out, 2/3 fade-in of the total transition time.
NavFadeAnimationAdapter — inner class extending FadeAnimationAdapter:
startAnimation()— reparents the animation leash to the specified parentSurfaceControland sets layer toInteger.MAX_VALUE(above IME, starting windows, etc.) to ensure nav bar is visible during transitions.shouldDeferAnimationFinish()— in sequential mode, automatically triggers the fade-in animation when the fade-out completes, chaining the two together.
sequenceDiagram
participant DP as DisplayPolicy
participant NBFAC as NavBarFadeAnimationController
participant ARC as AsyncRotationController
participant SA as SurfaceAnimator
participant SC as SurfaceControl
DP->>NBFAC: fadeOutAndInSequentially(totalDuration, outParent, inParent)
NBFAC->>NBFAC: mPlaySequentially = true
NBFAC->>NBFAC: Set durations: out=total/3, in=total*2/3
NBFAC->>NBFAC: fadeWindowToken(show=false)
NBFAC->>SA: startAnimation(leash, tx, type, callback)
SA->>SC: reparent(leash, fadeOutParent)
SA->>SC: setLayer(leash, MAX_VALUE)
Note over SA: Fade-out plays (133ms adjusted)
SA->>NBFAC: shouldDeferAnimationFinish()
NBFAC->>NBFAC: mPlaySequentially → trigger fadeWindowToken(show=true)
NBFAC->>SA: startAnimation(leash, tx, type, callback)
SA->>SC: reparent(leash, fadeInParent)
Note over SA: Fade-in plays (266ms adjusted)
SA->>DP: Animation complete
85.5 Navigation Bar Insets Interaction (InsetsPolicy)
InsetsPolicy is the central arbiter for system bar visibility and control,
managing which window can control the navigation bar’s visibility and how
transient bars behave.
Controllable types:
public static final int CONTROLLABLE_TYPES =
WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars() | WindowInsets.Type.ime();
Transient bar mechanism — the core of swipe-to-reveal:
- User swipes from bottom edge →
SystemGesturesPointerEventListenerfiresonSwipeFromBottom(). DisplayPolicy.requestTransientBars(swipeTarget, isGestureOnSystemBar)is called with the bottom gesture host window.- After validation (not lockscreen, not dream, user setup complete),
InsetsPolicy.showTransient(SHOW_TYPES_FOR_SWIPE, isGestureOnSystemBar)is invoked. showTransient()iterates all insets sources, finds hidden ones of matching types, and marks them asmShowingTransientTypes.- Control target is set to
mShowingTransientControlTarget— a specialControlTargetthat shows bars without affecting layout. StatusBarManagerInternal.showTransient()is called to notify SystemUI.
Transient bar lifecycle:
| Phase | Method | Effect |
|---|---|---|
| Show | showTransient() |
Sets mShowingTransientTypes, updates control targets |
| Hide | hideTransient() |
Moves types to mHidingTransientTypes, plays hide animation |
| Abort | abortTransient() |
Cancels transient state, requests layout traversal |
| Check | checkAbortTransient() |
Auto-aborts if app requests visibility or IME shows |
Navigation bar control target resolution — getNavControlTargetInner():
flowchart TD
START["getNavControlTargetInner(focusedWin, fake)"]
IME{"IME visible AND<br/>!mHideNavBarForKeyboard?"}
TRANSIENT{"isTransient(navigationBars)<br/>AND !fake?"}
SHADE{"focusedWin ==<br/>notificationShade?"}
NAVFOCUS{"focusedWin has<br/>nav bar provider?"}
FORCE_SHOW{"areTypesForciblyShown<br/>(navigationBars)?"}
FORCE_TRANSIENT{"areInsetsTypesForciblyShown<br/>Transiently?"}
FORCE_HIDE{"areTypesForciblyHidden<br/>(navigationBars)?"}
TOP_APP{"topApp hides nav bar<br/>AND !canReceiveKeys?"}
DEFAULT["Return focusedWin"]
START --> IME
IME -->|yes| PERM["mShowingPermanentControlTarget"]
IME -->|no| TRANSIENT
TRANSIENT -->|yes| TRANS["mShowingTransientControlTarget"]
TRANSIENT -->|no| SHADE
SHADE -->|yes| FOCUSED["Return focusedWin"]
SHADE -->|no| NAVFOCUS
NAVFOCUS -->|yes| FOCUSED2["Return focusedWin"]
NAVFOCUS -->|no| FORCE_SHOW
FORCE_SHOW -->|yes| PERM2["mShowingPermanentControlTarget"]
FORCE_SHOW -->|no| FORCE_TRANSIENT
FORCE_TRANSIENT -->|yes| TRANS2["mShowingTransientControlTarget"]
FORCE_TRANSIENT -->|no| FORCE_HIDE
FORCE_HIDE -->|yes| HIDE["mHidingPermanentControlTarget"]
FORCE_HIDE -->|no| TOP_APP
TOP_APP -->|yes| TOP["topFullscreenOpaqueWindow"]
TOP_APP -->|no| DEFAULT
The updateSystemBars() method determines force-show/hide states:
- Force show navigation bar when
forceShowingNavigationBars(win)returnstrue— this includes scenarios where the display override requires it. - Legacy policy fallback — when no display-level overrides exist and
showSystemBarsByLegacyPolicyis true, both status and navigation bars are forcibly shown. - Force-consumed types prevent apps from being obscured by bars they cannot control.
Panic gesture — DisplayPolicy includes a panic gesture mechanism with a
30-second expiration (PANIC_GESTURE_EXPIRATION). If an immersive app’s
keyguard is dismissed but the app doesn’t reappear within 30 seconds, the panic
gesture is dropped. The panic gesture reveals navigation bars via
SHOW_TYPES_FOR_PANIC = Type.navigationBars().
85.6 Key Source Files
| File | Path | Purpose |
|---|---|---|
| SystemGesturesPointerEventListener | frameworks/base/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java |
Edge swipe detection, trackpad gestures, mouse hover, fling detection |
| DisplayPolicy | frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java |
Navigation bar policy, gesture callbacks, system bar appearance, transient bar requests |
| InsetsPolicy | frameworks/base/services/core/java/com/android/server/wm/InsetsPolicy.java |
Bar control targets, transient/permanent visibility, force show/hide logic |
| NavBarFadeAnimationController | frameworks/base/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java |
Nav bar fade-in/out during app transitions |
| FadeAnimationController | frameworks/base/services/core/java/com/android/server/wm/FadeAnimationController.java |
Base class for fade animations on WindowToken surfaces |
| DisplayContent | frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java |
calculateSystemGestureExclusion(), exclusion listener management |
| WindowState | frameworks/base/services/core/java/com/android/server/wm/WindowState.java |
Per-window exclusion rects (EXCLUSION_LEFT, EXCLUSION_RIGHT) |
| InsetsStateController | frameworks/base/services/core/java/com/android/server/wm/InsetsStateController.java |
Insets source management, fake control dispatching |
| InsetsSourceProvider | frameworks/base/services/core/java/com/android/server/wm/InsetsSourceProvider.java |
Per-source insets control and visibility |
Part XXIII: Foldable Shell Features
84. Unfold Animation System
84.1 Unfold Animation Architecture — Shell-Side Foldable Animations
The unfold animation system provides smooth visual transitions when a foldable device is physically unfolded. It operates entirely within the WM Shell module and uses a dual-path architecture:
- Legacy path —
UnfoldAnimationControllerreceives progress events directly and applies surface transforms each frame. Used when Shell transitions are disabled. - Modern path —
UnfoldTransitionHandlerintegrates with the ShellTransitionsframework, claiming the unfold as a managed transition. Used when Shell transitions are enabled.
Both paths share the same UnfoldTaskAnimator implementations and coordinate
through the ShellUnfoldProgressProvider interface.
graph TD
subgraph "Hardware / Framework"
HINGE["Hinge sensor<br/>(DeviceStateManager)"]
SUPP["ShellUnfoldProgressProvider<br/>(progress 0.0 → 1.0)"]
end
subgraph "WM Shell — Legacy Path"
UAC["UnfoldAnimationController<br/>(implements UnfoldListener)"]
end
subgraph "WM Shell — Modern Path"
UTH["UnfoldTransitionHandler<br/>(implements TransitionHandler + UnfoldListener)"]
TR["Transitions<br/>(Shell transition framework)"]
end
subgraph "Shared Animators"
FUA["FullscreenUnfoldTaskAnimator<br/>(scale + crop fullscreen tasks)"]
SUA["SplitTaskUnfoldAnimator<br/>(crop split-screen tasks)"]
UBC["UnfoldBackgroundController<br/>(color layer at z=-1)"]
end
subgraph "SurfaceControl Layer"
TASK_SURF["Task SurfaceControl leashes"]
BG_SURF["Background color layer"]
end
HINGE -->|"device state changes"| SUPP
SUPP -->|"onStateChangeProgress(float)"| UAC
SUPP -->|"onStateChangeProgress(float)"| UTH
UTH <-->|"handleRequest / startAnimation"| TR
UAC -->|"applyAnimationProgress()"| FUA
UAC -->|"applyAnimationProgress()"| SUA
UTH -->|"applyAnimationProgress()"| FUA
UTH -->|"applyAnimationProgress()"| SUA
FUA -->|"setWindowCrop / setMatrix<br/>setCornerRadius"| TASK_SURF
SUA -->|"setWindowCrop"| TASK_SURF
FUA --> UBC
SUA --> UBC
UBC -->|"color layer z=-1"| BG_SURF
A critical gating mechanism prevents double-handling: when the Shell transition
path is active, UnfoldTransitionHandler.willHandleTransition() returns true,
and UnfoldAnimationController skips all processing.
84.2 UnfoldAnimationController — Fold / Unfold Event Coordination
UnfoldAnimationController implements ShellUnfoldProgressProvider.UnfoldListener
and serves as the legacy-path coordinator. It is registered as a task
organizer observer to track task surfaces.
Key fields:
| Field | Type | Role |
|---|---|---|
mAnimators |
List<UnfoldTaskAnimator> |
Ordered list — split animator first, then fullscreen |
mTaskSurfaces |
SparseArray<SurfaceControl> |
taskId → surface leash mapping |
mAnimatorsByTaskId |
SparseArray<UnfoldTaskAnimator> |
taskId → applicable animator |
mIsInStageChange |
boolean |
True while fold/unfold animation is active |
mTransactionPool |
TransactionPool |
Reusable SurfaceControl.Transaction instances |
Task tracking — when tasks appear, change, or vanish:
onTaskAppeared(taskInfo, leash)— iteratesmAnimatorsin order; the first animator for whichisApplicableTask(taskInfo)istrueclaims the task. This ordering ensures split-screen tasks are matched bySplitTaskUnfoldAnimatorbeforeFullscreenUnfoldTaskAnimator.onTaskInfoChanged(taskInfo)— re-evaluates applicability; reassigns to a different animator if the windowing mode changed (e.g., entering split).onTaskVanished(taskInfo)— removes tracking; resets surface if mid-animation.
Progress callbacks:
sequenceDiagram
participant Provider as ShellUnfoldProgressProvider
participant UAC as UnfoldAnimationController
participant Animator as UnfoldTaskAnimator
participant Pool as TransactionPool
participant SC as SurfaceControl
Provider->>UAC: onStateChangeStarted()
UAC->>UAC: check willHandleTransition() → false
UAC->>UAC: mIsInStageChange = true
UAC->>Pool: acquire()
loop Each animator with active tasks
UAC->>Animator: prepareStartTransaction(tx)
Note right of Animator: ensureBackground() for color layer
end
UAC->>SC: tx.apply()
UAC->>Pool: release(tx)
loop Progress 0.0 → 1.0
Provider->>UAC: onStateChangeProgress(progress)
UAC->>Pool: acquire()
loop Each animator with active tasks
UAC->>Animator: applyAnimationProgress(progress, tx)
Note right of Animator: interpolate crop, scale, corner radius
end
UAC->>SC: tx.apply()
UAC->>Pool: release(tx)
end
Provider->>UAC: onStateChangeFinished()
UAC->>Pool: acquire()
loop Each animator
UAC->>Animator: resetAllSurfaces(tx)
UAC->>Animator: prepareFinishTransaction(tx)
Note right of Animator: removeBackground()
end
UAC->>SC: tx.apply()
UAC->>Pool: release(tx)
UAC->>UAC: mIsInStageChange = false
84.3 UnfoldTransitionHandler — Transition Integration
UnfoldTransitionHandler implements both TransitionHandler and
UnfoldListener. It participates in the Shell transition framework, claiming
unfold events as transitions it will manage.
Transition claiming — handleRequest(IBinder transition, TransitionRequestInfo
request):
- Calls
shouldPlayUnfoldAnimation(request)which checks:ValueAnimator.areAnimatorsEnabled()istrue.- Transition type is
TRANSIT_CHANGE. - Display area increased (unfold, not fold) — detected via a
DefaultDisplayChangeannotation with valuesDEFAULT_DISPLAY_UNFOLD (1)andDEFAULT_DISPLAY_FOLD (2).
- Stores
mTransition = transitionand returns a non-nullWindowContainerTransactionto claim the transition.
Animation startup — startAnimation(IBinder, TransitionInfo, startT, finishT,
callback):
- Clears all animators’ task lists.
- Iterates every
TransitionInfo.Change— for applicable tasks (TRANSIT_CHANGEor opening), callsanimator.onTaskAppeared(taskInfo, leash). - For animators with active tasks: applies
prepareStartTransaction(startT)andprepareFinishTransaction(finishT), then starts the animator. - Applies
startT. - Posts a 5-second timeout runnable (
FINISH_ANIMATION_TIMEOUT_MILLIS) to guard against stuck animations.
Transition merging — the handler supports merging concurrent transitions:
tryMergeBubbleTaskTransition()— merges bubble-related always-on-top transitions viaBubbleTaskUnfoldTransitionMerger.tryMergeTaskFragmentClose()— mergesTRANSIT_CLOSEfor task fragments occurring during unfold.
Fold detection — onFoldStateChanged(boolean isFolded) force-finishes
the transition if the device is folded during an in-progress unfold animation.
84.4 Background Rendering During Unfold
UnfoldBackgroundController manages a colored background layer that sits
behind all task surfaces during the animation. Without this layer, the user
would see raw display content (or black) in the areas revealed by the crop
animation.
| Field | Value | Purpose |
|---|---|---|
BACKGROUND_LAYER_Z_INDEX |
-1 |
Places layer behind all app surfaces |
mBackgroundColor |
R.color.unfold_background |
Default unfold background RGB |
mSplitScreenBackgroundColor |
R.color.split_divider_background |
Tinted color for split-screen mode |
Lifecycle:
ensureBackground(Transaction tx)— creates aSurfaceControlcolor layer at z-index -1 if not already present; shows it and sets the current color. If the color changed (e.g., split screen toggled), updates it.removeBackground(Transaction tx)— removes the background layer surface after animation completes.onSplitVisibilityChanged(boolean visible)— switches the active color set betweenmBackgroundColorandmSplitScreenBackgroundColor.
Animator implementations:
FullscreenUnfoldTaskAnimator — for fullscreen (non-split) tasks:
| Parameter | Value | Effect |
|---|---|---|
HORIZONTAL_START_MARGIN |
0.08 (8%) | Left/right crop inset at progress 0.0 |
VERTICAL_START_MARGIN |
0.03 (3%) | Top/bottom crop inset at progress 0.0 |
START_SCALE |
0.94 | Scale factor at progress 0.0 |
END_SCALE |
1.0 | Scale factor at progress 1.0 |
Each frame: interpolate crop rect via RectEvaluator, compute
scale = lerp(0.94, 1.0, progress), apply setWindowCrop(), setMatrix()
with centered scale, setCornerRadius(), and show().
SplitTaskUnfoldAnimator — for split-screen tasks:
- Tracks
mMainStageBounds,mSideStageBounds,mRootStageBoundsviaSplitScreenListenercallbacks. - Uses
CROPPING_START_MARGIN_FRACTION = 0.05(5%). - Excludes margins on the divider side (the edge touching the split bar) and taskbar side (when expanded), so the crop animation only affects outer edges.
Both animators implement DisplayInsetsController.OnInsetsChangedListener to
recalculate bounds when the taskbar insets change, and
ConfigurationChangeListener to update the window corner radius.
84.5 Connection to §59 — Foldable Display Support (Device State Side)
The unfold animation system is the Shell-side consumer of foldable device state events described in §59. The connection chain is:
flowchart LR
subgraph "§59 — Device State Side"
DSM["DeviceStateManager"]
DSP["DeviceStateProvider<br/>(hinge sensor)"]
PROG["UnfoldProgressProvider<br/>(framework)"]
end
subgraph "§84 — Shell Animation Side"
SUPP["ShellUnfoldProgressProvider<br/>(wrapper interface)"]
UAC["UnfoldAnimationController"]
UTH["UnfoldTransitionHandler"]
end
DSP -->|"state callbacks"| DSM
DSM -->|"fold/unfold events"| PROG
PROG -->|"progress 0.0→1.0"| SUPP
SUPP -->|"UnfoldListener"| UAC
SUPP -->|"UnfoldListener"| UTH
ShellUnfoldProgressProvider is the bridge interface — its addListener()
accepts an UnfoldListener with four callbacks:
| Callback | When fired |
|---|---|
onStateChangeStarted() |
Hinge begins moving — animation setup |
onStateChangeProgress(float) |
Continuous progress [0.0, 1.0] — per-frame animation |
onStateChangeFinished() |
Hinge fully open/closed — cleanup |
onFoldStateChanged(boolean) |
Discrete fold state change — used for abort detection |
The NO_PROVIDER sentinel (ShellUnfoldProgressProvider.NO_PROVIDER) is used
on non-foldable devices; its addListener() is a no-op, so no animation
infrastructure is initialized.
Dependency injection uses Dagger qualifiers to maintain separate animator instances for the two paths:
@UnfoldTransition— marks animators for the legacyUnfoldAnimationControllerpath.@UnfoldShellTransition— marks animators for the modernUnfoldTransitionHandlerpath.
The FullscreenUnfoldTaskAnimator is a singleton shared between both paths,
while SplitTaskUnfoldAnimator has separate instances (one per qualifier) to
allow independent task tracking.
84.6 Key Source Files
| File | Path | Purpose |
|---|---|---|
| ShellUnfoldProgressProvider | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java |
Progress wrapper interface with UnfoldListener |
| UnfoldAnimationController | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java |
Legacy-path coordinator — task tracking, progress dispatch |
| UnfoldTransitionHandler | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java |
Shell transition integration — claims unfold transitions |
| UnfoldBackgroundController | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java |
Background color layer management during animation |
| UnfoldTaskAnimator | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/UnfoldTaskAnimator.java |
Core animator interface — task filtering, progress application |
| FullscreenUnfoldTaskAnimator | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java |
Scale + crop animation for fullscreen tasks |
| SplitTaskUnfoldAnimator | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java |
Crop animation for split-screen tasks |
| @UnfoldTransition | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/qualifier/UnfoldTransition.java |
Dagger qualifier for legacy-path animators |
| @UnfoldShellTransition | frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/qualifier/UnfoldShellTransition.java |
Dagger qualifier for Shell-transition-path animators |
Part XXIV: Emerging Platforms
86. XR and Spatial Window Management
86.1 Overview
Android’s XR (Extended Reality) window management introduces a fundamentally different paradigm
where applications can declare how they launch in spatial environments—whether rendering their own
3D scene, letting the system manage spatial composition, or running as traditional 2D panels in a
shared home space. The framework provides manifest-level properties for XR activity start modes,
safety boundary recommendations, and custom animation control, all gated behind the
android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES feature flag. Alongside these newer XR APIs, the
legacy VrController continues to manage VR render thread scheduling and state tracking for
earlier VR mode support.
graph TB
subgraph "XR Window Management Architecture"
MANIFEST["AndroidManifest.xml<br/>XR Properties"]
XWP["XrWindowProperties<br/>Constants & Modes"]
AMS["ActivityManagerService"]
VRC["VrController<br/>Thread Scheduling"]
WMS["WindowManagerService"]
VRMI["VrManagerInternal"]
MANIFEST -->|"declares"| XWP
XWP -->|"start modes<br/>boundary types"| AMS
AMS -->|"VR mode changes"| VRC
VRC -->|"render thread priority"| AMS
VRC <-->|"persistent VR state"| VRMI
AMS -->|"activity launch"| WMS
end
subgraph "XR Permissions (AppOps)"
EYE["Eye Tracking<br/>coarse / fine"]
FACE["Face Tracking"]
HAND["Hand Tracking"]
HEAD["Head Tracking"]
SCENE["Scene Understanding<br/>coarse / fine"]
end
WMS -.->|"enforces"| EYE
WMS -.->|"enforces"| FACE
WMS -.->|"enforces"| HAND
WMS -.->|"enforces"| HEAD
WMS -.->|"enforces"| SCENE
86.2 XrWindowProperties — Manifest Entries
XrWindowProperties is a utility class that defines string constants for XR-specific
<property> entries in the Android manifest. Every constant is annotated with
@FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES), meaning they are only active when the
XR manifest entries feature flag is enabled on the device.
Property: Activity Start Mode
| Constant | Value | Description |
|---|---|---|
PROPERTY_XR_ACTIVITY_START_MODE |
"android.window.PROPERTY_XR_ACTIVITY_START_MODE" |
Declares how an activity launches in XR |
XR_ACTIVITY_START_MODE_UNDEFINED |
"XR_ACTIVITY_START_MODE_UNDEFINED" |
Default; system determines mode based on context |
XR_ACTIVITY_START_MODE_FULL_SPACE_UNMANAGED |
"XR_ACTIVITY_START_MODE_FULL_SPACE_UNMANAGED" |
App renders own space via OpenXR |
XR_ACTIVITY_START_MODE_FULL_SPACE_MANAGED |
"XR_ACTIVITY_START_MODE_FULL_SPACE_MANAGED" |
System renders from app’s scene graph |
XR_ACTIVITY_START_MODE_HOME_SPACE |
"XR_ACTIVITY_START_MODE_HOME_SPACE" |
Launches as 2D panel in shared home space |
Property: Boundary Type Recommendation
| Constant | Value | Description |
|---|---|---|
PROPERTY_XR_BOUNDARY_TYPE_RECOMMENDED |
"android.window.PROPERTY_XR_BOUNDARY_TYPE_RECOMMENDED" |
Recommends safety boundary type |
XR_BOUNDARY_TYPE_NO_RECOMMENDATION |
"XR_BOUNDARY_TYPE_NO_RECOMMENDATION" |
Default; system uses current boundary |
XR_BOUNDARY_TYPE_LARGE |
"XR_BOUNDARY_TYPE_LARGE" |
Requests larger safety boundary for movement |
Property: Custom Animation
| Constant | Value | Description |
|---|---|---|
PROPERTY_XR_USES_CUSTOM_FULL_SPACE_MANAGED_ANIMATION |
"android.window.PROPERTY_XR_USES_CUSTOM_FULL_SPACE_MANAGED_ANIMATION" |
When true, app provides own enter/exit animation |
Properties can be declared at the <application> level (applying to all activities) or
overridden at the individual <activity> level. Setting XR_ACTIVITY_START_MODE_UNDEFINED at
the activity level resets to system-determined behavior.
86.3 XR Activity Start Modes
The three start modes represent fundamentally different rendering paradigms:
graph LR
subgraph "FULL_SPACE_UNMANAGED"
FSU_APP["Application"]
FSU_OPENXR["OpenXR Runtime"]
FSU_GPU["GPU / Compositor"]
FSU_APP -->|"submits frames"| FSU_OPENXR
FSU_OPENXR -->|"renders"| FSU_GPU
end
subgraph "FULL_SPACE_MANAGED"
FSM_APP["Application"]
FSM_SYS["System Scene Manager"]
FSM_COMP["System Compositor"]
FSM_APP -->|"provides scene graph"| FSM_SYS
FSM_SYS -->|"renders"| FSM_COMP
end
subgraph "HOME_SPACE"
HS_APP["Application (2D)"]
HS_PANEL["Panel in Home Space"]
HS_HOME["Home Environment"]
HS_APP -->|"standard views"| HS_PANEL
HS_PANEL -->|"placed in"| HS_HOME
end
FULL_SPACE_UNMANAGED — The application itself controls the entire rendering pipeline using OpenXR. The app has direct access to the XR compositor, manages its own scene graph, and is responsible for all spatial rendering. This mode is intended for immersive XR applications that need maximum rendering control.
FULL_SPACE_MANAGED — The system handles rendering from a scene graph provided by the
application. This mode allows apps to describe spatial content declaratively while the platform
manages the actual rendering, compositing, and integration with system UI elements. The
PROPERTY_XR_USES_CUSTOM_FULL_SPACE_MANAGED_ANIMATION property controls whether the system
default animation plays during enter/exit transitions.
HOME_SPACE — The activity runs as a traditional 2D panel within the XR home environment. This mode provides compatibility for existing Android applications that have not been adapted for spatial rendering, allowing them to appear as floating panels alongside other home space content.
UNDEFINED — The default. The system determines the appropriate mode based on the current activity mode and launching flags. This allows the system to make context-appropriate decisions about spatial presentation.
86.4 VrController — VR Render Thread Scheduling
VrController is a helper class for ActivityManagerService that manages VR mode state and
render thread scheduling. It maintains state through a bitmask of flags and ensures that exactly
one VR-related thread has elevated scheduling priority system-wide.
VR State Flags:
| Flag | Value | Meaning |
|---|---|---|
FLAG_NON_VR_MODE |
0 |
Device is not in VR mode |
FLAG_VR_MODE |
1 |
Top app is in VR mode |
FLAG_PERSISTENT_VR_MODE |
2 |
App set persistent VR flag |
State Machine Behavior:
stateDiagram-v2
[*] --> NON_VR: Initial State
NON_VR: FLAG_NON_VR_MODE (0)
NON_VR: No elevated threads
NON_VR: setVrThread fails
NON_VR: setPersistentVrThread fails
VR_MODE: FLAG_VR_MODE (1)
VR_MODE: Top app may set VR thread
VR_MODE: One thread at FIFO priority
PERSISTENT_VR: FLAG_PERSISTENT_VR_MODE (2)
PERSISTENT_VR: App may set persistent VR thread
PERSISTENT_VR: Regular setVrThread fails
BOTH: VR + PERSISTENT (3)
BOTH: Persistent thread kept
BOTH: setVrThread fails
NON_VR --> VR_MODE: onVrModeChanged(vrMode=true)
VR_MODE --> NON_VR: onVrModeChanged(vrMode=false)
NON_VR --> PERSISTENT_VR: onPersistentVrStateChanged(true)
PERSISTENT_VR --> NON_VR: onPersistentVrStateChanged(false)
VR_MODE --> BOTH: onPersistentVrStateChanged(true)
BOTH --> VR_MODE: onPersistentVrStateChanged(false)
PERSISTENT_VR --> BOTH: onVrModeChanged(vrMode=true)
BOTH --> PERSISTENT_VR: onVrModeChanged(vrMode=false)
Key Methods:
onVrModeChanged(ActivityRecord record)— Called when the top focused activity changes VR mode. Synchronizes withVrManagerInternaland returnstrueif the VR state changed.setVrThreadLocked(int tid, int pid, WindowProcessController proc)— Sets the VR render thread to FIFO scheduling priority. Fails if not in VR mode, persistent VR is set, or the thread is not in the top app.setPersistentVrThreadLocked(int tid, int pid, WindowProcessController proc)— Sets the persistent VR render thread. Only works whenFLAG_PERSISTENT_VR_MODEis set.updateVrRenderThreadLocked(int newTid, boolean suppressLogs)— Transitions the previous render thread back to normal scheduling and elevates the new thread to FIFO priority.onTopProcChangedLocked(WindowProcessController proc)— Updates VR render thread when theTOP_APPprocess changes.
Thread Scheduling: The VR render thread receives elevated FIFO scheduling priority via
ActivityManagerService.scheduleAsFifoPriority(). When a VR render thread is replaced, the old thread is returned
to SCHED_OTHER scheduling. The invariant is maintained that at most one thread system-wide has
this elevated priority.
86.5 Boundary Type Recommendations
The boundary type system allows XR applications to request specific safety boundary configurations:
XR_BOUNDARY_TYPE_NO_RECOMMENDATION — The default value. The system continues using
whichever safety boundary is currently configured. No prompts are shown to the user.
XR_BOUNDARY_TYPE_LARGE — Indicates the application involves significant user movement.
When this is declared, the system will prompt the user to use a larger safety boundary and
verify space clearance if needed. The actual size of the “large” boundary is determined by the
system, not specified by the application—ensuring consistent safety standards across devices.
This declarative approach allows the platform to manage safety without giving applications direct control over physical boundary dimensions, which could pose safety risks.
86.6 XR App Operations and Permissions
The XR framework introduces seven new app operations in AppOpsManager, all gated behind the
xrManifestEntries feature flag:
| Operation | String Key | Purpose |
|---|---|---|
OP_EYE_TRACKING_COARSE |
android:eye_tracking_coarse |
Coarse eye gaze data |
OP_EYE_TRACKING_FINE |
android:eye_tracking_fine |
Precise eye tracking |
OP_FACE_TRACKING |
android:face_tracking |
Facial expression data |
OP_HAND_TRACKING |
android:hand_tracking |
Hand position and gesture |
OP_HEAD_TRACKING |
android:head_tracking |
Head orientation and position |
OP_SCENE_UNDERSTANDING_COARSE |
android:scene_understanding_coarse |
Room-scale environment data |
OP_SCENE_UNDERSTANDING_FINE |
android:scene_understanding_fine |
Detailed spatial mapping |
These operations are initialized conditionally—if the XR feature flag is disabled, they map to
OP_NONE and are effectively no-ops. This allows the same framework code to run on both XR
and non-XR devices.
86.7 Window Management Integration Points
XR activities integrate with the window system at several levels:
ActivityRecord.requestedVrComponent— Set fromActivityInfo.requestedVrComponentduring activity initialization. Used byVrControllerto determine VR mode.LaunchParamsController— Checksactivity.requestedVrComponent != nullto route VR activities to the main display rather than virtual displays.ActivityTaskManagerService.mVr2dDisplayId— A virtual display ID for 2D content during VR mode. Non-VR activities are launched on this display when set, keeping them accessible while the main display is in VR mode.ActivityMetricsLogger— Tracksis_xr_activityfor analytics and performance monitoring.
86.8 Future Direction
XR window management is evolving rapidly within AOSP. The manifest-based property system indicates a direction where spatial behavior is declared rather than imperatively coded. Key trends include:
- Declarative spatial layout — Applications describe spatial intent; the system determines physical placement and rendering strategy.
- Safety-first boundaries — The system controls physical safety boundaries, with apps only able to recommend—not override—safety parameters.
- Unified permission model — XR sensor permissions (eye, face, hand, head, scene tracking) follow the standard app-ops model, ensuring consistent privacy controls.
- Backward-compatible migration — The
HOME_SPACEmode provides a compatibility path for existing 2D applications to function in XR environments without modification. - Feature-flag gating — All XR APIs are behind
FLAG_XR_MANIFEST_ENTRIES, allowing incremental device-by-device rollout.
86.9 Key Source Files
| File | Purpose |
|---|---|
frameworks/base/core/java/android/view/XrWindowProperties.java |
XR manifest property constants and start mode definitions |
frameworks/base/services/core/java/com/android/server/wm/VrController.java |
VR mode state tracking and render thread scheduling |
frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java |
Holds requestedVrComponent for VR activity detection |
frameworks/base/services/core/java/com/android/server/wm/LaunchParamsController.java |
Routes VR activities to appropriate displays |
frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java |
Manages mVr2dDisplayId for 2D-in-VR display |
frameworks/base/core/java/android/app/AppOpsManager.java |
XR sensor permission operations (eye, face, hand, head, scene) |
frameworks/base/services/core/java/com/android/server/wm/WindowProcessController.java |
Process-level VR thread tracking |
Part XXV: Debugging and Observability
87. Window Tracing and Debugging Infrastructure
87.1 Overview
Android’s window management system includes a comprehensive tracing and debugging infrastructure
that spans from Java framework services to native SurfaceFlinger code. The system provides a
dual-implementation tracing architecture—supporting both the legacy Winscope format and modern
Perfetto-based continuous monitoring—alongside transition tracing, surface leak detection, and
the dumpsys window diagnostic interface. This infrastructure enables developers and platform
engineers to capture and analyze the complete state of the window hierarchy, surface transactions,
and display transitions at various granularity levels.
graph TB
subgraph "Window Manager Tracing (Java)"
WT["WindowTracing<br/>(Abstract Base)"]
WTL["WindowTracingLegacy<br/>Winscope Format"]
WTP["WindowTracingPerfetto<br/>Modern Integration"]
WTDS["WindowTracingDataSource<br/>Perfetto Bridge"]
TT["TransitionTracer<br/>(Interface)"]
PTT["PerfettoTransitionTracer"]
WT -->|"if !perfettoWmTracing"| WTL
WT -->|"if perfettoWmTracing"| WTP
WTP --> WTDS
TT --> PTT
end
subgraph "SurfaceFlinger Tracing (Native)"
LT["LayerTracing<br/>Layer State Snapshots"]
LDS["LayerDataSource"]
TXT["TransactionTracing<br/>Transaction Recording"]
TDS["TransactionDataSource"]
LT --> LDS
TXT --> TDS
end
subgraph "Debugging Tools"
DUMP["dumpsys window"]
LEAK["destroyLeakedSurfaces()"]
PROTO["ProtoLog"]
end
PERFETTO["Perfetto<br/>Trace System"]
WTDS -->|"android.windowmanager"| PERFETTO
PTT -->|"transitions"| PERFETTO
LDS -->|"android.surfaceflinger.layers"| PERFETTO
TDS -->|"android.surfaceflinger.transactions"| PERFETTO
87.2 WindowTracing — Dual Implementation Architecture
WindowTracing is the abstract base class that provides the factory method
createDefaultAndStartLooper(). This factory checks the android.tracing.Flags.perfettoWmTracing()
flag at runtime and returns either WindowTracingLegacy or WindowTracingPerfetto.
Core Abstract Interface:
WindowTracing (abstract)
├── createDefaultAndStartLooper(service, choreographer) // Factory
├── startTrace(PrintWriter pw) // Begin tracing
├── stopTrace(PrintWriter pw) // End tracing
├── saveForBugreport(PrintWriter pw) // Snapshot for bugreport
├── logState(String where) // Schedule state capture
├── dumpToProto(ProtoOutputStream, logLevel, where, ts) // Serialize WM state
└── abstract log(String where) // Subclass implementation
Key Constants:
| Constant | Value | Purpose |
|---|---|---|
TAG |
"WindowTracing" |
Log tag |
WHERE_START_TRACING |
"trace.enable" |
Trigger identifier for trace start |
WHERE_ON_FRAME |
"onFrame" |
Frame callback trigger |
The base class uses Choreographer frame callbacks for frame-based tracing—transactions trigger
checks via shouldLogOnFrame() and shouldLogOnTransaction() to determine capture timing.
87.3 WindowTracingLegacy — Winscope Format
The legacy implementation writes traces in the Winscope protobuf format to
/data/misc/wmtrace/wm_trace.winscope. It uses an in-memory TraceBuffer with configurable
capacity based on the log level.
Buffer Capacity by Log Level:
| Level | Constant | Buffer Size | What’s Captured |
|---|---|---|---|
CRITICAL (2) |
BUFFER_CAPACITY_CRITICAL |
5 MB | Only visible windows, minimal overhead |
TRIM (1) |
BUFFER_CAPACITY_TRIM |
10 MB | All windows, reduced config data (default) |
ALL (0) |
BUFFER_CAPACITY_ALL |
20 MB | All elements with maximum information |
Shell Commands:
dumpsys window trace start # Start tracing, reset buffer
dumpsys window trace stop # Stop and write to file
dumpsys window trace save-for-bugreport # Snapshot for bugreport
dumpsys window trace frame # Set frame-based logging
dumpsys window trace transaction # Set transaction-based logging
dumpsys window trace level [all|trim|critical] # Set log level
dumpsys window trace size <KB> # Custom buffer size
87.4 WindowTracingPerfetto — Modern Continuous Monitoring
The Perfetto-based implementation registers the android.windowmanager data source with the
Perfetto tracing framework. It uses atomic session counters to track how many Perfetto tracing
sessions are active for each logging frequency.
sequenceDiagram
participant PC as Perfetto Client
participant DS as WindowTracingDataSource
participant WTP as WindowTracingPerfetto
participant WMS as WindowManagerService
participant CH as Choreographer
PC->>DS: Start trace session
DS->>DS: Parse config (level, frequency)
DS->>WTP: onStart(config)
WTP->>WTP: Increment session counter<br/>(frame or transaction)
loop Per Vsync Frame (if frame sessions > 0)
CH->>WTP: FrameCallback
WTP->>WMS: dumpToProto()
WMS-->>WTP: Protobuf state
WTP->>DS: Write trace packet
end
PC->>DS: Stop trace session
DS->>WTP: onStop(instance)
WTP->>WTP: Decrement session counter
Session Configuration:
| Config | Description |
|---|---|
LOG_FREQUENCY_FRAME |
Snapshot per Choreographer frame |
LOG_FREQUENCY_TRANSACTION |
Snapshot per surface transaction |
LOG_FREQUENCY_SINGLE_DUMP |
One-time snapshot on start event |
Data Source Properties:
- Name:
android.windowmanager - Buffer Policy:
PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_DROP - PostponeStop:
true(ensures final state is captured) - Default Config:
TRIMlevel,FRAMEfrequency
87.5 What Gets Traced
The Window Manager tracing captures the complete WMS state hierarchy serialized as protobuf:
| Category | Data Captured |
|---|---|
| Windows | All WindowState objects — position, size, visibility, flags, layer, type |
| Displays | DisplayContent — display info, rotation, configuration, bounds |
| Focus | Current focused window and app, input focus state |
| Animations | Active window animations, transition states |
| Tasks | Task hierarchy, stack ordering, windowing modes |
| Activities | ActivityRecord — lifecycle state, visibility, configuration |
| Input | Input windows, touch regions, input channel states |
| Surfaces | Surface hierarchy, layer assignments, visibility |
The logLevel parameter controls verbosity:
- ALL — Every element with all fields populated
- TRIM — All elements but with reduced configuration data
- CRITICAL — Only currently visible windows with minimal fields
87.6 TransitionTracer — Shell Transition Logging
The TransitionTracer interface and its PerfettoTransitionTracer implementation capture
window transition lifecycle events. The tracer logs transitions through their complete lifecycle.
Interface Methods:
TransitionTracer (interface)
├── logSentTransition(transition, targets) // Transition dispatched to Shell
├── logFinishedTransition(transition) // Transition completed
├── logAbortedTransition(transition) // Transition aborted
├── logRemovingStartingWindow(startingData) // Starting window removal
├── startTrace(PrintWriter pw)
├── stopTrace(PrintWriter pw)
├── isTracing() → boolean
└── saveForBugreport(PrintWriter pw)
Logged Transition Fields (ShellTransition Proto):
| Proto Field | Description |
|---|---|
ID |
Sync ID identifying the transition |
CREATE_TIME_NS |
When the transition was created |
SEND_TIME_NS |
When sent to Shell for execution |
FINISH_TIME_NS |
Completion timestamp |
WM_ABORT_TIME_NS |
Abort timestamp (if aborted) |
START_TRANSACTION_ID |
Starting surface transaction |
FINISH_TRANSACTION_ID |
Finishing surface transaction |
TYPE |
Transition type (open, close, etc.) |
FLAGS |
Transition flags |
TARGETS |
Array of window/layer changes (MODE, FLAGS, LAYER_ID, WINDOW_ID) |
STARTING_WINDOW_REMOVE_TIME_NS |
When starting window was removed |
87.7 SurfaceFlinger Tracing — LayerTracing and TransactionTracing
The native SurfaceFlinger provides its own Perfetto-integrated tracing for layer state and surface transactions.
graph TB
subgraph "LayerTracing"
LT_ACTIVE["MODE_ACTIVE<br/>Per-vsync snapshots"]
LT_GEN["MODE_GENERATED<br/>Snapshots on flush"]
LT_BUG["MODE_GENERATED_BUGREPORT_ONLY<br/>On bugreport flush"]
LT_DUMP["MODE_DUMP<br/>Single snapshot on start"]
LT_FLAGS["Trace Flags"]
LT_INPUT["TRACE_INPUT (1<<1)"]
LT_COMP["TRACE_COMPOSITION (1<<2)"]
LT_EXTRA["TRACE_EXTRA (1<<3)"]
LT_HWC["TRACE_HWC (1<<4)"]
LT_BUF["TRACE_BUFFERS (1<<5)"]
LT_VD["TRACE_VIRTUAL_DISPLAYS (1<<6)"]
LT_FLAGS --- LT_INPUT
LT_FLAGS --- LT_COMP
LT_FLAGS --- LT_EXTRA
LT_FLAGS --- LT_HWC
LT_FLAGS --- LT_BUF
LT_FLAGS --- LT_VD
end
subgraph "TransactionTracing"
TT_ACT["MODE_ACTIVE<br/>Ring buffer per transaction"]
TT_CONT["MODE_CONTINUOUS<br/>Ring buffer on flush"]
TT_BUF512["Continuous Buffer<br/>512 KB"]
TT_BUF100["Active Buffer<br/>100 MB"]
TT_ACT --> TT_BUF100
TT_CONT --> TT_BUF512
end
PERF["Perfetto"]
LT_ACTIVE -->|"android.surfaceflinger.layers"| PERF
TT_ACT -->|"android.surfaceflinger.transactions"| PERF
TT_CONT -->|"android.surfaceflinger.transactions"| PERF
LayerTracing captures layer state snapshots with configurable trace flags:
TRACE_INPUT— Input window regionsTRACE_COMPOSITION— Composition type and stateTRACE_EXTRA— Additional metadataTRACE_HWC— Hardware Composer stateTRACE_BUFFERS— Buffer queue stateTRACE_VIRTUAL_DISPLAYS— Virtual display layersTRACE_ALL— Combination of INPUT, COMPOSITION, and EXTRA
Data source name: android.surfaceflinger.layers with shmem_size_hint_kb = 1024 (each entry
is approximately 50 KB).
TransactionTracing records surface transactions using a dedicated worker thread:
- Binder thread queues transactions via
addQueuedTransaction()(lock-freeLocklessStack) - Main thread signals committed transactions via
addCommittedTransactions() - Worker thread processes and buffers transactions in
loop()
Output file: /data/misc/wmtrace/transactions_trace.winscope
87.8 dumpsys window — Command Interface
The dumpsys window command is implemented through WindowManagerShellCommand, invoked from
WindowManagerService.onShellCommand(). Key subcommands include:
| Command | Purpose |
|---|---|
dumpsys window trace |
Shows current trace status |
dumpsys window windows |
Lists all windows with state |
dumpsys window displays |
Display configuration and hierarchy |
dumpsys window policy |
Window policy controller state |
dumpsys window tokens |
Window token hierarchy |
dumpsys window a |
All window manager state |
The trace status output includes:
- For Legacy: Enabled/Disabled, log level, buffer usage statistics
- For Perfetto: Enabled/Disabled, sessions logging “on frame” / “on transaction”
87.9 Surface Leak Detection — destroyLeakedSurfaces()
DisplayContent.destroyLeakedSurfaces() actively detects and destroys surfaces from dead
processes or hidden applications. It iterates all windows on the display and checks for two
leak conditions:
Session Leak — The window’s session no longer exists in mWmService.mSessions:
Log: "LEAKED SURFACE (session doesn't exist): <window>"
Action: Destroy surface via transaction, add to mForceRemoves
Hidden App Leak — The window belongs to an activity that is no longer client-visible:
Log: "LEAKED SURFACE (app token hidden): <window>"
ProtoLog: "SURFACE LEAK DESTROY: <window>"
Action: Destroy surface via transaction
The method is called from RootWindowContainer.destroyLeakedSurfaces() and returns true if
any leaks were found and destroyed. The surface destruction is batched into a single
SurfaceControl.Transaction for efficiency.
flowchart TD
START["destroyLeakedSurfaces()"]
ITER["forAllWindows()"]
CHECK_SC{"mSurfaceControl<br/>!= null?"}
CHECK_SESSION{"Session exists in<br/>mSessions?"}
CHECK_VISIBLE{"ActivityRecord<br/>isClientVisible()?"}
SESSION_LEAK["LEAKED SURFACE<br/>(session doesn't exist)"]
APP_LEAK["LEAKED SURFACE<br/>(app token hidden)"]
DESTROY["wsa.destroySurface(t)"]
FORCE["Add to mForceRemoves"]
APPLY["transaction.apply()"]
RETURN["Return: leaks found?"]
START --> ITER
ITER --> CHECK_SC
CHECK_SC -->|"null"| ITER
CHECK_SC -->|"exists"| CHECK_SESSION
CHECK_SESSION -->|"no"| SESSION_LEAK
CHECK_SESSION -->|"yes"| CHECK_VISIBLE
CHECK_VISIBLE -->|"not visible"| APP_LEAK
CHECK_VISIBLE -->|"visible"| ITER
SESSION_LEAK --> DESTROY
DESTROY --> FORCE
APP_LEAK --> DESTROY
FORCE --> ITER
ITER -->|"done"| APPLY
APPLY --> RETURN
87.10 Perfetto Integration for Continuous Monitoring
All tracing components register as Perfetto data sources, enabling unified trace collection across the entire graphics pipeline:
| Data Source | Component | Layer |
|---|---|---|
android.windowmanager |
WindowTracingPerfetto | Java (Framework) |
| Shell transitions | PerfettoTransitionTracer | Java (Framework) |
android.surfaceflinger.layers |
LayerDataSource | Native (C++) |
android.surfaceflinger.transactions |
TransactionDataSource | Native (C++) |
Common Properties:
- Buffer exhaustion policy: Stall and Drop across all data sources
- Bugreport detection via
FlushFlags::Reason::kTraceClone+CloneTarget::kBugreport - Trace data serialized as protobuf packets
The Perfetto integration enables several workflows:
- On-device continuous tracing — Always-on low-overhead monitoring
- Triggered dumps — Single snapshots for specific debugging
- Bugreport integration — Automatic trace inclusion in bug reports
- Winscope analysis — Post-hoc visualization of window state over time
87.11 Key Source Files
| File | Purpose |
|---|---|
frameworks/base/services/core/java/com/android/server/wm/WindowTracing.java |
Abstract base with factory method and Choreographer integration |
frameworks/base/services/core/java/com/android/server/wm/WindowTracingLegacy.java |
Legacy Winscope format writer (5/10/20 MB buffers) |
frameworks/base/services/core/java/com/android/server/wm/WindowTracingPerfetto.java |
Modern Perfetto data source integration |
frameworks/base/services/core/java/com/android/server/wm/WindowTracingDataSource.java |
Perfetto data source bridge with config parsing |
frameworks/base/services/core/java/com/android/server/wm/WindowTracingLogLevel.java |
Log level annotation: ALL(0), TRIM(1), CRITICAL(2) |
frameworks/base/services/core/java/com/android/server/wm/WindowTracingLogFrequency.java |
Log frequency annotation: FRAME(0), TRANSACTION(1), SINGLE_DUMP(2) |
frameworks/base/services/core/java/com/android/server/wm/TransitionTracer.java |
Transition tracing interface |
frameworks/base/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java |
Perfetto-based transition lifecycle logging |
frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java |
Surface leak detection via destroyLeakedSurfaces() |
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java |
Shell command entry point and trace initialization |
frameworks/native/services/surfaceflinger/Tracing/LayerTracing.h |
Native layer state snapshot tracing |
frameworks/native/services/surfaceflinger/Tracing/LayerDataSource.h |
Perfetto data source for layer traces |
frameworks/native/services/surfaceflinger/Tracing/TransactionTracing.h |
Native transaction recording with ring buffer |
frameworks/native/services/surfaceflinger/Tracing/TransactionDataSource.h |
Perfetto data source for transaction traces |
Part XXVI: Content Capture and Protection
88. Screen Recording and Content Protection
88.1 Overview
Android’s window management system provides a layered approach to screen recording and content
protection. The ContentRecordingController orchestrates recording sessions at the system level,
managing which display or task is being captured through virtual display mirroring. The
ScreenRecordingCallbackController enables applications to detect when they are being recorded,
allowing them to take protective action. The SensitiveContentPackages system and FLAG_SECURE
provide complementary mechanisms to block screen capture for specific windows or applications—
protecting sensitive content such as passwords, financial data, and notifications from being
captured in recordings or screenshots.
graph TB
subgraph "Recording Infrastructure"
MP["MediaProjection<br/>Manager"]
CRC["ContentRecordingController<br/>Session Orchestration"]
CR["ContentRecorder<br/>Per-Display Recording"]
VD["Virtual Display<br/>Mirror Output"]
MP -->|"session events"| CRC
CRC -->|"manages"| CR
CR -->|"mirrors to"| VD
end
subgraph "App Notification"
SRCC["ScreenRecordingCallbackController"]
MPWC["MediaProjectionWatcherCallback"]
CB["IScreenRecordingCallback<br/>(per-app)"]
MPWC -->|"start/stop events"| SRCC
SRCC -->|"dispatches"| CB
end
subgraph "Content Protection"
FS["FLAG_SECURE<br/>Window Flag"]
SCP["SensitiveContentPackages<br/>Dynamic Blocking"]
DPC["DevicePolicyCache<br/>Enterprise Policy"]
SEC["isSecureLocked()<br/>Combined Check"]
FS --> SEC
SCP --> SEC
DPC --> SEC
end
MP --> MPWC
SEC -->|"blocks capture of"| CR
88.2 ScreenRecordingCallbackController — Recording State Notification
ScreenRecordingCallbackController manages the lifecycle of screen recording notifications
to applications. It allows apps to register IScreenRecordingCallback instances to be
informed when they are visible within an active screen recording session.
Key Data Structures:
| Field | Type | Purpose |
|---|---|---|
mCallbacks |
ArrayMap<IBinder, Callback> |
Registered callbacks keyed by binder |
mLastInvokedStateByUid |
ArrayMap<Integer, Boolean> |
Last notified recording state per UID |
mRecordedWC |
WindowContainer |
Currently recorded window container |
mWatcherCallbackRegistered |
boolean |
Whether MediaProjection watcher is registered |
Registration Flow:
sequenceDiagram
participant App as Application
participant SRCC as ScreenRecordingCallbackController
participant MPS as MediaProjectionService
participant WMS as WindowManagerService
App->>SRCC: register(IScreenRecordingCallback)
SRCC->>SRCC: ensureMediaProjectionWatcherCallbackRegistered()
SRCC->>MPS: addCallback(MediaProjectionWatcherCallback)
MPS-->>SRCC: Current MediaProjectionInfo (or null)
SRCC->>SRCC: setRecordedWindowContainer(info)
SRCC->>SRCC: uidHasRecordedActivity(callingUid)
SRCC-->>App: Return current recording state
Note over MPS,SRCC: Later, when recording starts...
MPS->>SRCC: onStart(MediaProjectionInfo)
SRCC->>SRCC: setRecordedWindowContainer(info)
SRCC->>SRCC: getRecordedUids()
SRCC->>App: onScreenRecordingStateChanged(true)
Note over MPS,SRCC: When recording stops...
MPS->>SRCC: onStop(MediaProjectionInfo)
SRCC->>App: onScreenRecordingStateChanged(false)
SRCC->>SRCC: mRecordedWC = null
Window Container Resolution: When a recording starts, the controller determines the recorded
window container based on MediaProjectionInfo.getLaunchCookie():
- Null cookie → Records the default display (
mWmService.mRoot.getDefaultDisplay()) - Non-null cookie → Finds the matching
ActivityRecordby launch cookie and records its task
Visibility Tracking: The onProcessActivityVisibilityChanged() method is called when process
visibility changes during an active recording. It performs three-level deduplication:
- Skip if recording is not active or no callback registered for the UID
- Skip if the state hasn’t changed from last invocation
- Skip if the visibility change doesn’t actually affect the UID’s recording status
Death Recipient: Each registered callback is linked to a DeathRecipient that automatically
unregisters the callback if the client process dies, preventing resource leaks.
88.3 ContentRecordingController — Session Orchestration
ContentRecordingController manages the handoff between displays when recording sessions change.
It supports only one content recording session device-wide at any time and handles six
distinct scenarios:
| Scenario | Condition | Action |
|---|---|---|
| Invalid | Incoming session is malformed | Ignored |
| Ignored | Same session as current | No action |
| Start | New session, no existing | Begin recording immediately |
| Takeover | New session, different from existing | Stop old, start new |
| Updating | Same display, consent newly granted | Update session in place |
| Stopping | Incoming is null, session active | Stop recording |
Session Management Logic:
flowchart TD
IN["setContentRecordingSessionLocked<br/>(incomingSession)"]
VALID{"Session valid?"}
SAME{"Same display as<br/>current session?"}
CONSENT{"Consent newly<br/>granted?"}
NEW_NULL{"incomingSession<br/>== null?"}
HAS_PREV{"Previous session<br/>exists?"}
GET_DC["Get/Create DisplayContent<br/>for virtual display ID"]
DC_NULL{"DisplayContent<br/>found?"}
SET_SESSION["displayContent.setContentRecordingSession()"]
UPDATE["displayContent.updateRecording()"]
PAUSE["Pause old session<br/>on previous display"]
CACHE["Update mSession<br/>and mDisplayContent"]
IN --> VALID
VALID -->|"invalid"| REJECT["Return (ignored)"]
VALID -->|"valid"| SAME
SAME -->|"yes"| CONSENT
CONSENT -->|"no"| REJECT2["Return (duplicate)"]
CONSENT -->|"yes"| GET_DC
SAME -->|"no"| NEW_NULL
NEW_NULL -->|"no"| GET_DC
NEW_NULL -->|"yes"| HAS_PREV
GET_DC --> DC_NULL
DC_NULL -->|"null"| REJECT3["Return (display gone)"]
DC_NULL -->|"found"| SET_SESSION
SET_SESSION --> UPDATE
UPDATE --> HAS_PREV
HAS_PREV -->|"yes"| PAUSE
HAS_PREV -->|"no"| CACHE
PAUSE --> CACHE
88.4 ContentRecorder — Per-Display Recording Engine
ContentRecorder is the per-DisplayContent class that manages the actual recording mechanics.
It implements WindowContainerListener to react to changes in the recorded window container.
Recording Content Types:
| Type | Constant | Source |
|---|---|---|
| Display | RECORD_CONTENT_DISPLAY |
Entire display content |
| Task | RECORD_CONTENT_TASK |
Specific task (app window) |
| Below Overlay | RECORD_CONTENT_BELOW_OVERLAY |
Display content below overlay layer |
Recording Lifecycle:
updateRecording()— Entry point called on display changes. Pauses if display has own content; starts recording if needed.startRecordingIfNeeded()— Validates preconditions (display off, already recording, waiting for consent, PIP mode), retrieves the targetWindowContainer, creates a mirrored surface hierarchy viaSurfaceControl.mirrorSurface().updateMirroredSurface()— Computes scaling to fit the recorded content into the virtual display output surface, applying letterboxing as needed.pauseRecording()— Removes the mirrored surface, restores windowing and overlay layers.stopRecording()— Full stop includingclearContentRecordingSession()and listener unregistration.
Surface Mirroring Architecture:
The recorder creates a mirror of the source SurfaceControl and reparents it to the virtual
display’s root surface. Simultaneously, it reparents the virtual display’s own windowing and
overlay layers to null, preventing any content launched directly on the virtual display from
appearing in the mirrored output. This ensures the recording shows only the intended content.
Configuration Change Handling: The recorder tracks orientation and windowing mode changes
through onConfigurationChanged(). For task recording, if the task enters PIP mode, recording
is paused (bounds become inaccurate in PIP). When orientation changes, the surface scaling is
recomputed. The MediaProjectionManager is notified of windowing mode and capture bounds changes.
88.5 Integration with FLAG_SECURE and SensitiveContentPackages
Content protection operates at the window level through WindowState.isSecureLocked(), which
combines three independent protection mechanisms:
boolean isSecureLocked() {
// 1. Global disable for testing
if (mWmService.getDisableSecureWindows()) return false;
// 2. Per-window FLAG_SECURE
if ((mAttrs.flags & FLAG_SECURE) != 0) return true;
// 3. Dynamic sensitive content protection
if (mWmService.mSensitiveContentPackages.shouldBlockScreenCaptureForApp(
pkg, uid, windowToken)) return true;
// 4. Enterprise device policy
return !DevicePolicyCache.getInstance().isScreenCaptureAllowed(mShowUserId);
}
FLAG_SECURE — Set by applications via WindowManager.LayoutParams.FLAG_SECURE. When set,
the window’s surface is marked as secure via transaction.setSecure(mSurfaceControl, true),
causing SurfaceFlinger to replace the window’s content with black in any screen capture or
recording.
SensitiveContentPackages — A dynamic cache of package/UID/window-token combinations that should be blocked from screen capture. This system supports two protection modes:
| Protection Mode | Flag | Granularity |
|---|---|---|
| Sensitive Content App Protection | sensitiveContentAppProtection() |
Per-window (specific window token) |
| Sensitive Notification App Protection | sensitiveNotificationAppProtection() |
Per-package (null window token) |
The PackageInfo class encapsulates the protected entity:
mPkg— Package namemUid— Application UIDmWindowToken— Specific window token (null for package-level blocking)
Management Methods:
addBlockScreenCaptureForApps()— Adds packages to the protection setremoveBlockScreenCaptureForApps()— Removes packages from protectionclearBlockedApps()— Clears all protections
This dynamic system allows the notification system to protect sensitive notifications without
requiring the app itself to set FLAG_SECURE. When a sensitive notification is posted, the
system can add the package to the blocked set; when the notification is dismissed, the package
is removed.
88.6 Virtual Display Interaction for Recording
Content recording is inherently tied to virtual displays. The recording architecture works as follows:
graph LR
subgraph "Source"
SD["Source Display<br/>(or Task)"]
SC["SurfaceControl<br/>Hierarchy"]
end
subgraph "Virtual Display"
VD["VirtualDisplay<br/>(created by MediaProjection)"]
MS["Mirrored Surface<br/>(via mirrorSurface)"]
ROOT["Root SurfaceControl"]
end
subgraph "Consumer"
SURF["Output Surface<br/>(from app)"]
ENC["MediaCodec / Encoder"]
FILE["Recording File"]
end
SC -->|"SurfaceControl.mirrorSurface()"| MS
MS -->|"reparented to"| ROOT
ROOT -->|"renders to"| SURF
SURF -->|"consumed by"| ENC
ENC --> FILE
Key Implementation Details:
-
Mirror Creation:
SurfaceControl.mirrorSurface(sourceSurface, stopAt)creates a live mirror of the source hierarchy. ThestopAtparameter (when recording overlay flag is enabled) excludes content above the owner’s top overlay window. -
Layer Isolation: The virtual display’s own
windowingLayerandoverlayLayerare reparented tonull, preventing any directly-launched content from appearing in the recording. -
Scaling:
computeScaling()calculates the appropriate scale to fit the recorded content into the output surface, maintaining aspect ratio with letterboxing. -
Consent Flow: Recording sessions can be created in a
waitingForConsentstate. Recording does not begin until consent is granted and the session is updated. -
Surface Size Detection:
fetchSurfaceSizeIfPresent()retrieves the output surface dimensions. If the surface is not yet available, recording startup is deferred.
88.7 App-Level Screen Capture APIs and Window System Hooks
Applications interact with screen recording through several API layers:
MediaProjection API — The primary screen capture mechanism. Applications request a
MediaProjection through MediaProjectionManager, which triggers a system consent dialog.
Once granted, the projection creates a virtual display that receives mirrored content managed
by ContentRecorder.
IScreenRecordingCallback — Applications register to be notified of recording state changes.
The register() call returns the current recording state immediately and the callback receives
future state changes via onScreenRecordingStateChanged(boolean visibleInScreenRecording).
ContentRecordingSession — The session object that describes what to record:
virtualDisplayId— The target virtual displaycontentToRecord— DISPLAY, TASK, or BELOW_OVERLAYtokenToRecord— Window token for task recordingdisplayToRecord— Display ID for display recordingtargetUid— UID of the recording targetisWaitingForConsent— Whether consent has been granted
Window System Hooks:
DisplayContent.setContentRecordingSession()— Assigns a recording session to a displayDisplayContent.updateRecording()— Triggers recording state evaluationDisplayContent.pauseRecording()— Pauses recording on the displayWindowState.isSecureLocked()— Determines if a window should be black in capturesWindowState.canScreenshotIme()— Returns!isSecureLocked(), controlling whether the IME surface can be included in screenshots
88.8 Key Source Files
| File | Purpose |
|---|---|
frameworks/base/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java |
Per-app recording state notification with UID-level tracking |
frameworks/base/services/core/java/com/android/server/wm/ContentRecordingController.java |
System-wide recording session orchestration (one session at a time) |
frameworks/base/services/core/java/com/android/server/wm/ContentRecorder.java |
Per-display recording engine with surface mirroring and scaling |
frameworks/base/services/core/java/com/android/server/wm/SensitiveContentPackages.java |
Dynamic screen capture blocking for sensitive content/notifications |
frameworks/base/services/core/java/com/android/server/wm/WindowState.java |
isSecureLocked() combining FLAG_SECURE, sensitive content, and device policy |
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java |
Central service hosting SensitiveContentPackages and recording controllers |
frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java |
Display-level recording session management and ContentRecorder hosting |
frameworks/base/core/java/android/view/ContentRecordingSession.java |
Session descriptor with content type, display ID, and consent state |
frameworks/base/core/java/android/window/IScreenRecordingCallback.aidl |
AIDL interface for recording state callbacks |
frameworks/base/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java |
Policy enforcement for FLAG_SECURE on virtual displays |
Part XXVII: Reference and Evolution
55. Architectural Evolution and Future Direction
Android’s window and display system has undergone continuous architectural evolution, driven by the need to support new form factors (foldables, connected displays, AR glasses), improve performance on the composition hot path, and enable richer desktop-class windowing.
55.1 Five Major Architectural Shifts
| Shift | From | To | Key Impact |
|---|---|---|---|
| SurfaceFlinger FrontEnd | Monolithic Layer objects with interleaved client/composition state | Separated RequestedLayerState / LayerSnapshot pipeline |
Predictable hot-path performance, snapshot cloning for background consumers |
| Shell Transitions | WMS-internal prepareAppTransition / AppTransitionController |
Shell-driven TransitionHandler chain with ITransitionPlayer |
Transition logic moved out of system_server into SystemUI shell |
| Desktop Experience | Tablet-only freeform windowing | 133 DesktopExperienceFlags controlling full desktop paradigm |
External display support, multi-desk, windowing mode switching |
| Virtual Display Composition | LegacyVirtualDisplaySurface with synchronous sink IPC |
VirtualDisplaySurface with SinkSurfaceHelper thread isolation |
Prevents app-side deadlocks from blocking SF main thread |
| Virtual Device Framework | Basic DisplayManager.createVirtualDisplay() |
Full VirtualDeviceManager with input, sensors, audio, camera |
Complete companion device virtualization stack |
55.2 Architectural Timeline
graph LR
A["Android 4.4<br/>VirtualDisplaySurface<br/>introduced"] --> B["Android 10<br/>CompositionEngine<br/>extracted"]
B --> C["Android 12<br/>Shell Transitions<br/>ITransitionPlayer"]
C --> D["Android 13<br/>VirtualDeviceManager<br/>CDM integration"]
D --> E["Android 14<br/>FrontEnd refactor<br/>LayerLifecycleManager"]
E --> F["Android 15<br/>Desktop windowing<br/>DesktopModeFlags"]
F --> G["Android 16<br/>DesktopExperienceFlags<br/>133 flags"]
G --> H["Current (main)<br/>VirtualDisplaySurface v2<br/>Multi-desk support"]
55.3 SurfaceFlinger FrontEnd Pipeline Refactoring
The FrontEnd refactoring (under frameworks/native/services/surfaceflinger/FrontEnd/) replaced the monolithic Layer class with a clean five-stage pipeline optimized for predictability on the composition hot path.
Pipeline stages:
graph TD
A["TransactionHandler<br/>Queue and filter ready transactions"] --> B["LayerLifecycleManager<br/>Apply transactions, manage lifecycle"]
B --> C["LayerHierarchyBuilder<br/>Build/update traversal graph"]
C --> D["LayerSnapshotBuilder<br/>Generate z-ordered LayerSnapshots"]
D --> E["CompositionEngine<br/>Consume snapshots, emit to HWC"]
E --> F["Callback emission<br/>Release fences, frame stats"]
Core data structures:
| Class | Purpose | Key Properties |
|---|---|---|
RequestedLayerState |
Stores client-requested state per layer | Extends layer_state_t; tracks 21 change flag types (Created, Destroyed, Hierarchy, Geometry, Content, Buffer, etc.) |
LayerSnapshot |
Read-only snapshot for CompositionEngine/RenderEngine | Contains globalZ, transformedBounds, roundedCorner, inputInfo, frameRate, reachability |
LayerLifecycleManager |
Owns RequestedLayerState collection, manages lifecycle |
Tracks layers via mIdToLayer map; provides ILifecycleListener for add/destroy callbacks |
LayerHierarchy |
Graph-based hierarchy representation | Mirrored layers share nodes with multiple parents (no cloning) |
LayerSnapshotBuilder |
Incremental snapshot generator | Supports ForceUpdateFlags::NONE/ALL/HIERARCHY; tryFastUpdate() for buffer-only changes |
TransactionHandler |
Transaction queuing and filtering | Per-ApplyToken ordering; barrier support for cross-process synchronization |
Design principles:
- Simple buffer updates should be consistently fast (avoid contention and context switching)
- Change flags enable partial hierarchy updates and short-circuiting
- Snapshots can be cloned for non-critical consumers (input, accessibility) on background threads
- The current implementation moves (not copies) snapshots from FrontEnd to CompositionEngine to avoid work on the hot path
55.4 Shell Transitions Architecture Evolution
Shell Transitions moved transition animation logic from WindowManagerService (system_server) into the SystemUI Shell process, enabling richer animations and decoupled development.
Transition lifecycle:
--start--> PENDING --onTransitionReady--> READY --play--> ACTIVE --finish--> done
--merge--> MERGED --^
Key architectural elements:
| Component | Role |
|---|---|
ITransitionPlayer |
Binder interface from WMCore to Shell; Shell registers as the transition player |
Transitions (Shell) |
Central coordinator; manages tracks, dispatches to handlers |
TransitionHandler |
Interface for handling specific transition types (PIP, split, desktop, keyguard) |
DefaultTransitionHandler |
Fallback handler using TransitionAnimation from WM resources |
MixedTransitionHandler |
Handles transitions spanning multiple transition types (e.g., PIP + split) |
RemoteTransitionHandler |
Delegates to app-provided RemoteTransition objects |
OneShotRemoteHandler |
Single-use remote handler for specific transitions |
KeyguardTransitionHandler |
Keyguard-specific transitions (occlude/unocclude) |
Track-based parallelism: Multiple transitions can animate simultaneously across different tracks. Within a track, animations are serialized. When a transition conflicts with multiple active tracks, it is marked SYNC and all tracks must flush before it plays.
Custom transition types defined in Shell:
| Constant | Value | Purpose |
|---|---|---|
TRANSIT_EXIT_PIP |
FIRST_CUSTOM + 1 |
Expanding PIP to fullscreen |
TRANSIT_REMOVE_PIP |
FIRST_CUSTOM + 3 |
Dismissing PIP |
TRANSIT_SPLIT_SCREEN_PAIR_OPEN |
FIRST_CUSTOM + 4 |
Opening two tasks in split |
TRANSIT_SPLIT_DISMISS |
FIRST_CUSTOM + 7 |
Dismissing split-screen |
TRANSIT_MAXIMIZE |
FIRST_CUSTOM + 8 |
Freeform to maximized |
TRANSIT_RESTORE_FROM_MAXIMIZE |
FIRST_CUSTOM + 9 |
Maximized to freeform |
TRANSIT_MINIMIZE |
FIRST_CUSTOM + 20 |
Minimizing a task |
TRANSIT_DESKTOP_MODE_TYPES |
FIRST_CUSTOM + 100 |
Desktop mode transition base |
55.5 DesktopExperienceFlags Taxonomy
DesktopExperienceFlags is an enum of 133 flags controlling all aspects of the desktop windowing experience. Each flag wraps an aconfig flag function and a shouldOverrideByDevOption boolean, allowing the persist.wm.debug.desktop_experience_devopts system property to force-enable flags for developer preview.
Flag categories (representative, not exhaustive):
| Category | Example Flags | Count |
|---|---|---|
| Connected displays | CONNECTED_DISPLAYS_CURSOR, ENABLE_CONNECTED_DISPLAYS_DND, ENABLE_CONNECTED_DISPLAYS_PIP, ENABLE_CONNECTED_DISPLAYS_WALLPAPER |
~15 |
| Desktop windowing core | ENABLE_DESKTOP_APP_LAUNCH_BUGFIX, ENABLE_DESKTOP_IME_BUGFIX, ENABLE_DESKTOP_TASKBAR_ON_FREEFORM_DISPLAYS |
~25 |
| Window management | ENABLE_DRAG_TO_MAXIMIZE, ENABLE_TILE_RESIZING, ENABLE_FULLSCREEN_WINDOW_CONTROLS, ENABLE_WINDOW_DECORATION_REFACTOR |
~20 |
| Multi-desk | ENABLE_MULTIPLE_DESKTOPS_BACKEND, ENABLE_MULTIPLE_DESKTOPS_FRONTEND, ENABLE_KEYBOARD_SHORTCUTS_TO_SWITCH_DESKS |
~10 |
| Display management | DISPLAY_TOPOLOGY, ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT, ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING |
~15 |
| PIP / Split | ENABLE_INTERACTIVE_PICTURE_IN_PICTURE, ENABLE_CROSS_DISPLAYS_PIP_TASK_LAUNCH, ENABLE_DRAGGING_PIP_ACROSS_DISPLAYS |
~10 |
| Desktop-first policy | ENABLE_DESKTOP_FIRST_LISTENER, ENABLE_DESKTOP_FIRST_POLICY_IN_LPM, FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH |
~8 |
| Transitions / Animations | ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS, ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION |
~8 |
| Compatibility / Bugfixes | ENABLE_CAMERA_COMPAT_EXTERNAL_DISPLAY_ROTATION_BUGFIX, various *_BUGFIX flags |
~22 |
Override mechanism: The isTrue() method checks if the developer option override is active (persist.wm.debug.desktop_experience_devopts). If the flag has shouldOverrideByDevOption = true and the override is enabled, the flag returns true regardless of its aconfig value.
Reference files:
frameworks/native/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h— Layer lifecycle management and change trackingframeworks/native/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h— Incremental snapshot generationframeworks/native/services/surfaceflinger/FrontEnd/RequestedLayerState.h— Client-requested layer state with 21 change flag typesframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java— Shell transition coordinatorframeworks/base/core/java/android/window/DesktopExperienceFlags.java— 133 desktop experience flags
56. Key Files Reference
56.1 Window Manager Service
| File | Purpose |
|——|———|
| frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java | Central WMS |
| frameworks/base/services/core/java/com/android/server/wm/WindowState.java | Per-window state |
| frameworks/base/services/core/java/com/android/server/wm/Task.java | Task container |
| frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java | Activity state |
| frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java | Per-display WMS state |
| frameworks/base/services/core/java/com/android/server/wm/DisplayArea.java | Display area hierarchy |
| frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java | Multi-display root |
| frameworks/base/services/core/java/com/android/server/wm/DragDropController.java | System-wide drag management |
| frameworks/base/services/core/java/com/android/server/wm/DragState.java | Per-drag state machine |
56.2 Shell Components
| File | Purpose |
|——|———|
| frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java | Task lifecycle management |
| frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java | Shell transition dispatch |
| frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java | Window decoration base |
| frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java | Split-screen management |
| frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java | PiP transitions |
| frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java | Shell drag handler |
| frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java | Drop zone visualization |
| frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/SplitDragPolicy.java | Drop target policy (672 lines) |
| frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt | WMS-to-Shell drag bridge |
| frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java | Per-drag session state |
| frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt | Desktop mode tasks |
56.3 SurfaceFlinger and Composition
| File | Purpose |
|——|———|
| frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp | Main compositor |
| frameworks/native/services/surfaceflinger/Layer.h | Layer base class |
| frameworks/native/services/surfaceflinger/CompositionEngine/ | Composition pipeline |
| frameworks/native/services/surfaceflinger/DisplayHardware/HWComposer.h | HWC HAL abstraction |
56.4 Graphics Buffer Pipeline
| File | Purpose |
|——|———|
| frameworks/native/libs/gui/include/gui/BufferQueue.h | BufferQueue factory |
| frameworks/native/libs/gui/include/gui/BufferQueueCore.h | Core state management |
| frameworks/native/libs/gui/include/gui/BufferSlot.h | Slot state machine |
| frameworks/native/libs/gui/BufferQueueProducer.cpp | Producer operations (dequeue, queue, cancel) |
| frameworks/native/libs/gui/BufferQueueConsumer.cpp | Consumer operations (acquire, release) |
| frameworks/native/libs/gui/include/gui/BLASTBufferQueue.h | Transaction-based delivery |
| frameworks/native/libs/gui/BLASTBufferQueue.cpp | BLASTBufferQueue implementation |
| frameworks/native/libs/ui/include/ui/GraphicBuffer.h | Cross-process buffer |
| frameworks/native/libs/ui/include/ui/Fence.h | Sync fence wrapper |
56.5 Virtual Display
| File | Purpose |
|——|———|
| frameworks/native/services/surfaceflinger/DisplayHardware/VirtualDisplay/VirtualDisplaySurface.h | Three-BQ routing |
| frameworks/native/services/surfaceflinger/DisplayHardware/VirtualDisplay/SinkSurfaceHelper.h | Thread-isolated sink |
| frameworks/base/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java | Virtual device implementation |
| frameworks/base/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java | Window policy control |
56.6 Hardware Abstraction
| File | Purpose |
|——|———|
| hardware/interfaces/graphics/composer/aidl/ | HWC AIDL interface |
| hardware/interfaces/graphics/allocator/aidl/ | Gralloc allocator AIDL |
| hardware/interfaces/graphics/mapper/stable-c/ | Gralloc mapper stable-C |
| hardware/interfaces/graphics/common/aidl/android/hardware/graphics/common/BufferUsage.aidl | Buffer usage flags |
56.7 Emulator Graphics
| File | Purpose |
|——|———|
| device/generic/goldfish/hals/hwc3/GuestFrameComposer.h | Emulator CPU composition |
| device/generic/goldfish/hals/hwc3/HostFrameComposer.h | Emulator host GPU composition |
| device/generic/goldfish/hals/hwc3/DrmClient.h | DRM/KMS display output |
| device/generic/goldfish/hals/gralloc/allocator.cpp | Emulator gralloc allocator |
56.8 Native (InputFlinger)
| File | Purpose |
|——|———|
| frameworks/native/services/inputflinger/dispatcher/InputDispatcher.h | Input event dispatcher — window target resolution |
| frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp | Dispatcher implementation (touch/key routing) |
| frameworks/native/services/inputflinger/reader/include/InputReader.h | Raw input event reader from evdev |
| frameworks/native/services/inputflinger/InputManager.h | InputFlinger service entry point |
56.9 Input System (Framework)
| File | Purpose |
|——|———|
| frameworks/base/services/core/java/com/android/server/input/InputManagerService.java | Java-side input management service |
| frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java | WM-to-InputDispatcher bridge (per display) |
| frameworks/base/core/java/android/view/InputChannel.java | Socketpair transport for input events |
| frameworks/base/core/java/android/view/InputEventReceiver.java | App-side input event receiver |
| frameworks/base/core/java/android/view/InputEvent.java | Base input event class |
56.10 Documentation
| File | Purpose |
|——|———|
| frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md | Architecture motivation |
| frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md | SystemUI integration patterns |
| frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/docs/transitions.md | Transition system design |
| frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md | Threading model |
| frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md | DI architecture |
| frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md | Development guidelines |