Still under review.

Table of Contents

Part I: Foundations and Overview

  1. Executive Summary
  2. System Architecture Overview
  3. Android vs Linux Graphics Stack
  4. Display Servers: Android SurfaceFlinger vs Linux Wayland/X11

Part II: Core Objects — Activity, Window, and Task

  1. Activity and Window Relationship
  2. Task — The Central Connecting Concept

Part III: Window Management System

  1. WM Core (Server-Side)
  2. WM Shell Library
  3. SystemUI and Launcher3 Integration
  4. Dependency Injection Architecture
  5. Threading Model
  6. Shell Feature Modules
  7. Custom Window Manager and Shell Development
  8. SurfaceControlViewHost and Embedded Windows

Part IV: Transitions, Animation, and Surface Leash

  1. Transition System
  2. Surface Leash — Deep Dive
  3. Animation Framework Architecture
  4. Window Animation System
  5. Shell Transition Animations

Part V: Display System

  1. Display System Architecture
  2. DisplayManagerService
  3. DisplayArea Hierarchy and Policy
  4. Insets System
  5. Surface System and Composition
  6. Window-Display Interaction Model

Part VI: Multi-Window System

  1. Multi-Window Architecture
  2. Windowing Modes and Bounds
  3. Multi-Window Operations
  4. Window Manager Paradigm Analysis: Stacking vs Tiling
  5. Multi-Window Evolution
  6. Embedded Activity

Part VII: Multi-Display System

  1. Multi-Display Architecture
  2. Virtual Displays
  3. Display Topology and Spatial Layout
  4. Cross-Display Window Movement

Part VIII: Rendering Pipeline

  1. Graphics Buffer Pipeline: From Buffer to Activity
  2. HWUI Performance Architecture
  3. Window Shadow System
  4. HDR for Window and Surface
  5. Android’s Direct Rendering Infrastructure
  6. Hardware Composer (HWC)
  7. Gralloc — Graphics Memory Allocator
  8. RenderEngine — SurfaceFlinger’s GPU Compositor

Part IX: Cross-Cutting Topics

  1. Input System and Focus Management — InputFlinger pipeline, SurfaceFlinger↔InputFlinger relationship, window targeting, ViewRootImpl InputStage chain, keyboard shortcuts
  2. Per-Variant Shell and WM Customization
  3. SystemUI Multi-Display Architecture
  4. Launcher3 and Recents Multi-Display Architecture
  5. Multi-Window and Multi-Display Test Infrastructure
  6. Caption Bar Architecture — Legacy DecorView vs Shell-based, SurfaceControl hierarchy, WindowDecoration, drag-to-move/resize, view host pooling
  7. 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

  1. BufferQueue and BLASTBufferQueue Architecture — Slot state machine, triple buffering, producer-consumer, BLAST transaction-based delivery
  2. Buffer Sharing Architecture and Lifecycle — Gralloc HAL, fence synchronization, cross-process sharing, end-to-end flow
  3. Virtual Display Composition Pipeline — SurfaceFlinger internals, VirtualDisplaySurface, three-BQ routing, SinkSurfaceHelper
  4. CompanionDeviceManager and Virtual Device Framework — Device profiles, VirtualDeviceImpl, window policy control, input routing, mirror streaming

Part XI: Display Refresh and Frame Scheduling

  1. 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

  1. Screen Rotation and Orientation — DisplayRotation, SeamlessRotator, AsyncRotationController, FixedRotationTransformState, sensor orientation detection
  2. Foldable Display Support — DeviceStateManagerService, FoldableDeviceStateProvider, LogicalDisplayMapper display swapping, BookStyleDeviceStatePolicy
  3. Keyguard and Lock Screen Window Management — KeyguardController, KeyguardViewMediator, KeyguardTransitionHandler, occlude/unocclude, AOD integration
  4. Starting Windows and Splash Screens — StartingWindowController, StartingSurfaceDrawer, SplashScreenView, SnapshotController, exit animations
  5. 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

  1. Wallpaper Window System — WallpaperController, wallpaper targeting, parallax offsets, freeze mechanism, multi-display wallpaper
  2. Display Color Management — ColorDisplayService, night display, white balance, saturation, daltonizer, SurfaceFlinger color pipeline
  3. Window Types and Z-Order Policy — TYPE_* constants, 36-layer model, sub-window ordering, permission requirements
  4. Accessibility Window Management — AccessibilityController, magnification, window change detection, input interception, security
  5. IME Window Management — Three-target architecture, ImeInsetsSourceProvider, SOFT_INPUT flags, multi-display IME routing

Part XIV: Shell Feature Implementations

  1. Picture-in-Picture (PIP) Architecture — PipTaskOrganizer, PipTransition state machine, PipAnimationController, touch/stash, TV PIP, PIP v2
  2. Bubbles System Architecture — BubbleController, BubbleStackView, BubbleData, expanded views, overflow, Shell transitions integration
  3. Split Screen Implementation — StageCoordinator, SplitLayout, divider mechanics, enter/exit transitions, task snapping
  4. Desktop Windowing Mode — DesktopModeController, DesktopTasksController, freeform window management, caption integration, task positioning
  5. Predictive Back and Back Navigation — BackNavigationController, BackAnimationController, CrossTaskBackAnimation, gesture flow, custom app callbacks

Part XV: Client-Side Architecture

  1. ViewRootImpl and View-to-WMS Bridge — IWindowSession, relayout protocol, Choreographer, InputStage chain, RenderThread, measure-layout-draw

Part XVI: System Configuration and Multi-User

  1. Configuration Change Propagation — ConfigurationContainer hierarchy, three-tier config, cascade chain, triggers, callback vs restart
  2. Multi-User Window Isolation — Per-user state tracking, user switch protocol, task filtering, display assignment, work profiles

Part XVII: Task Lifecycle and Snapshots

  1. Recent Tasks and Task Snapshots — RecentTasks management, SnapshotController, capture pipeline, persistence, cache, starting windows

Part XVIII: Security and Privacy

  1. Window Privacy and Security — FLAG_SECURE, SensitiveContentPackages, DisplayHash, screen recording callbacks, MDM, secure composition

Part XIX: System UI Windows

  1. Notification Shade and Status Bar Windows — ShadeWindowLayoutParams, NotificationShadeWindowState, gesture handling, Z-order, input control

Part XX: Power and Performance

  1. Power Management and Window System — AWAKE→DREAMING→DOZING→ASLEEP, DreamManagerService, AOD, DisplayPowerController, sleep tokens
  2. Game Mode and Performance Hints — GameManagerService, SystemPerformanceHinter, RefreshRatePolicy, TaskFpsCallbackController

Part XXI: Task Management and System Alerts

  1. Task Affinity and Launch Modes — 5 launch modes, ActivityStarter pipeline, task affinity, intent flags, task reparenting
  2. Toast and System Alert Windows — TYPE_TOAST constraints, SYSTEM_ALERT_WINDOW permission, AlertWindowNotification, overlay opacity, tapjacking defense

Part XXII: Accessibility and Input Features

  1. One-Handed Mode — DisplayAreaOrganizer scaling, OneHandedController, Y-offset animation, touch/timeout handling
  2. System Gestures and Navigation Bar — Edge swipe detection, gesture exclusion zones, 3 navigation modes, NavBarFadeAnimationController, transient bars

Part XXIII: Foldable Shell Features

  1. Unfold Animation System — Dual-path architecture, UnfoldAnimationController, UnfoldTransitionHandler, FullscreenUnfoldTaskAnimator, SplitTaskUnfoldAnimator

Part XXIV: Emerging Platforms

  1. XR and Spatial Window Management — XrWindowProperties, VrController, XR app operations, window management integration

Part XXV: Debugging and Observability

  1. Window Tracing and Debugging Infrastructure — Legacy Winscope, Perfetto, TransitionTracer, LayerTracing, TransactionTracing, dumpsys

Part XXVI: Content Capture and Protection

  1. Screen Recording and Content Protection — ScreenRecordingCallbackController, ContentRecordingController, ContentRecorder, FLAG_SECURE, virtual display interaction

Part XXVII: Reference and Evolution

  1. Architectural Evolution and Future Direction
  2. Key Files Reference

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 SurfaceControl references.

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. SurfaceControl objects are Binder-referenced; buffer handles are transmitted as Binder FDs.

3. GPU Driver Access

  • Linux/Wayland: Clients can directly open /dev/dri/renderD128 for 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_buffer wraps a DMA-BUF fd passed directly via the Wayland socket. Zero copy: client renders into a GBM buffer, hands fd to compositor.
  • Android: BufferQueue is a circular buffer of GraphicBuffer slots. Producer and consumer share a BufferQueue via Binder references. Buffer handles (native_handle_t with 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_hwcomposer provides 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:


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:

  1. 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.

  2. 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.

  3. Compositor owns display: Both SurfaceFlinger and Wayland compositors control the actual display hardware (KMS in Linux; HWC2 in Android).

  4. Separate input routing: Both systems route input through a privileged intermediary (Wayland compositor owns evdev; Android InputDispatcher distributes 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:


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:

  1. Activity.attach() creates PhoneWindow → stores in mWindow
  2. Activity.onCreate() calls setContentView()PhoneWindow.setContentView() inflates layout into DecorView.mContentParent
  3. Activity.onResume()WindowManagerGlobal.addView(decorView, params) → creates ViewRootImpl
  4. ViewRootImpl opens IWindowSession to server → Session.addToDisplay()WindowManagerService.addWindow()
  5. Server creates WindowState, attaches to ActivityRecord (which extends WindowToken)

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:

  • ActivityRecord contains WindowState objects as children (inherited from WindowContainer)
  • WindowState.mActivityRecord holds a back-reference to its owning ActivityRecord
  • ActivityRecord.findMainWindow() searches its children for the primary application window (type TYPE_BASE_APPLICATION or TYPE_APPLICATION_STARTING)

An Activity can own multiple WindowState objects:

  • Main application window (TYPE_BASE_APPLICATION)
  • Starting/splash window (mStartingWindow, type TYPE_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 WindowContainerTransactionSurfaceControl.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 management
  • frameworks/base/services/core/java/com/android/server/wm/WindowState.java — Individual window state tracking
  • frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java — Default Window implementation for activities
  • frameworks/base/core/java/android/view/ViewRootImpl.java — Client-side window management and rendering root
  • frameworks/base/core/java/android/view/WindowManagerImpl.java — Client-side WindowManager API
  • frameworks/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: Configuration inheritance 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:

  1. If resultTo == null && mInTask == null && !mAddingToTask && FLAG_ACTIVITY_NEW_TASK → return null (force new task)
  2. If source Activity exists (mSourceRecord != null) → return sourceRecord.getTask()
  3. If mInTask is set (explicit task target) → return mInTask
  4. 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):

  1. TaskOrganizerController.prepareLeash(task) creates a child SurfaceControl of the Task’s surface
  2. ITaskOrganizer.onTaskAppeared(RunningTaskInfo, leash) delivers this leash to Shell
  3. Shell applies position, scale, alpha to the leash — animating the entire Task without touching individual windows

Task surface methods:

  • makeSurface() — inherited from WindowContainer, creates the Task’s own SurfaceControl. The inner DecorSurfaceContainer class separately manages mContainerSurface (layer container) + mDecorSurface (TaskFragment decoration dividers)
  • updateSurfaceSize(t) — applies setWindowCrop() to constrain rendering to task bounds
  • assignChildLayers(t) — manages z-ordering of child TaskFragments and Activities within the Task
  • reparentSurfaceControl(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 implementation
  • frameworks/base/services/core/java/com/android/server/wm/TaskFragment.java — Task fragment for activity embedding
  • frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java — Root of the window hierarchy
  • frameworks/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 Animatable interface

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 service
  • frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java — Container hierarchy base class
  • frameworks/base/services/core/java/com/android/server/wm/WindowToken.java — Window grouping token
  • frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java — Per-display window management
  • frameworks/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:

  1. Implements a public interface (e.g., SplitScreen, Pip, Bubbles)
  2. Has a controller managing core logic
  3. Has transition handlers for Shell-driven animations
  4. Exposes an AIDL-based external interface for cross-process access (e.g., Launcher3)
  5. Registers as a TaskListener with ShellTaskOrganizer

Reference files:

  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java — Unified task organizer
  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java — DI component interface
  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java — Shell initialization lifecycle
  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java — Shell event hub
  • frameworks/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, ShellInterfaceImpl
  • frameworks/base/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java — CoreStartable binding SysUI events to Shell
  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExternalInterfaceBinder.java — Permission-enforcing binder wrapper
  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java — Shell-SystemUI DI bridge
  • packages/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 module
  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java — Component interface with 15 exports
  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java — Common bindings shared across variants
  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java — Thread and executor providers
  • frameworks/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

  1. Shell Main Thread: All Shell feature controllers execute on @ShellMainThread. External calls are posted to this thread before execution.
  2. SysUI Main Thread: UI updates and SysUI callbacks run on the main (UI) thread via @ExternalMainThread executor.
  3. Cross-thread calls: Marshaled via executors; ShellController.ShellInterfaceImpl wraps calls with mMainExecutor.execute()
  4. Blocking calls: createExternalInterfaces() and handleCommand() use ShellExecutor.executeBlocking() with 2-second CountDownLatch timeout
  5. Binder calls to WM: Cross-process via AIDL, handled on Binder thread pool
  6. 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 providers
  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java — Shell executor interface with executeBlocking()
  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java — Handler-backed executor with boost semantics
  • frameworks/base/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/annotations/ShellMainThread.java — Main thread annotation
  • frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.javamGlobalLock, mH, and mAnimationHandler

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 with PipTaskOrganizer, 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 controller
  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java — Split screen controller
  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt — Desktop mode controller
  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java — Freeform task listener
  • frameworks/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 state
  • finishTransaction: to be applied by the handler when animation ends
  • Each TransitionInfo.Change contains 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 API
  • frameworks/base/core/java/android/window/ITaskOrganizer.aidl — Task organizer AIDL interface
  • frameworks/base/core/java/android/window/WindowContainerTransaction.java — Atomic WM operations
  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java — Shell transition handling
  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md — Shell architecture overview
  • frameworks/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 WindowViewRootImplWindowState 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): The ViewRootImpl that drives rendering
  • mWm (line 61): WindowlessWindowManager — does NOT call into WMS
  • mSurfaceControl (line 63): Root SurfaceControl for 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): root SurfaceControl for all windows in this host
  • mStateForWindow (line 80): HashMap<IBinder, State> — tracks per-window SurfaceControl
  • mRealWm (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 WindowState exists in WM Core for SCVH windows
  • No ActivityRecord association
  • The host’s SurfaceControl (from getSurfacePackage()) 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 API
  • frameworks/base/core/java/android/view/WindowlessWindowManager.java — Windowless WM for embedded surfaces
  • frameworks/base/core/java/android/view/SurfaceControlViewHost.java (inner class SurfacePackage) — Cross-process surface package
  • frameworks/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 communication
  • frameworks/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.javacreateAnimationLeash() (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.javagetLeashSurface() (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 lifecycle
  • frameworks/base/services/core/java/com/android/server/wm/Transition.java — leash creation for transitions
  • frameworks/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 (-1 when stopped)
  • mDuration — total animation time (default 300ms)
  • mInterpolator — easing function (default AccelerateDecelerateInterpolator)
  • mValuesPropertyValuesHolder[] defining animated properties
  • mDurationScale — system-wide animation speed multiplier

Per-frame execution:

  1. AnimationHandler calls doAnimationFrame(long frameTime) each VSYNC
  2. Calculates elapsed fraction: (currentTime - mStartTime) / (mDuration * mDurationScale)
  3. Applies interpolator: mInterpolator.getInterpolation(fraction) → eased progress (0→1)
  4. Each PropertyValuesHolder evaluates its value at the interpolated fraction
  5. 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 execution
  • playSequentially(Animator...) — sequential execution
  • play(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 WindowAnimationSpec for 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 SurfaceAnimator
  • frameworks/base/services/core/java/com/android/server/wm/LocalAnimationAdapter.java — Bridges AnimationSpec to SurfaceAnimationRunner
  • frameworks/base/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java — Off-thread animation executor using SurfaceFlinger VSYNC
  • frameworks/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?

  1. Animation transforms (scale, translate, alpha) apply to the leash, not the window itself
  2. The window’s content surface remains unmodified — no layout thrashing
  3. Animations can be cancelled cleanly by simply removing the leash
  4. Animation transfer between windows is possible without interruption

Animation lifecycle:

  1. startAnimation() — creates leash, reparents window surface under leash, hands leash to AnimationAdapter
  2. Per-frame — adapter applies transforms to leash via SurfaceControl.Transaction
  3. 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 (extends ValueAnimator) with SurfaceFlinger VSYNC timing
  • Transaction application deferred to CALLBACK_TRAVERSAL callback

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:

  1. scheduleAnimation() → posts mAnimationFrameCallback to Choreographer
  2. animate(long frameTimeNs) fires each VSYNC:
    • For each DisplayContent: updateWindowsForAnimator() + prepareSurfaces()
    • Checks isAnimating(CHILDREN, ANIMATION_TYPE_ALL) to continue scheduling
    • Merges all DisplayContent.mPendingTransaction into a single transaction
    • mTransaction.apply() → sends batched surface operations to SurfaceFlinger

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 interface
  • frameworks/base/services/core/java/com/android/server/wm/TransitionController.java — Transition lifecycle management replacing legacy AppTransition
  • frameworks/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_exit
  • activity_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 → taskOpenEnterAnimation
  • TRANSIT_OPEN + !isTask + entering → activityOpenEnterAnimation
  • TRANSIT_CLOSE + isTask + exiting → taskCloseExitAnimation
  • TRANSIT_TO_FRONT + entering → taskToFrontEnterAnimation
  • Wallpaper and translucent variants have dedicated attribute paths

19.4 Shell Animation Execution

DefaultTransitionHandler processes each TransitionInfo.Change:

  1. Determine animation: loadAnimation() selects the Animation object based on transition type, flags, and activity options
  2. Initialize: Animation.initialize(animWidth, animHeight, parentWidth, parentHeight)
  3. Constrain: restrictDuration(MAX_ANIMATION_DURATION) caps at 1,500ms; scaleCurrentDuration(animationScale) applies system scale
  4. Build surface animator: DefaultSurfaceAnimator.buildSurfaceAnimation() creates a ValueAnimator (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

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 IRemoteTransition for app open/close transitions
  • Shell delegates animation to the remote process via Binder
  • Remote receives leash SurfaceControl and 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
  • SYNC transitions 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, and TransitionHandler interface
  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java — Default animation handler for standard open/close/change transitions
  • frameworks/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 layout
  • frameworks/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() elevates android.display and android.anim to THREAD_GROUP_TOP_APP
  • Default display gate: System blocks at PHASE_WAIT_FOR_DEFAULT_DISPLAY up to 10s (× HW_TIMEOUT_MULTIPLIER) until LocalDisplayAdapter reports a physical display
  • Two-phase registration: Default adapters (Local, Virtual) during onStart(); additional adapters (Overlay, WiFi) during systemReady()

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 position
  • setDisplayProperties() — WM reports whether display has content
  • performTraversal() — WM requests SurfaceFlinger transaction execution

Reference files:

  • frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java — Display lifecycle management, boot sequence
  • frameworks/base/services/core/java/com/android/server/display/LogicalDisplay.java — OS-level display abstraction
  • frameworks/base/services/core/java/com/android/server/display/DisplayDevice.java — Abstract hardware display representation
  • frameworks/base/services/core/java/com/android/server/display/DisplayDeviceRepository.java — Central device registry
  • frameworks/base/services/core/java/com/android/server/display/DisplayPowerController.java — Display power state and brightness control
  • frameworks/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 for FEATURE_ROOT, manages per-display root leashes
  • RootTaskDisplayAreaOrganizer - Registers for FEATURE_DEFAULT_TASK_CONTAINER, manages task areas

Reference files:

  • frameworks/base/services/core/java/com/android/server/wm/DisplayAreaPolicy.java — Policy interface for DisplayArea hierarchy construction
  • frameworks/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 builder
  • frameworks/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):

  1. Status Bar: Applies to both tasks (full width inset)
  2. Navigation Bar: Full width at bottom/side; both tasks must account for it
  3. Per-Task Insets: Each task computes its own insets relative to its bounds
  4. IME: When IME appears, affects only the focused task’s layout; other task maintains position
  5. Inset Source Override: InsetsSourceProvider can have per-window override frames via mOverrideFrameProviders

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:

  1. ImeInsetsSourceProvider (services/core/.../wm/ImeInsetsSourceProvider.java:47): Extends InsetsSourceProvider with freeze/thaw semantics. The mFrozen flag decouples server visibility from dispatch to prevent flickering during IME transitions

  2. IME Target Selection: DisplayContent.getImeTarget() determines which window receives IME insets — the focused window with FLAG_NOT_FOCUSABLE not set

  3. Client-side control: InsetsController manages IME animations. Apps can use WindowInsetsAnimation.Callback to synchronize layout with IME show/hide animation

  4. InsetsAnimationControlImpl: Drives interpolation between shown/hidden states using SurfaceControl.Transaction to move the IME surface in sync with app content

  5. IME in Multi-Display: Each DisplayContent has its own ImeInsetsSourceProvider, 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 arrays
  • frameworks/base/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java — IME insets source management
  • frameworks/base/core/java/android/view/InsetsController.java — Client-side insets animation control
  • frameworks/base/core/java/android/view/InsetsAnimationControlImpl.java — Animation interpolation engine
  • frameworks/base/services/core/java/com/android/server/wm/InsetsSourceProvider.java — Base insets source provider
  • frameworks/base/services/core/java/com/android/server/wm/InsetsStateController.java — Per-display insets state management
  • frameworks/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 compositor
  • frameworks/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 operations
  • frameworks/native/services/surfaceflinger/SurfaceFlinger.h — Native compositor main header
  • frameworks/native/services/surfaceflinger/Layer.h — Compositable layer (maps to SurfaceControl)
  • frameworks/native/services/surfaceflinger/DisplayDevice.h — Physical/virtual display output
  • frameworks/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:

  1. DisplayDevice - LogicalDisplay: 1:1 mapping (n:1 possible for mirroring); managed by LogicalDisplayMapper
  2. LogicalDisplay - DisplayContent: 1:1 via displayId; DMS creates LogicalDisplay, WM creates corresponding DisplayContent
  3. DisplayContent - SurfaceControl: Each WindowContainer creates a corresponding SurfaceControl, forming a parallel tree
  4. WM - DMS communication: Via DisplayManagerInternal — WM sends display info overrides; DMS sends hardware change notifications
  5. WM - SurfaceFlinger: Via SurfaceControl.Transaction — BLAST sync ensures frame-level consistency
  6. 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_WINDOW permission
  • 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.javaassignChildLayers(), applySurfaceChangesTransaction(), performLayout()
  • frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java — System window layout rules and post-layout policy
  • frameworks/base/services/core/java/com/android/server/wm/WindowSurfacePlacer.java — Coordinates surface placement passes across all displays
  • frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.javamoveRootTaskToDisplay() 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

  1. Activity Launch: ActivityStarter processes launch request, consults LaunchParamsController
  2. Bounds Calculation: Chain of LaunchParamsModifier instances calculates target windowing mode and bounds
  3. Task Creation/Reuse: Task created in appropriate windowing mode within TaskDisplayArea
  4. Shell Notification: ShellTaskOrganizer dispatches to appropriate listener based on windowing mode
  5. Surface Management: Shell feature controller manages the task’s SurfaceControl leash 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 requests
  • frameworks/base/services/core/java/com/android/server/wm/LaunchParamsController.java — Chains LaunchParamsModifier instances
  • frameworks/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 mode
  • tasksAreFloating(): True for FREEFORM or PINNED
  • canResizeTask(): 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_FULLSCREEN even 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 split
  • SNAP_TO_2_33_66 / SNAP_TO_2_66_33 — One-third / two-thirds
  • SNAP_TO_2_90_10 / SNAP_TO_2_10_90 — Minimized splits
  • SNAP_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 fields
  • frameworks/base/services/core/java/com/android/server/wm/ConfigurationContainer.java — Configuration resolution chain
  • frameworks/base/services/core/java/com/android/server/wm/TaskFragment.java — resolveOverrideConfiguration() with windowing mode enforcement
  • frameworks/base/core/java/android/content/pm/ActivityInfo.java — RESIZE_MODE constants and WindowLayout
  • frameworks/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_TYPE bitmask constants from DragPositioningCallback:
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 TaskPositioner and Transitions.TransitionHandler
  • Applies resize bounds in real-time via WindowContainerTransaction during 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) — updates WindowConfiguration bounds
  • setDragResizing(token, boolean) — signals drag state to WM Core (flag CHANGE_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:

  1. User clicks maximize button in window header
  2. Current bounds saved via repository.saveBoundsBeforeMaximize()
  3. Destination calculated via calculateMaximizeBounds() (stable bounds of display)
  4. If task is tiled, snapEventHandler.removeTaskIfTiled() releases it
  5. ToggleResizeDesktopTaskTransitionHandler starts transition with TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE

Restore Flow:

  1. User clicks restore button (task already maximized)
  2. Saved pre-maximize bounds retrieved, or default bounds calculated via calculateDefaultDesktopTaskBounds()
  3. 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()):

  1. Task removed from tiling if applicable
  2. Check for PiP conversion (if ENABLE_DESKTOP_WINDOWING_PIP flag is set, minimize can convert to PiP instead)
  3. Regular minimize path: wct.reorder(taskInfo.token, false /* onTop */) pushes task behind others
  4. Desktop exit: If last visible task is minimized, performDesktopExitCleanUp() brings home/wallpaper forward

Animation: DesktopMinimizationTransitionHandler handles the visual transition.

Transition Types:

  • TRANSIT_MINIMIZE — standard minimize
  • TRANSIT_DESKTOP_MODE_TASK_LIMIT_MINIMIZE — minimize due to task count limit
  • TRANSIT_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):

  • SnapEventHandler interface 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:

  1. User clicks close (X) button in window header
  2. DesktopTasksController creates WCT with removeTask(token)
  3. Transition started with TRANSIT_CLOSE

Animation (CloseDesktopTaskTransitionHandler):

  • Filters changes with mode TRANSIT_CLOSE and WINDOWING_MODE_FREEFORM
  • Two parallel animations:
    • Bounds (200ms): Scale down to 95% with center anchor, offset Y by 36dp downward, STANDARD_ACCELERATE interpolator
    • Alpha (100ms): Fade from 1.0 to 0.0, LINEAR interpolator
  • 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 mode
  • CANCEL_SPLIT_LEFT/RIGHT — enter split-select at specified position
  • CANCEL_BUBBLE_LEFT/RIGHT — convert to bubble

28.7 PiP Operations

Picture-in-Picture operations are managed by PipTaskOrganizer and PipMotionHelper.

Enter PiP

  1. Activity calls enterPictureInPictureMode()
  2. PipTransitionState progresses: UNDEFINED → TASK_APPEARED → ENTRY_SCHEDULED → ENTERING_PIP → ENTERED_PIP
  3. 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, sets PipBoundsState.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():

  1. If split inactive: prepareActiveSplit() → activates split root, adds task to side stage
  2. If split active: prepareBringSplit() → reparents top task to main stage, brings split forward
  3. WCT operations: setReparentLeafTaskIfRelaunch(false), evictAllChildren(), reparentTopTask(), addTask(), activateSplit()

Exit Split Screen

StageCoordinator.prepareExitSplitScreen(stageToTop, wct, exitReason):

  1. Removes all tasks from non-top stage via removeAllTasks()
  2. Sets windowing mode to fullscreen for top stage tasks
  3. Reparents root task to display area if on non-default display
  4. Calls deactivateSplit() and mSplitState.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 placement
  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java — Phone-specific PiP controller managing enter/exit/dismiss and gesture interactions
  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java — Central split-screen orchestrator managing two stages, divider, enter/exit flows
  • frameworks/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 DividerSnapAlgorithm targets 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:

  1. SurfaceControl layers still exist: Both split-screen stages have SurfaceControl objects with z-orders. The “non-overlapping” property is enforced by setting Rect bounds, not by removing z-order.
  2. 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.
  3. 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.
  4. Transition animations require z-order: The Shell transition system animates windows by manipulating SurfaceControl layers 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 constraints
  • frameworks/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 of Task) 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:supportsPictureInPicture attribute
  • 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.java signal 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 fragment
  • mIsEmbeddedtrue when this TaskFragment is embedded (not the root Task)
  • mRelativeEmbeddedBounds — bounds relative to parent Task (not display)
  • mAdjacentTaskFragments — tracks split-paired TaskFragments via AdjacentSet

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_EMBEDDING in embedded Activity’s ActivityInfo and EMBED_ANY_APP_IN_UNTRUSTED_MODE permission 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 change
  • TASK_FRAGMENT_TRANSIT_OPEN — new Activity appeared
  • TASK_FRAGMENT_TRANSIT_CLOSE — Activity finished
  • TASK_FRAGMENT_TRANSIT_CHANGE — resize/configuration change
  • TASK_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_FRAGMENT
  • wct.reparentActivityToTaskFragment(fragmentToken, activityToken)OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT
  • wct.setAdjacentTaskFragments(token1, token2, params)OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS
  • wct.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:

  1. App declares SplitPairRule (e.g., “Activity A and Activity B should split 50/50”)
  2. When Activity B launches, SplitController intercepts and evaluates rules
  3. SplitPresenter creates two TaskFragments with calculated bounds via WCT
  4. Activities are reparented into their respective TaskFragments
  5. 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_RESIZE for divider drag animations
  • Detects embedded windows via FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY
  • Skips animation for FLAG_FILLS_TASK windows (non-split, full-task activities)
  • Coordinates with the Jetpack organizer’s RemoteAnimationDefinition for 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 drag
  • frameworks/base/core/java/android/window/TaskFragmentOrganizer.java — Client-side API for creating and managing embedded TaskFragments via WindowContainerTransaction
  • frameworks/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 displays
  • GROUP_TYPE_SECONDARY - Projected displays (e.g., MediaProjection)
  • Reasons: REASON_NON_DESKTOP / REASON_EXTENDED / REASON_FALLBACK map to PRIMARY; REASON_PROJECTED maps 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 operations
  • frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java — Per-display window hierarchy including independent focus, rotation, IME, and DisplayArea policy
  • frameworks/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_ONLY and AUTO_MIRROR are mutually exclusive; OWN_CONTENT_ONLY takes precedence
  • SHOULD_SHOW_SYSTEM_DECORATIONS requires TRUSTED to function
  • PUBLIC without OWN_CONTENT_ONLY implies AUTO_MIRROR behavior

33.4 Lifecycle

Virtual displays are bound to the creating process via IBinder.DeathRecipient:

  1. Creation: VirtualDisplayAdapter links the app token to the VirtualDisplayDevice via appToken.linkToDeath(device, 0)
  2. Surface changes: setVirtualDisplaySurfaceLocked() allows hot-swapping the render target
  3. Resize: resizeVirtualDisplayLocked() updates width, height, and density
  4. Release: releaseVirtualDisplayLocked() calls device.destroyLocked(true) and unlinkToDeath
  5. 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 token
  • frameworks/base/core/java/android/hardware/display/VirtualDisplayConfig.java — Builder-pattern configuration
  • frameworks/base/services/core/java/com/android/server/display/VirtualDisplayAdapter.java — Server-side virtual display management
  • frameworks/base/core/java/android/companion/virtual/VirtualDeviceManager.java — API for creating virtual devices with virtual displays
  • frameworks/base/services/core/java/com/android/server/display/OverlayDisplayAdapter.java — Developer overlay display configuration
  • frameworks/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: onTopologyChangedCallback notifies 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 with mDisplayId, mDensity, mBoundsInGlobalDp (absolute bounds in density-independent pixels), and mAdjacentEdges
  • mPrimaryDisplayId — 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 displayId and persistent uniqueId for 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 a Layout
  • 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 display
  • VIEWPORT_EXTERNAL - HDMI/USB-C display
  • VIEWPORT_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 mapping
  • frameworks/base/core/java/android/hardware/display/DisplayTopology.java — Tree structure with normalization, absolute bounds calculation, and graph generation
  • frameworks/base/core/java/android/hardware/display/DisplayTopologyGraph.java — Spatial adjacency graph for native input routing
  • frameworks/base/services/core/java/com/android/server/display/DisplayTopologyXmlStore.java — XML persistence layer with LRU ordering
  • frameworks/base/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java — Foldable device state to display layout mapping
  • frameworks/base/services/core/java/com/android/server/display/layout/Layout.java — Display arrangement configuration
  • frameworks/base/core/java/android/view/DisplayInfo.java — Display properties including density, dimensions, rotation, and unique ID
  • frameworks/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():

  1. Transition collection: transitionController.collectReparentChange() records the reparent for shell transition animation
  2. Hierarchy update: Remove from old parent, add to new parent
  3. Display change propagation: If display changed, onDisplayChanged() cascades recursively through Task → TaskFragment → ActivityRecord → WindowState
  4. Surface reparenting: reparentSurfaceControl() moves the SurfaceControl to the new parent’s surface tree via SurfaceControl.Transaction.reparent()
  5. Z-order reassignment: mParent.assignChildLayers() recalculates layer ordering on new display
  6. Layout invalidation: Both old and new DisplayContent marked setLayoutNeeded()

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):

  • MoveToDisplayItem transaction sent to client
  • Activity receives onMovedToDisplay() first, then onConfigurationChanged()
  • No destruction — Activity adapts its layout in-place

Path C — Activity relaunched (unhandled config changes):

  • Full lifecycle: onPause()onStop()onDestroy()onCreate()onStart()onResume()
  • ActivityRelaunchItem preserves pending results and intents
  • preserveWindow flag 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:

  1. Internal Display reference updated to new display ID
  2. IME focus controller notified (IME may differ per display)
  3. View tree receives dispatchMovedToDisplay() cascade
  4. requestLayout() triggers full re-measure with new display metrics
  5. 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 when ENABLE_SHRINK_WINDOW_BOUNDS_AFTER_DRAG is 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 with LINEAR interpolator
  • WindowDragTransitionHandler — immediate surface position for drag drops (no animation delay)
  • TransitionInfo.Change tracks startDisplayId and endDisplayId to 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 reparenting
  • frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java — moveRootTaskToDisplay() and moveRootTaskToTaskDisplayArea() orchestrate server-side cross-display moves
  • frameworks/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:

  1. Checks whether hardware rendering is enabled (mAttachInfo.mHardwareRenderer != null)
  2. Calls draw(fullRedrawNeeded, surfaceSyncGroup, mSyncBuffer) (line 5578)
  3. Inside draw(): if hardware renderer available, calls mAttachInfo.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 sync
  • setFrameCommitCallback(Executor, Runnable) (line 487): fires when frame is submitted to GPU
  • syncAndDraw() (line 523): invokes native nSyncAndDrawFrame() — 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 callbacks
  • frameCallback() (line 73): called on VSYNC; triggers dispatchFrameCallbacks() (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 into
  • queueBuffer() — 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:

  1. Walks its layer list; acquires new buffers from BufferQueue where available
  2. Calls HWC::validateDisplay() — the HWC decides which layers go to hardware overlay planes and which need GPU compositing
  3. If GPU compositing needed: RenderEngine draws CLIENT layers into a framebuffer
  4. Calls HWC::presentDisplay() — commits to display hardware with a present fence
  5. Returns release fences to producers via BufferQueue::releaseBuffer()

Reference files:

  • frameworks/base/core/java/android/view/ViewRootImpl.javaperformDraw() (line 5548)
  • frameworks/base/core/java/android/view/ThreadedRenderer.javadraw() (line 828)
  • frameworks/base/graphics/java/android/graphics/HardwareRenderer.javaFrameRenderRequest (lines 430–546)
  • frameworks/base/libs/hwui/renderthread/RenderThread.cppthreadLoop() (line 393)
  • frameworks/native/libs/gui/Surface.cpp — BufferQueue producer side (lines 121–150)
  • frameworks/base/core/java/android/view/SurfaceControl.javasetBuffer() (lines 4658–4761)
  • frameworks/base/core/java/android/view/Surface.java — Client-side Surface API wrapping BufferQueue producer
  • frameworks/native/libs/gui/include/gui/BufferQueue.h — Native BufferQueue producer-consumer interface
  • frameworks/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:

  1. makeCurrent() (line 58): EGL context switch to window surface
  2. getFrame() (line 110): mEglManager.beginFrame() — dequeue buffer from ANativeWindow
  3. draw() (line 116):
    • Creates GrGLFramebufferInfo wrapping FBO0 (line 128)
    • Creates SkSurface from the framebuffer (line 157)
    • Renders the display list tree into SkSurface
    • Flushes Skia’s deferred command buffer (line 182)
  4. swapBuffers() (line 194): eglSwapBuffers() with damage regions for partial updates
  5. flush() (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:

  1. makeCurrent() (line 58): Creates VkSurface from ANativeWindow if not yet created
  2. getFrame() (line 69): vulkanManager().dequeueNextBuffer() — Vulkan swapchain buffer
  3. finishFrame() (line 118): Vulkan-native submission (no eglSwapBuffers)
  4. swapBuffers() (line 130): Returns present fence for hardware synchronization
  5. 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:

  1. Main thread: records display list changes (via View.draw()SkCanvas)
  2. RenderThread: executes display list → GPU submission, runs on its own VSYNC offset
  3. GPU: executes commands asynchronously
  4. 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): Each View owns a RenderNode containing a staging and active DisplayList. Properties (transforms, alpha, clip, elevation) are tracked via RenderProperties with a DirtyPropertyMask to detect changes.
  • RecordingCanvas (RecordingCanvas.h): Extends Skia’s SkNoDrawCanvas to record (not execute) draw calls into DisplayListData.
  • SkiaRecordingCanvas (SkiaRecordingCanvas.h): Wraps RecordingCanvas with SkiaDisplayList, adding support for layer rendering, animated image updates, and VectorDrawable.
  • 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): Manages TextureView content — holds an ASurfaceTexture consumer and maps buffer slots to SkImage objects via ImageSlot.

37.9 GPU Memory and Cache Management

CacheManager (CacheManager.h): Manages Skia’s GPU resource budget per pipeline:

  • configureContext() — sets GrContextOptions (resource limits, shader cache)
  • trimMemory() — responds to ComponentCallbacks2.onTrimMemory() levels
  • getMemoryUsage() — reports CPU and GPU resource consumption
  • onFrameCompleted() — 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 JNI
  • frameworks/base/libs/hwui/renderthread/RenderProxy.h — UI thread → RenderThread bridge
  • frameworks/base/libs/hwui/renderthread/DrawFrameTask.h — Frame sync/draw orchestration
  • frameworks/base/libs/hwui/renderthread/RenderThread.h — Singleton render thread with GPU context
  • frameworks/base/libs/hwui/renderthread/RenderThread.cpp — RenderThread event loop and frame callbacks
  • frameworks/base/libs/hwui/renderthread/CanvasContext.h — Per-window rendering context, pipeline owner
  • frameworks/base/libs/hwui/renderthread/CanvasContext.cpp — Pipeline selection logic and draw orchestration
  • frameworks/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 recording
  • frameworks/base/libs/hwui/DisplayList.h — Recorded draw operations container
  • frameworks/base/libs/hwui/DamageAccumulator.h — Dirty region propagation
  • frameworks/base/libs/hwui/DeferredLayerUpdater.h — TextureView layer management
  • frameworks/base/libs/hwui/renderthread/CacheManager.h — GPU memory management
  • frameworks/base/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp — Display list replay and caching
  • frameworks/base/libs/hwui/pipeline/skia/ShaderCache.cpp — Persistent shader cache
  • frameworks/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:

  1. View.setElevation(dp)RenderNode.setElevation(px)
  2. RenderProperties.mElevation set (in pixels)
  3. During RenderNode recording, Skia reads getZ() as the z-plane parameter
  4. SkCanvas::drawShadow() is called with the shape, z, light geometry, and color
  5. SkShadowTessellator generates triangle strips
  6. 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 algorithms
  • external/skia/src/core/SkDrawShadowInfo.h — shadow metrics (blur radius formulas)
  • frameworks/base/libs/hwui/LightingInfo.h — light position configuration
  • frameworks/base/libs/hwui/Lighting.hLightGeometry struct
  • frameworks/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 setHdrSdrRatio to 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.javaHdrCapabilities (lines 2904–2948)
  • frameworks/base/core/java/android/view/SurfaceControl.javasetDesiredHdrHeadroom(), setDataSpace(), setHdrMetadata()
  • frameworks/base/core/java/android/hardware/DataSpace.java — dataspace constants
  • frameworks/native/libs/gui/include/gui/HdrMetadata.h — HDR metadata structure
  • frameworks/native/libs/tonemap/include/tonemap/tonemap.hToneMapper interface
  • 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 (DRM DRM_PLANE_TYPE_OVERLAY)
  • CLIENT → GPU-rendered framebuffer → DRM DRM_PLANE_TYPE_PRIMARY
  • CURSOR → DRM DRM_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:

  1. Producer allocates buffer via Gralloc → receives native_handle_t with DMA-BUF fds
  2. Handle is shared cross-process via Binder (fd duplication)
  3. Consumer imports the handle via Gralloc Mapper → accesses same physical memory
  4. GPU uses EGLImageKHR or Vulkan VkDeviceMemory from 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 setup
  • hardware/interfaces/graphics/allocator/4.0/IAllocator.hal — Gralloc HAL interface
  • hardware/libhardware/include_all/hardware/hwcomposer2.h — HWC2 layer types
  • frameworks/native/services/surfaceflinger/DisplayHardware/HWC2.cpp — present flow
  • frameworks/native/libs/ui/GraphicBuffer.cpp — AHardwareBuffer ↔ GraphicBuffer
  • frameworks/native/libs/ui/Fence.cpp — sync_file fence wrapper
  • frameworks/native/vulkan/libvulkan/swapchain.cpp — Vulkan surface
  • frameworks/native/libs/gui/include/gui/GLConsumer.h — GL texture consumer (SurfaceTexture)
  • frameworks/native/libs/nativewindow/AHardwareBuffer.cpp — AHardwareBuffer NDK implementation
  • frameworks/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 async validateDisplay()/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). Added DisplayConfiguration with VRR support, DisplayLuts for color lookup tables, picture profiles for per-layer processing, and startHdcpNegotiation() for lazy HDCP activation

41.3 Framework Integration

HWComposer (frameworks/native/services/surfaceflinger/DisplayHardware/HWComposer.h) orchestrates the HAL:

  • getDeviceCompositionChanges() — validate phase, returns DeviceRequestedChanges with changed composition types
  • presentAndGetReleaseFences() — present phase, returns present fence and per-layer release fences
  • setClientTarget() — 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/disconnect
  • onVsync() / onVsyncIdle() — VSYNC timing
  • onRefresh() — 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 DrmClientDrmDisplay → 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 interface
  • hardware/interfaces/graphics/composer/aidl/android/hardware/graphics/composer3/Composition.aidl — Composition type enum
  • frameworks/native/services/surfaceflinger/DisplayHardware/HWComposer.h — Framework-side HWC orchestration
  • frameworks/native/services/surfaceflinger/DisplayHardware/HWC2.h — HWC2 Display/Layer abstractions
  • frameworks/native/services/surfaceflinger/DisplayHardware/AidlComposerHal.h — AIDL HAL wrapper
  • 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 — 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 around GraphicBuffer
  • GraphicBuffer — extends ANativeWindowBuffer, carries buffer_handle_t (native_handle with fds)
  • GraphicBufferAllocator — singleton that selects the correct allocator version at runtime
  • GraphicBufferMapper — singleton providing lock()/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, totalSizeInBytes
  • sampleIncrementInBits — bits per pixel sample
  • horizontalSubsampling / 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 Surface objects via SurfaceComposerClient and 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:

  1. Buffer allocation: mflinger creates a Surface via SurfaceComposerClient. Android’s BufferQueue allocates the backing buffer through gralloc (GraphicBufferAllocator), producing a buffer_handle_t containing a DMA-BUF or ION file descriptor.

  2. Cross-process fd passing: When mclient requests a lock, mflinger calls a custom Surface::lockWithHandle() (a MaruOS AOSP patch) to obtain the buffer_handle_t. The gralloc buffer’s fd (handle->data[0]) is sent to mclient over a Unix domain socket using SCM_RIGHTS ancillary data.

  3. Zero-copy shared memory: mclient mmap()s the received fd with PROT_READ | PROT_WRITE | MAP_SHARED. This maps the same physical memory (DMA-BUF) that SurfaceFlinger will read from — no pixel copies between processes.

  4. Lock/unlock discipline: The lockWithHandle() / unlockAndPost() cycle dequeues and requeues buffers in the BufferQueue, 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_BUFFER deliberately 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 the buffer_handle_t. MaruOS patches Surface to add lockWithHandle() 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 interface
  • hardware/interfaces/graphics/mapper/stable-c/include/android/hardware/graphics/mapper/IMapper.h — Mapper stable-C API
  • hardware/interfaces/graphics/common/aidl/android/hardware/graphics/common/BufferUsage.aidl — Buffer usage flags
  • hardware/interfaces/graphics/common/aidl/android/hardware/graphics/common/PixelFormat.aidl — Pixel format definitions
  • hardware/interfaces/graphics/common/aidl/android/hardware/graphics/common/StandardMetadataType.aidl — Buffer metadata types
  • frameworks/native/libs/ui/include/ui/GraphicBuffer.h — Framework GraphicBuffer class
  • frameworks/native/libs/ui/GraphicBufferAllocator.cpp — Allocator version selection logic
  • frameworks/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 API
  • device/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_PROTECTED buffers)

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 a std::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::Yes enforced)

Reference files:

  • frameworks/native/libs/renderengine/include/renderengine/RenderEngine.h — Base abstraction (RenderEngineCreationArgs, enums)
  • frameworks/native/libs/renderengine/RenderEngine.cpp — Factory method, backend selection
  • frameworks/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 + Vulkan
  • frameworks/native/libs/renderengine/skia/GraphiteVkRenderEngine.h — Graphite + Vulkan (current default)
  • frameworks/native/libs/renderengine/threaded/RenderEngineThreaded.h — Async composition wrapper
  • frameworks/native/libs/renderengine/skia/compat/SkiaGpuContext.h — GPU context abstraction
  • frameworks/native/libs/renderengine/skia/VulkanInterface.h — Vulkan instance/device singleton
  • frameworks/native/libs/renderengine/include/renderengine/LayerSettings.h — Per-layer composition parameters
  • frameworks/native/libs/renderengine/include/renderengine/DisplaySettings.h — Display composition parameters
  • frameworks/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:

  • KeyboardInputMapperNotifyKeyArgs (scancode → keycode via key layout files)
  • TouchInputMapperNotifyMotionArgs (multi-touch slot protocol → pointer coordinates with pressure, size, orientation)
  • CursorInputMapperNotifyMotionArgs (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 findingfindTouchedWindowTargets() (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 inputConfig flags: NOT_TOUCHABLE, NOT_FOCUSABLE, DROP_INPUT, DROP_INPUT_IF_OBSCURED
  • SPY windows (IS_SPY flag) receive a copy of touch events without consuming them
  • WATCH_OUTSIDE_TOUCH windows receive ACTION_OUTSIDE for touches outside their bounds
  • Touch occlusion security: untrusted overlays (non-system UIDs) are blocked from obscuring touches based on touchOcclusionMode and cumulative opacity

Key target findingfindFocusedWindowTargetLocked() (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 window
  • waitQueue: Events sent but not yet acknowledged (finished) by the window
  • responsive flag: 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:

  1. A timeout entry is added to AnrTracker based on the window’s ANR timeout (typically 5 seconds)
  2. When the window finishes (acknowledges) the event, the timeout entry is removed
  3. If the timeout fires before acknowledgment, InputDispatcher calls onAnrLocked() which notifies InputManagerServiceActivityManagerService to 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:

  1. 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 transforms
    • getInputTransform(): Computes the coordinate transform from display space to the window’s buffer space, accounting for buffer transforms, layer transforms, and parent chain transforms
  2. SurfaceFlinger.updateInputFlinger() (frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp:4620): Called during the commit phase, triggers buildWindowInfos() which iterates all layer snapshots and constructs a WindowInfosUpdate

  3. 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 geometry
    • displayInfos: Vector of display transforms and dimensions
    • vsyncId: 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 a WindowInfo with a SurfaceControl
  • setFocusedWindow(): 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_TOUCHABLE for non-interactive windows, NOT_FOCUSABLE for FLAG_NOT_FOCUSABLE windows, IS_SPY for monitoring channels, INTERCEPTS_STYLUS for 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 DisplayContent finds 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:

  1. Creation: WMS.addWindow() creates an InputChannel pair — server side registered with InputDispatcher, client side returned to the app
  2. Registration: Server channel associated with the window’s InputWindowHandle
  3. Event delivery: InputDispatcher writes InputMessage via InputPublisher; InputConsumer on the app side reads and delivers through InputEventReceiver
  4. Back-pressure: If the window’s waitQueue grows too large (events sent but unacknowledged), the dispatcher stops sending to that window — this is the early ANR signal
  5. 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.onTouchListener takes priority over View.onTouchEvent()

Key dispatch (DecorView.dispatchKeyEvent(), frameworks/base/core/java/com/android/internal/policy/DecorView.java:356):

  • DecorView routes to Activity.dispatchKeyEvent() via the Window.Callback interface
  • Activity gives PhoneWindow.superDispatchKeyEvent() first chance
  • PhoneWindow delegates back to DecorView.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_USER flag 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 KeyGestureController for 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.xml resource
  • Meta+B → launch browser, Meta+E → launch email, Meta+C → launch contacts, etc.
  • Constructs Intent objects 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:

  1. When Meta+/ is pressed, PhoneWindowManager triggers the shortcut helper
  2. KeyboardShortcuts queries the current Activity for its shortcuts via Activity.onProvideKeyboardShortcuts()
  3. Also collects system shortcuts from PhoneWindowManager
  4. 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 key
  • META_CTRL_ON (0x1000) — Ctrl key
  • META_ALT_ON (0x02) — Alt key
  • META_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:

  1. InputFilter (accessibility): InputManagerService can install an InputFilter that sees all events before dispatch, used for accessibility services

  2. Input consumers (WM level): InputMonitor.mInputConsumers insert invisible input-receiving surfaces at specific z-positions. PiP uses INPUT_CONSUMER_PIP to intercept taps on the PiP window

  3. SPY windows: Windows with inputConfig::IS_SPY receive copies of all touch events within their bounds without consuming them. Used by system gesture monitors (back gesture, edge swipe) and the status bar

  4. TaskPositioner: Used in freeform/desktop mode to intercept touches on window title bars for drag-to-move and drag-to-resize operations

  5. System gesture exclusion: Apps can declare setSystemGestureExclusionRects() to prevent the system back gesture from firing in specific areas, coordinated through DisplayPolicy

  6. Launcher quickstep: TouchInteractionService (packages/apps/Launcher3/quickstep/src/com/android/quickstep/TouchInteractionService.java) uses InputMonitorCompat to create a monitoring channel that observes all touch events on the display for gesture navigation

  7. 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 orchestration
  • frameworks/native/services/inputflinger/reader/include/InputReader.h — Event reading from EventHub
  • frameworks/native/services/inputflinger/reader/include/EventHub.h — Raw device access via epoll/inotify
  • frameworks/native/services/inputflinger/reader/mapper/TouchInputMapper.cpp — Touch processing (~2600 lines)
  • frameworks/native/services/inputflinger/reader/mapper/KeyboardInputMapper.h — Key event generation
  • frameworks/native/services/inputflinger/InputProcessor.h — ML-based motion classification
  • frameworks/native/services/inputflinger/UnwantedInteractionBlocker.h — Palm rejection
  • frameworks/native/services/inputflinger/PointerChoreographer.h — Pointer icon management
  • frameworks/native/services/inputflinger/InputFilter.h — Accessibility input filtering
  • frameworks/native/services/inputflinger/dispatcher/InputDispatcher.h — Core routing engine (line 408: findTouchedWindowTargets, line 817: findFocusedWindowTargetLocked)
  • frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp — Dispatcher implementation
  • frameworks/native/services/inputflinger/dispatcher/FocusResolver.h — Per-display focus state
  • frameworks/native/services/inputflinger/dispatcher/Connection.h — Per-channel dispatch state
  • frameworks/native/services/inputflinger/dispatcher/AnrTracker.h — ANR timeout tracking
  • frameworks/native/services/inputflinger/dispatcher/TouchedWindow.h — Per-device touch state
  • frameworks/native/services/inputflinger/dispatcher/InputTarget.h — DispatchMode enum
  • frameworks/native/libs/gui/include/gui/WindowInfo.h — WindowInfo data structure
  • frameworks/native/libs/gui/include/gui/WindowInfosListener.h — onWindowInfosChanged callback
  • frameworks/native/libs/gui/include/gui/WindowInfosUpdate.h — Batched window/display update
  • frameworks/native/services/surfaceflinger/WindowInfosListenerInvoker.h — SF dispatches to listeners
  • frameworks/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 service
  • frameworks/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 detection
  • frameworks/base/services/core/java/com/android/server/policy/ModifierShortcutManager.java — Meta+letter shortcuts
  • frameworks/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 channel
  • frameworks/base/core/java/android/view/InputEventReceiver.java — App-side event receiver
  • frameworks/base/core/java/android/view/KeyEvent.java — Key codes and meta state constants
  • frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java — Shortcut helper UI
  • packages/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 with TvSplitScreenController, replaces StartingWindow algorithm)
  • CarWMShellModule + AutoShellModule — Auto (replaces FullscreenTaskListener, adds ScalableUI, Panel system)

Layer 3 — WMComponent: Each variant declares which module it includes:

  • WMComponent includes WMShellModule.class (default)
  • TvWMComponent includes TvWMShellModule.class
  • CarWMComponent includes CarWMShellModule.class

45.2 The @DynamicOverride Mechanism

@DynamicOverride (WMShell/src/com/android/wm/shell/dagger/DynamicOverride.java) is the key to variant customization. It enables:

  1. Base module declares an optional binding with @BindsOptionalOf @DynamicOverride
  2. Base module provides the feature with a check: if the @DynamicOverride Optional is present, use the override; otherwise use the default or Optional.empty()
  3. Variant module can provide a @DynamicOverride implementation 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, includes TvWMShellModule
  • Minimal set — no Bubbles, no Freeform, no Desktop Mode, no One-Handed

Key TV overrides (TvWMShellModule.java):

  1. TvSplitScreenController: TV-specific split-screen with D-pad navigation instead of touch. Uses the same StageCoordinator core but different interaction model

  2. TvStartingWindowTypeAlgorithm: Different splash screen logic for TV apps (larger screens, different transition expectations)

  3. TV PiP (TvPipModule): Entirely different PiP UX from phone:
    • TvPipController — D-pad controlled, no drag gestures. State machine: STATE_NO_PIPSTATE_PIPSTATE_PIP_MENU
    • TvPipBoundsAlgorithm — Gravity-based corner positioning (not free-form). Supports expanded PiP mode with fixed dimensions
    • TvPipMenuController — 10-second auto-close timeout, D-pad navigation for move/expand/close actions
    • TvPipTransition — Zoom animation with ZOOM_ANIMATION_SCALE_FACTOR = 0.97f
    • TvPipBoundsState — Persists last gravity position to SharedPreferences, RTL support
  4. No-touch navigation: TvNotificationShadeWindowController is a no-op (no pull-down panel). All system UI is D-pad navigated via KEYCODE_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, includes CarWMShellModule
  • Exports automotive-specific components: AutoTaskStackController, AutoLayoutManager, AutoDecorManager, ScalableUIWMInitializer

Key Auto overrides (CarWMShellModule.java):

  1. CarFullscreenTaskMonitorListener: Replaces default FullscreenTaskListener — monitors tasks with automotive-specific behavior (driver distraction awareness, multi-display passenger zones)

  2. AutoDisplayCompatWindowDecorViewModel: Automotive window decorations — compatibility UI for apps not designed for automotive screens

  3. ScalableUI Panel System: Automotive’s unique panel-based layout architecture:
    • PanelConfigReader — reads XML panel definitions
    • TaskPanel, DecorPanel, BasePanel — panel types
    • PanelAutoTaskStackTransitionHandlerDelegate — custom transition handling per panel
    • Gated by Flag.ScalableUIEnabled
  4. DisplaySystemBarsController: Automotive-specific system bars with CarWMUserHelper for multi-user support

  5. No PiP: @BindsOptionalOf abstract Pip optionalPip() — PiP is not bound (Optional.empty)

Auto Shell Library (packages/services/Car/libs/car-wm-shell-lib/):

  • AutoShellModule — Binds AutoTaskStackController, AutoHomeTaskMonitor
  • AutoLayoutManager — Manages layout of tasks, insets, and safe regions per display
  • AutoCaptionController — Caption/subtitle bars per display with safe region support
  • AutoTaskRepository — Maps task IDs to surface controls, tracks root task stacks
  • AutoDecorManager — Window decoration management for automotive

Activity Interception (frameworks/opt/car/services/):

  • CarActivityInterceptorUpdatableImpl — Intercepts activity launches to route them to appropriate displays/task containers
  • CarLaunchParamsModifier — Controls display assignment during activity launch, manages driver vs passenger display routing
  • CarActivityManager — 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_WINDOW for 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)
  • DesktopTasksController enables 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:

  1. Custom DisplayAreaPolicy.Provider: Register via R.string.config_deviceSpecificDisplayAreaPolicyProvider — allows completely custom display area hierarchies (like automotive’s CarDisplayAreaPolicyProvider)

  2. Custom WMShellModule: Create a new Dagger module extending the base, overriding specific @DynamicOverride bindings

  3. Custom SystemUI: Ship a replacement SystemUI APK with a custom WMComponent that includes OEM-specific modules

  4. Feature Flags: Use DesktopExperienceFlags and DesktopModeFlags to gate features at runtime without code changes

  5. DisplayAreaOrganizer: Register custom organizers for specific display area features via FEATURE_VENDOR_FIRST through FEATURE_VENDOR_LAST

Reference files:

  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java — Shared base module with @DynamicOverride bindings
  • frameworks/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 interface
  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java — Override mechanism annotation
  • packages/apps/TvSystemUI/src/com/android/systemui/tv/dagger/TvWMComponent.kt — TV WMComponent
  • packages/apps/Car/SystemUI/src/com/android/systemui/wmshell/CarWMComponent.java — Automotive WMComponent
  • packages/apps/Car/SystemUI/src/com/android/systemui/wmshell/CarWMShellModule.java — Automotive Shell module
  • packages/services/Car/libs/car-wm-shell-lib/src/com/android/wm/shell/automotive/AutoShellModule.java — Auto Shell library module
  • frameworks/opt/car/services/builtInServices/src/com/android/server/wm/CarDisplayAreaPolicyProvider.java — Automotive display area policy
  • frameworks/base/services/core/java/com/android/server/wm/DisplayAreaPolicy.java — Display area policy base and DefaultProvider
  • frameworks/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 TvStageCoordinator
  • packages/services/Car/libs/car-wm-shell-lib/src/com/android/wm/shell/automotive/AutoTaskRepository.java — Automotive task tracking
  • frameworks/opt/car/services/updatableServices/src/com/android/server/wm/CarLaunchParamsModifierUpdatableImpl.java — Auto display routing
  • frameworks/opt/car/services/updatableServices/src/com/android/internal/car/updatable/ExtraDisplayMonitor.java — MUMD passenger display monitor
  • packages/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 displays
  • onDisplayRemoved(displayId) — triggers cleanup of per-display instances
  • onDisplayChanged(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:

  1. Lazy-creates instances on first forDisplay(displayId) call
  2. Watches displayRepository.displayRemovalEvent for cleanup
  3. 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-display NavigationBar instance
  • Fallback: Delegates to TaskbarDelegate when no NavigationBar exists for a display

46.4 Status Bar Multi-Display

Status bar initialization follows a two-layer per-display pattern:

  1. StatusBarWindowController (per display): Manages the status bar window (WindowManager.LayoutParams) for a specific display. Tracks mDisplayId, handles WindowManager.InvalidDisplayException if the display is removed during initialization.

  2. StatusBarInitializer (per display): Creates the status bar view hierarchy for a specific display. Retrieves per-display dependencies from ReferenceSysUIDisplaySubcomponent:

    • StatusBarWindowController — window management
    • StatusBarModePerDisplayRepository — translucency/opaque mode
    • StatusBarConfigurationController — per-display config changes
    • DarkIconDispatcher — 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: @DisplayAware for display-aware dependencies, @DisplayId for the display ID binding
  • Lifecycle: start() called when display added, stop() called on removal (cancels displayCoroutineScope)
  • 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 interface
  • frameworks/base/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt — DisplayManager.DisplayListener wrapper
  • frameworks/base/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt — Per-display lazy instance pattern
  • frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt — Multi/Single display window controller store
  • frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt — Multi/Single display initializer store
  • frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt — Per-display status bar view creation
  • frameworks/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 subcomponent
  • frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConnectedDisplays.kt — Feature flag for multi-display status bar
  • frameworks/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, implements BgDataModel.Callbacks and DragController.DragListener
  • Creates a display-specific DeviceProfile via InvariantDeviceProfile.createDeviceProfileForSecondaryDisplay(context) — adapts grid, density, bounds, and orientation for the secondary display
  • Has its own SecondaryDragLayer, ActivityAllAppsContainerView, and SecondaryDragController — all scoped to the secondary display
  • Uses SecondaryDisplayDelegate (with SecondaryDisplayQuickstepDelegateImpl for 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):

  • mTasks is a single ArrayList<Task> ordered by recency — all displays combined
  • getRecentTasks(maxNum, flags, userId) has no displayId parameter — returns tasks from all displays
  • Each Task carries its display via getDisplayId()

Display-specific filtering (RunningTasks.java):

  • getTasks() accepts a WindowContainer root — either RootWindowContainer (all displays) or a specific DisplayContent (one display)
  • RootWindowContainer.getRunningTasks(maxNum, list, flags, callingUid, profileIds, displayId) extracts the specific DisplayContent when displayId != INVALID_DISPLAY

Shell-side (RecentsTransitionHandler.java):

  • Maintains multiple RecentsController instances — one per active recents animation per display
  • Each controller tracks mDisplayId and filters tasks via mShellTaskOrganizer.getRunningTasks(displayId)
  • findControllerForDisplay(displayId) searches the active controllers list

Launcher3-side (RecentsView.java, RecentsFilterState.java):

  • When enableOverviewOnConnectedDisplays() is true, each display gets its own RecentsWindowManager instance
  • RecentsFilterState.getFilter(packageName, displayId) creates a predicate that includes groupTask.matchesDisplayId(displayId) — only showing tasks from the current display
  • GroupTask carries displayId from Task.TaskKey.displayId
  • RecentsView.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 CrossDisplayMoveTransitionInfo and CrossDisplayMoveAnimator

Reference files:

  • packages/apps/Launcher3/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java — Secondary display home activity
  • packages/apps/Launcher3/src/com/android/launcher3/secondarydisplay/SecondaryDisplayDelegate.java — Display-specific delegate base
  • packages/apps/Launcher3/quickstep/src/com/android/launcher3/secondarydisplay/SecondaryDisplayQuickstepDelegateImpl.java — Quickstep delegate for secondary displays
  • packages/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 filtering
  • packages/apps/Launcher3/quickstep/src/com/android/quickstep/views/RecentsView.java — Display-aware recents view
  • packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/GroupTask.kt — Per-task displayId tracking
  • packages/apps/Launcher3/quickstep/src/com/android/quickstep/OverviewComponentObserver.java — Per-display container routing
  • packages/apps/Launcher3/quickstep/src/com/android/quickstep/window/RecentsWindowManager.kt — Per-display recents window
  • packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/CrossDisplayMoveTransition.java — Cross-display task animation
  • packages/apps/Launcher3/src/com/android/launcher3/InvariantDeviceProfile.java — Secondary display DeviceProfile creation
  • packages/apps/Launcher3/AndroidManifest-common.xml — SECONDARY_HOME intent filter registration
  • frameworks/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 filtering
  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java — Multi-display recents animation controllers
  • frameworks/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_DEVICES for 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 VirtualDisplay via DisplayManager.createVirtualDisplay()
  • Uses ImageReader for 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, InsetsPolicy stubbing
  • Automatic registration in RootWindowContainer
  • Unique ID generation for multi-display test isolation

WindowTestsBase (base class):

  • newDisplay(width, height) — creates test display
  • createWindow(parent, type) — creates window on specific display
  • createTask(displayContent, windowingMode) — creates task on display
  • createActivityRecord(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 tests
  • cts/tests/framework/base/windowmanager/src/android/server/wm/multidisplay/MultiDisplaySecurityTests.java — Security boundary tests
  • cts/tests/framework/base/windowmanager/src/android/server/wm/multidisplay/MultiDisplayPolicyTests.java — Display policy tests
  • cts/tests/framework/base/windowmanager/src/android/server/wm/multidisplay/MultiDisplaySystemDecorationTests.java — System decoration tests
  • cts/tests/framework/base/windowmanager/src/android/server/wm/window/MultiWindowTests.java — Multi-window CTS tests
  • cts/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java — VirtualDisplaySession infrastructure
  • cts/tests/framework/base/windowmanager/util/src/android/server/wm/MultiDisplayTestBase.java — Multi-display test base class
  • cts/tests/framework/base/windowmanager/util/src/android/server/wm/VirtualDisplayHelper.java — Real VirtualDisplay helper
  • cts/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/embedding/MultiDisplayTestHelper.java — Activity embedding multi-display helper
  • frameworks/base/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java — Mock display builder for unit tests
  • frameworks/base/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java — WM unit test base class
  • frameworks/base/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java — Per-display task filtering verification
  • frameworks/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)
  • DecorView lines 1255-1272: Handles APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND flag 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_CONSUMPTION forces 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 mDecorationContainerSurface as a container layer child of the task surface (lines 458-476)
  • View hosting: Uses WindowDecorViewHost to render caption views via SurfaceControlViewHost in a WindowlessWindowManager — no full WMS overhead
  • Inset registration: Registers caption bar as WindowInsets.Type.captionBar() inset source via WindowDecorationInsets (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:
    1. WindowInsets.Type.captionBar() — pushes app content below the caption
    2. WindowInsets.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, when Flags.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 = 8
  • CTRL_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 bounds
  • mResizeHandleEdgeInset — handle size inside the window bounds
  • mLargeTaskCorners — 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 into
  • calculateLimitedTouchableRegion() — restricts Shell’s touchable region to non-customized buttons only
  • When transparent, the caption surface uses INPUT_FEATURE_SPY to 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 preWarmSize view hosts and puts them in the pool. Each is warmUp()-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_POOL flag is enabled, maintains separate pools per display. Pools are created on onDisplayAdded() and cleaned up on onDisplayRemoved()
  • Desktop mode aware (line 82): Pre-warming only occurs on displays that support desktop mode (isDesktopModeSupportedOnDisplay())
  • Pool sizing: Configurable maxPoolSize and preWarmSize via constructor injection
  • Reusable hosts (ReusableWindowDecorViewHost): Supports warmUp() for pre-initialization and reset() for state cleanup between uses
  • Async pre-warming: Uses mainScope.launch coroutine 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 header
  • frameworks/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 interface
  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/SurfaceControlViewHostAdapter.kt — Low-level SCVH wrapper
  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/CaptionRegionHelper.kt — Customizable region calculation
  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/caption/CaptionController.kt — Abstract caption controller
  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/caption/AppHeaderController.kt — Desktop mode app header
  • frameworks/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 types
  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java — Resize region computation
  • frameworks/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 ShellTaskOrganizer
  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/BoxShadowHelper.java — Shadow settings helper
  • frameworks/base/libs/WindowManager/Shell/res/layout/caption_window_decor.xml — Freeform caption layout
  • frameworks/base/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml — Desktop mode header layout
  • frameworks/base/core/java/com/android/internal/policy/DecorView.java — Legacy caption inset consumption (lines 1255-1287)
  • frameworks/base/core/java/android/view/WindowInsets.javaCAPTION_BAR type (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.javastartDragAndDrop() (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.javaperformDrag() 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):

  1. Get shadow metrics from DragShadowBuilder.onProvideShadowMetrics()
  2. Validate shadow dimensions (non-zero, within limits)
  3. Create SurfaceControl for drag shadow with callSiteDebugInfo = "View.startDragAndDrop"
  4. Lock canvas, call DragShadowBuilder.onDrawShadow(canvas) to render
  5. Call Session.performDrag() with surface, touch coordinates, and ClipData
  6. 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 timeout
  • A11Y_DRAG_TIMEOUT_DEFAULT_MS = 60,000 ms — accessibility drag timeout

Handler messages:

  • MSG_DRAG_END_TIMEOUT (0) — Drop result not received
  • MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT (1) — Input channel cleanup
  • MSG_ANIMATION_END (2) — Return/cancel animation complete
  • MSG_REMOVE_DRAG_SURFACE_TIMEOUT (3) — Surface cleanup
  • MSG_UNHANDLED_DROP_LISTENER_TIMEOUT (4) — Unhandled drop listener timeout

performDrag() (lines 165-313) — The main entry point:

  1. Validate via IDragDropCallback.prePerformDrag() (vendor hook)
  2. Verify no drag already in progress
  3. Verify calling window is valid
  4. Get DisplayContent from calling window
  5. Create DragState with unique token
  6. Register input channel via callback (or skip for accessibility)
  7. Wait for touch focus transfer (up to 5 seconds)
  8. Show drag surface with alpha, set overlay position
  9. Return drag token

reportDropResult() (lines 319-381) — Drop completion:

  1. Validate drop window holds correct token
  2. Clear drag end timeout
  3. If not consumed: route to GlobalDragListener for unhandled drop
  4. Determine if cross-window drag occurred
  5. Call endDragLocked() with consumption status
  6. Notify global listener of cross-window drops

Multi-display handling:

  • handleDisplayTopologyChange() (lines 494-504) — cancels active drag when display topology changes
  • ENABLE_CONNECTED_DISPLAYS_DND feature flag (line 117) — gates cross-display drag support
  • Registers DisplayTopology.Listener when 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 DragInputEventReceiver for motion tracking
  • Creates InputWindowHandle with TYPE_DRAG layout type
  • Z-order set to Integer.MAX_VALUE for highest priority
  • DISPLAY_TOPOLOGY_AWARE config 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):

  1. App drag restriction: Application-type drags (MIMETYPE_APPLICATION_*) only to local windows or global-intercepting windows
  2. Global flag check: Non-global drag limited to originating window
  3. Same-app check: DRAG_FLAG_GLOBAL_SAME_APPLICATION limited to same UID
  4. Cross-profile check: Honors DISALLOW_CROSS_PROFILE_COPY_PASTE user restriction
  5. Global drag interception: Windows with PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP can 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 IDragAndDropPermissions binder 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 GlobalDragListener callback for WMS bridge
  • Registers display lifecycle callbacks

Drop target window creation (onDisplayAdded(), lines 236-278):

  • Creates FrameLayout root view per display
  • Window params: TYPE_APPLICATION_OVERLAY with:
    • FLAG_NOT_FOCUSABLE | FLAG_HARDWARE_ACCELERATED
    • PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP — intercepts all global drag events
    • SYSTEM_FLAG_SHOW_FOR_ALL_USERS
  • Window initially INVISIBLE; shown on drag start
  • DragLayout added as child for drop zone visualization
  • DragToBubbleController added 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 identifier
  • context — display-specific context
  • wm — per-display WindowManager
  • rootView — drop target window root
  • dragLayout — DragLayout for drop zone UI
  • hasDrawn — first-draw tracking
  • isHandlingDrag — active drag state
  • activeDragCount — concurrent drag counter

DragLayout (845 lines)

Manages visual drop target zones for split-screen and fullscreen drop scenarios:

Drop zone configuration:

  • Two DropZoneView instances for left/right or top/bottom split
  • SplitDragPolicy provides target calculation based on current task state
  • Flexible split support via enableFlexibleSplit() flag for variable target counts

Key flow:

  1. prepare(session) — initialize with drag session data
  2. updateSession(session) — calculate targets based on running task
  3. show() — make layout visible, recompute drop targets
  4. update(event) — highlight active drop zone as pointer moves
  5. drop(event, dragSurface, hideTaskToken, callback) — animate drop and launch
  6. hide(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 events
  • DragPositioningCallback (89 lines) — Interface for move/resize positioners
  • FluidResizeTaskPositioner (238 lines) — Real-time position/resize updates
  • DragPositioningCallbackUtility (~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 resize
  • CTRL_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):

  1. Detect display change via displayId comparison
  2. Retrieve new DisplayContent and update mCurrentDisplayContent
  3. Scale drag shadow by density ratio between displays
  4. Reparent shadow SurfaceControl to new display’s overlay layer
  5. Update InputWindowHandle.displayId for 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 using DisplayLayout transformations, enabling DPI-agnostic positioning
  • constrainBoundsForDisplay() (lines 90-179) — Applies 96 DP overhang margin (allowing partial window off-screen), scales non-resizable windows to fit while preserving aspect ratio
  • convertGlobalDpToLocalPxForRect() (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; creates SurfaceControl.mirrorSurface() for visual preview; shows VISIBLE on cursor display, TRANSLUCENT on others
  • onDragEnd() (lines 127-138) — Cleans up indicator surfaces per taskId
  • Scale factor calculated as targetDpi / startDpi for consistent visual sizing

MultiDisplayDragMoveIndicatorSurface (144 lines):

  • Creates mirrored surface via SurfaceControl.mirrorSurface() for the dragged task
  • Reparents to RootTaskDisplayAreaOrganizer for 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 hold
  • mMaxAcquiredBufferCount (default 1, set by consumer) — controls how many buffers the consumer can hold
  • mAsyncMode — 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; mPendingRelease queues buffers awaiting return to the BQ
  • Transaction synchronization: syncNextTransaction() allows callers to intercept the next transaction for synchronization; mSyncTransaction and mTransactionReadyCallback coordinate this
  • Frame timeline: mPendingFrameTimelines queue pairs frame numbers with FrameTimelineInfo for Choreographer integration
  • Buffer release channel: BufferReleaseChannel::ProducerEndpoint provides a dedicated channel for SF-to-client buffer release notifications

Reference files:

  • frameworks/native/libs/gui/include/gui/BufferQueue.h — Factory class with createBufferQueue() (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 with BufferState state 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 — Static sp<Fence> meaning “no synchronization needed”
  • SIGNAL_TIME_PENDING (INT64_MAX) — Not yet signaled
  • SIGNAL_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 interface
  • frameworks/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> mOpenSlots of available slot numbers
  • Implements OnEntryRemoved to recycle evicted slot numbers back to mOpenSlots

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.cppprocessDisplayAdded() at lines 4265-4391, processDisplayRemoved() at line 4393
  • frameworks/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 management
  • frameworks/base/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java — Window policy enforcement for virtual displays
  • frameworks/base/services/companion/java/com/android/server/companion/virtual/InputController.java — Virtual input device lifecycle management
  • frameworks/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 point
  • onComposerHalVsyncPeriodTimingChanged() — refresh rate change notification
  • onComposerHalVsyncIdle() — idle power state VSYNC

HWComposer (HWComposer.h, 622 lines):

  • onVsync(HWDisplayId, timestamp) (line 251) — maps hardware display ID to logical PhysicalDisplayId
  • setVsyncEnabled(PhysicalDisplayId, Vsync) (line 253) — controls hardware VSYNC delivery
  • getDisplayVsyncPeriod(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:

  1. 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 sample
    • nextAnticipatedVSyncTimeFrom(timePoint) — predict next VSYNC >= timePoint
    • currentPeriod() — current VSYNC period in nanoseconds
    • setRenderRate(Fps, applyImmediately) — VRR support: different render vs display rate
    • VsyncTimeline inner class manages per-render-rate timeline (e.g., 120 Hz display rendering at 60 Hz)
  2. VSyncReactor (VSyncReactor.h, 114 lines) — Manages VSYNC period transitions during refresh rate changes:
    • addHwVsyncTimestamp(timestamp, hwcVsyncPeriod, periodFlushed) — react to hardware samples
    • addPresentFence(FenceTime) — validate predictions against actual present times
    • onDisplayModeChanged(DisplayModePtr) — handle mode transitions
    • State machine: mMoreSamplesNeededmPeriodConfirmationInProgress → confirmed
  3. 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 work
  • readyDuration — 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:

  1. vsyncCallback(vsyncTime, targetWakeupTime, readyTime) — received from VSyncDispatch
  2. Handler::dispatchFrame(VsyncId, expectedVsyncTime) — triggers SF frame processing
  3. SF executes commit() (line 683 in SurfaceFlinger.h) then composite() (line 685):
    • commit() — process pending transactions, update layer state
    • composite() — 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 (Render cycle), one for SF internal timing (LastComposite cycle)
  • requestNextVsync(connection) — one-shot VSYNC delivery
  • setVsyncRate(rate, connection) — periodic delivery (rate=1 every VSYNC, rate=2 every other)
  • Generates VsyncEventData with frame timeline predictions for jank analysis

Choreographer (Choreographer.java, 1,714 lines):

  • One instance per Looper thread; apps use VSYNC_SOURCE_APP
  • doFrame() (line 1021) executes five callback phases in order:
    1. CALLBACK_INPUT (0) — process input events
    2. CALLBACK_ANIMATION (1) — update animations
    3. CALLBACK_INSETS_ANIMATION (2) — inset transitions
    4. CALLBACK_TRAVERSAL (3) — View measure/layout/draw
    5. CALLBACK_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 callback
  • doTraversal() (line 3123) — removes sync barrier, calls performTraversals()
  • 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 SetFrameRate
  • summarize() — returns vector<LayerRequirement> for all active layers
  • Activity window: 1 second; inactive layers excluded from voting

RefreshRateSelector (RefreshRateSelector.h, 596 lines; .cpp, 1,885 lines):

  • LayerVoteType enum: NoVote, Min, Max, Heuristic, ExplicitDefault, ExplicitExactOrMultiple, ExplicitExact, ExplicitGte, ExplicitCategory
  • getRankedFrameRates() (impl line 578) — scores each available refresh rate against all layer requirements
  • getFrameRateOverrides() (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 downscaling
    • FRAME_RATE_COMPATIBILITY_FIXED_SOURCE (1) — video at exact rate
    • FRAME_RATE_COMPATIBILITY_AT_LEAST (2) — greater than or equal to specified rate
    • FRAME_RATE_COMPATIBILITY_EXACT (100) — exact rate required
  • Mode switch strategy: CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS vs CHANGE_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 started
  • endTime — when render/composition finished
  • presentTime — actual on-screen time
  • desiredPresentTime — 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/disable
  • drm_crtc_send_vblank_event() — delivers page-flip-complete events to userspace
  • Events read via the DRM fd using poll()/epoll() + drmHandleEvent()

Atomic commit flow:

  1. Compositor constructs atomic request (framebuffer IDs, plane properties, CRTC state)
  2. DRM_IOCTL_MODE_ATOMIC with DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK
  3. Kernel validates (atomic_check()), commits (atomic_commit())
  4. Commit completion tracked via struct drm_crtc_commit:
    • flip_done — hardware flipped to new buffer (VBLANK); userspace event sent
    • hw_done — all register writes complete
    • cleanup_done — old state freed

Wayland compositor scheduling (Weston example):

  1. Page-flip-complete event arrives from DRM
  2. Compositor starts repaint timer: delay = frame_period - repaint_window
  3. Timer fires → compositor samples all client surfaces, composes, submits atomic commit
  4. Composed frame hits the next VBLANK

Wayland client frame protocol:

  1. Client calls wl_surface_frame(surface) → receives wl_callback
  2. Client renders, attaches buffer, commits
  3. Compositor fires wl_callback.done(timestamp) when ready for next frame
  4. 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 VRR
  • VRR_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:

  1. Queries rotationForOrientation() to compute the desired rotation.
  2. Compares the result to the current rotation.
  3. If changed, initiates the rotation update through DisplayContent.updateOrientation(), which triggers applyRotationAndFinishFixedRotation().
  4. Returns true if 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:

  1. Fixed-to-user rotation — highest priority, system-level lock set via setFixedToUserRotation().
  2. Forced rotation — set by system overlay or policy override such as camera compatibility.
  3. Lid switch — physical lid state on clamshell devices.
  4. Dock mode — car or desk dock orientations defined by config.
  5. App-requested orientation — the screenOrientation attribute from the top activity.
  6. Sensor proposal — accelerometer or game rotation vector data filtered through WindowOrientationListener.
  7. User preference — the auto-rotate toggle and user-set rotation in Settings.
  8. System defaultROTATION_0 when 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 a SurfaceControl.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 the SurfaceControl, 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:

  1. Sets mIsHalfFolded to true.
  2. Records the preferred half-folded rotation in mHalfFoldedRotation.
  3. Calls adjustRotation() on proposals from the sensor or app to ensure they are compatible with the half-folded state.
  4. 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: AsyncRotationController is 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 DisplayManagerService to all display consumers. DisplayRotationCoordinator (101 lines) synchronizes the default display rotation to secondary displays via getDefaultDisplayCurrentRotation().
  • Section 23 — Insets System: FixedRotationTransformState creates its own InsetsState for 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:

  1. Hardware DetectionFoldableDeviceStateProvider reads hinge angle and lid switch sensors, translating raw readings into candidate device states.
  2. State ManagementDeviceStateManagerService maintains 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.
  3. Display MappingLogicalDisplayMapper performs the physical-to-logical display swap on fold/unfold, enabling or disabling the inner and outer panels with a 500 ms transition timeout.
  4. Window Manager IntegrationDeviceStateController bridges 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 DeviceStateProviderListener from 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 in mProcessRecords.

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:

  1. User enters PIN/password/pattern on the keyguard UI
  2. KeyguardViewMediator validates credentials via LockPatternUtils
  3. On success, handleHide() is called directly (no mKeyguardDonePending phase)
  4. 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 KeyguardViewMediator manages its UI.
  • Secondary displays: May or may not show keyguard depending on DisplayContent flags and policy. Each maintains its own mShowing, mOccluded, and mSleepToken.
  • Occlude independence: An activity with SHOW_WHEN_LOCKED on 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’s View hierarchy 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:

  1. Capture: Triggered when a task transitions to invisible, when recents requests bulk snapshots (snapshotTasks()), or on app crash. The capture calls through SurfaceFlinger’s captureLayersToBuffer() to produce a HardwareBuffer.
  2. Cache: The in-memory LRU cache (mTaskSnapshotCache) holds recent snapshots keyed by task ID. Cache eviction recycles the underlying HardwareBuffer to prevent GPU memory leaks.
  3. Persist: A background thread managed by mTaskSnapshotPersister writes snapshots to disk as compressed bitmaps, enabling survival across process restarts.
  4. Retrieve: getSnapshot() checks the LRU cache first, then falls back to disk. The retrieved HardwareBuffer is passed to the snapshot creator in StartingSurfaceDrawer.
  5. Recycle: When a snapshot is no longer needed (task removed, newer snapshot captured), the HardwareBuffer is 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 WindowState requires holding the WM global lock and performing policy checks, input channel setup, and z-order calculation. A SurfaceControlViewHost bypasses all of this.
  • No input channel overhead: Starting windows are non-interactive; they need no input dispatch infrastructure.
  • Simpler removal: Detaching a child SurfaceControl is a single transaction operation, compared to the full window removal sequence (animation, surface destroy, input teardown).
  • Process isolation: The SurfaceControlViewHost lives 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:

  1. The application calls SplashScreen.setOnExitAnimationListener() before its first frame, signaling that it wants to control the splash screen dismissal.
  2. When the application draws its first frame, WMS calls copySplashScreenView() on the Shell controller.
  3. Shell serializes the SplashScreenView into a SplashScreenViewParcelable, capturing the icon bitmap, background color, branding image, and current animation state.
  4. The parcelable is delivered to the application through the OnExitAnimationListener callback.
  5. The application reconstructs a SplashScreenView from the parcelable and overlays it on its window.
  6. The application runs whatever exit animation it desires (fade, scale, slide, morphing).
  7. When the animation completes, the application calls SplashScreenView.remove(), which triggers cleanup of both the app-side overlay and the Shell-side SurfaceControlViewHost.

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:

  1. Tap blocking – Prevents accidental input in the letterbox region from reaching the app or underlying windows.
  2. Double-tap reachability – Detects double-tap gestures in the letterbox area and forwards them to AppCompatReachabilityPolicy to 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:

  1. Computes mSizeCompatBounds – the actual pixel bounds the app believes it occupies.
  2. Adjusts screenWidthDp and screenHeightDp to reflect the app’s logical dimensions, not the physical container.
  3. 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 by shouldEnableUserAspectRatioSettings() 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:

  1. The activity declares a fixed orientation (PORTRAIT, LANDSCAPE, etc.) in its manifest.
  2. The current display orientation does not match the declared orientation.
  3. 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 = RIGHT
  • config_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:

  1. User double-taps within a letterbox bar surface.
  2. The InputInterceptor on that surface detects the double-tap gesture.
  3. AppCompatReachabilityPolicy.handleDoubleTap() is invoked.
  4. The policy cycles the position: LEFT to CENTER to RIGHT (for horizontal) or TOP to CENTER to BOTTOM (for vertical).
  5. AppCompatLetterboxPolicy recalculates the content position within the container.
  6. Letterbox.applySurfaceChanges() repositions all surfaces in the next transaction.
  7. 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_WALLPAPER attribute, 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:

  1. The draw state transitions from PENDING to TIMEOUT.
  2. The wallpaper window is shown regardless of whether it has drawn its first frame.
  3. 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).
  4. The state then resets to NORMAL on 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 public IColorDisplayManager binder 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 composition SparseArray and 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:

  1. Start with the 4x4 identity matrix.
  2. Iterate the SparseArray in ascending key order (level 100 first, level 300 last).
  3. At each step, multiply the accumulated result by the current level’s matrix using android.opengl.Matrix.multiplyMM().
  4. The method uses a double-buffer scheme (mTempColorMatrix[2][16]) alternating between indices i % 2 and (i + 1) % 2 to 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:

  1. Convert the target CCT to CIE XYZ coordinates via ColorSpace.cctToXyz(cct).
  2. Compute the Bradford chromatic adaptation matrix using ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.BRADFORD, nominalWhiteXYZ, targetXYZ).
  3. Transform the adaptation matrix from XYZ space to the display’s RGB space:
    result = displayRGBtoXYZ^-1 * bradfordMatrix * displayRGBtoXYZ
    

    This is computed as mul3x3(inverseTransform, mul3x3(adaptation, transform)).

  4. Normalize by dividing all coefficients by the maximum of the adapted R, G, B peak values, ensuring no channel exceeds 1.0.
  5. 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:

  1. Sets the global saturation via transaction 1022 (either 1.0 or 1.1).
  2. Sets the DisplayColor setting via transaction 1023 along with the optional composition color mode.
  3. Persists both values as system properties (persist.sys.sf.color_saturation, persist.sys.sf.native_mode, persist.sys.sf.color_mode).
  4. 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).
  5. 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.Secure content 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 = 10000
  • TYPE_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 to DisplayMagnifier for magnification region updates.
  • onWindowFocusChangedNot() – updates mFocusedWindow and mFocusedDisplay, triggering accessibility focus recalculation.
  • onDisplayRemoved() – cleans up per-display state from all sparse arrays, preventing resource leaks.
  • performComputeChangedWindowsNot() – delegates to WindowsForAccessibilityObserver.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:

  1. enableWindowMagnification() transitions to CONNECTING, binds to SysUI.
  2. SysUI responds with a connection callback, transitioning to CONNECTED.
  3. disableWindowMagnification() transitions to DISCONNECTING, SysUI tears down the magnifier overlay.
  4. 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:

  1. Initialize availableBounds to full screen dimensions (or circular path for round displays).
  2. Initialize mMagnificationRegion and mImeRegion to empty.
  3. Call populateWindowsOnScreen() to gather visible WindowState objects.
  4. 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 into mImeRegion. d. Subtract already-accounted-for regions to avoid double-counting. e. If the window shouldMagnify(), union into mMagnificationRegion. f. Otherwise, union into nonMagnifiedBounds and subtract from availableBounds.
  5. Send MESSAGE_NOTIFY_MAGNIFICATION_REGION_CHANGED via 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:

  • AccessibilitySecurityPolicy checks CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT before any content query. Only services declared with this capability (and granted by the user) can access window trees.
  • FLAG_SECURE blocking: In AbstractAccessibilityServiceConnection.provideWindowSurfaceInfo(), if a window has FLAG_SECURE and the service lacks canCaptureSecureLayers() permission, screenshot requests are rejected with ERROR_TAKE_SCREENSHOT_SECURE_WINDOW.
  • RETRIEVE_WINDOW_CONTENT permission (android.permission.RETRIEVE_WINDOW_CONTENT) is enforced at the AccessibilityManagerService API boundary for findAccessibilityNodeInfosByViewId() and related tree-query methods.
  • Content redaction: When a service has general content access but not secure-layer capture, the SECURE_CONTENT_POLICY_REDACT policy 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, and mIsImeVisibleArray are all keyed by display ID. Each display can have independent magnification state.
  • AccessibilityWindowManager: Each display has its own DisplayWindowsObserver maintaining a separate window list, enabling independent TYPE_WINDOWS_CHANGED event streams.
  • AccessibilityInputFilter: Feature flags and gesture handlers are managed per display via enableFeaturesForDisplay() / disableFeaturesForDisplay(), allowing different input processing on different displays.
  • Focus arbitration: mTopFocusedDisplayId identifies the display with the global input focus. mLastNonProxyTopFocusedDisplayId separately 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 — blocks setServerVisible() 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_FOCUSABLE and FLAG_ALT_FOCUSABLE_IM are 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:

  1. If the display uses DISPLAY_IME_POLICY_FALLBACK_DISPLAY or is a virtual display without local IME, return a RemoteInsetsControlTarget so the default display’s shell controls animations.
  2. If the display is in split-screen mode, return a RemoteInsetsControlTarget owned by the shell, because the shell must coordinate the resize of both split sides when the IME appears or disappears.
  3. 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. ImeContainer overrides getOrientation() to always return SCREEN_ORIENTATION_UNSET. The IME never drives display orientation — it adapts to whatever orientation the current app has established.
  • Conditional layer assignment. The mNeedsLayer flag 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, ImeContainer moves 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:

  1. Target computation occurs before the IME window is added, based on the focused app window.
  2. The control leash is not dispatched until three conditions are met: server visibility, drawn state, and given insets readiness.
  3. Input dispatch flows through mImeInputTarget, which may differ from the layering or control targets.
  4. 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. ImeInsetsSourceProvider extends 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_METHOD and TYPE_INPUT_METHOD_DIALOG are system window types described in Section 65. Their Z-order base layer and special handling in canBeImeLayeringTarget() 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 a boundsChangeTransaction, then fades alpha 0 → 1 in TRANSITION_DIRECTION_TO_PIP.
  • Bounds entry (animateResizePip, line 1 675) — creates a PipTransitionAnimator<Rect> that interpolates the window from its original bounds to the calculated PIP rectangle. An app-icon PipContentOverlay is 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) — intercepts TRANSIT_PIP and TRANSIT_TO_BACK requests, sets state to EXITING_PIP when the current PIP task moves to back.
  • startAnimation() (line 217) — the main dispatcher; routes to startEnterAnimation() (line 1 029), startExitAnimation() (line 702), removePipImmediately() (line 950), or cleanupPipExitTransition() (line 969) based on transition type and tracked IBinder tokens (mExitTransition, mMoveToBackTransition, mRequestedEnterTransition).
  • mergeAnimation() (line 375) — calls end() 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 a Float alpha value; applies tx.setAlpha(leash, alpha) each frame.
  • ofBounds() (line 593) — interpolates Rect bounds using a RectEvaluator (line 705); computes source-rect insets with a second mInsetsEvaluator (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, calls mMotionHelper.movePip().
  • onUp() (line 875) — reads velocity from PipTouchState; 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:

  1. Fling velocity exceeds DEFAULT_STASH_VELOCITY_THRESHOLD (18 000 px/s, line 77), or
  2. 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 TvPipMenuViewTvPipMenuController.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: mFixedExpandedHeightInPx for horizontal expansion and mFixedExpandedWidthInPx for vertical expansion (lines 59–60).
  • Keep-clear areasTvPipKeepClearAlgorithm (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 without WindowContainerTransaction.

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:

  1. PipTransitionController (460 lines) is the base class that registers with Transitions.addHandler(). Both PipTransition (v1/v2) and TvPipTransition extend it.

  2. Transition types defined for PIP: TRANSIT_PIP is defined in WindowManager (line 380). The remaining types — TRANSIT_EXIT_PIP, TRANSIT_EXIT_PIP_TO_SPLIT, TRANSIT_REMOVE_PIP, TRANSIT_CLEANUP_PIP_EXIT — are defined in com.android.wm.shell.transition.Transitions.

  3. 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.

  4. Swipe-to-home — Launcher’s gesture sends setInSwipePipToHomeTransition(true) on PipTransitionState; handleSwipePipToHomeTransition() (line 1 240 of PipTransition) bridges the Launcher animation with Shell’s own enter animation via startSwipePipToHome() / stopSwipePipToHome() on PipTaskOrganizer.

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:

  1. Classic Bubble Stack — floating circles rendered by BubbleStackView, draggable anywhere on screen, managed entirely within System UI.
  2. Bubble Bar — a launcher-integrated bar where bubbles appear as icons in a taskbar-like strip, with expanded views rendered by BubbleBarLayerView and its BubbleBarExpandedView.
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 removalmBubbleData.removeBubblesWithPackageName(s, DISMISS_PACKAGE_REMOVED)
  • Shortcut invalidationmBubbleData.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:

  1. mStackMagnetListener — magnetizes the entire collapsed stack toward the dismiss circle during drag.
  2. 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
  • BubbleBarLayerView is the WindowManager-added overlay for expanded views
  • BubbleBarExpandedView replaces BubbleExpandedView, adding:
    • BubbleBarCaptionView — window caption/drag handle
    • BubbleBarMenuView — context menu view
    • BubbleBarMenuViewController — menu logic (“Don’t bubble conversation”, “App settings”, “Dismiss”, and optionally “Move to fullscreen” behind flag)
    • BubbleBarExpandedViewDragController — drag-to-reposition/dismiss
  • BubbleBarAnimationHelper drives 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:

  1. Store pendingstorePendingEnterTransition()
  2. Inflate viewsonInflatedCallback.accept(handler)
  3. Start animationstartAnimation() with TransitionInfo
  4. FinishfinishCallback.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 repositioning
  • BubbleBarMenuViewController — context menu with “Don’t bubble conversation”, “App settings”, “Dismiss”, and optionally “Move to fullscreen” (behind flag) actions
  • Caption insetsmTaskView.setCaptionInsets(Insets.of(0, mCaptionHeight, 0, 0)) reserves space for the caption bar
  • Surface clippingmTaskView.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 stack
  • prepareForBubbleDrag(View) — readies a bubble for individual drag out
  • dragBubbleOut(View, float, float) — tracks drag position
  • snapBubbleBack(View, float, float) — springs a released bubble back to its row position
  • dismissDraggedOutBubble(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 SurfaceControlViewHostBubbleExpandedView embeds TaskView which uses SurfaceControlViewHost (SCVH) to host the activity’s surface within the bubble’s view hierarchy. The embedded task surface is positioned and clipped through SurfaceControl transactions coordinated in BubbleTransitions.

  • §65 Window Types — Bubbles are added to WindowManager with TYPE_APPLICATION_OVERLAY priority layering. The BubbleStackView (classic mode) and BubbleBarLayerView (bar mode) are both added as top-level window manager views with LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS to ensure proper rendering around display cutouts.

  • Shell TransitionsBubbleTransitions implements Transitions.TransitionHandler from 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.TaskView component provides the embedded window surface. Bubble-specific wrapping occurs in BubbleTaskView which manages TaskView lifecycle, task ID tracking, and TaskOrganizer registration.

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 from SplitLayout
  • prepareExitSplitScreen(int dismissTop, WindowContainerTransaction wct, @ExitReason int exitReason) — Deactivates stages and resets the dismissed stage
  • onSnappedToDismiss(boolean closedBottomRightStage, @ExitReason int exitReason) — Triggered when the divider is dragged past a dismiss threshold; determines which stage survives and starts a dismiss transition via mSplitTransitions.startDismissTransition()
  • onDoubleTappedDivider() — Calls switchSplitPosition() to swap the two stages
  • updateSurfaceBounds(@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 active
  • mId — The @StageType identifier (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:

  1. Initializes the transition with initTransition()
  2. Checks if the pending transition was canceled
  3. Delegates to a remote handler if one is registered (e.g., Launcher provides custom anims)
  4. Falls back to playInternalAnimation() which uses alpha fade-in/fade-out with ALPHA_IN and ALPHA_OUT interpolators and FADE_DURATION (133ms)

Custom transit types used:

  • TRANSIT_SPLIT_SCREEN_PAIR_OPEN — Both tasks enter simultaneously
  • TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE — Second task enters an existing stage
  • TRANSIT_SPLIT_DISMISS_SNAP — Divider-drag dismiss
  • TRANSIT_SPLIT_DISMISS — Programmatic dismiss
  • TRANSIT_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:

  1. onLayoutPositionChanging() — Updates surface bounds in real-time using a pooled SurfaceControl.Transaction with setFrameTimelineVsync() for smooth rendering
  2. onLayoutSizeChanging() — Applies parallax effects (controlled by constants PARALLAX_NONE, PARALLAX_DISMISSING, PARALLAX_ALIGN_CENTER, PARALLAX_FLEX, PARALLAX_FLEX_HYBRID) and calls StageTaskListener.onResizing() on each stage
  3. onLayoutSizeChanged() — Commits final bounds via WindowContainerTransaction, calls updateWindowBounds(), and triggers StageTaskListener.onResized() for cleanup
  4. onSnappedToDismiss() — If the divider passes the dismiss threshold, determines the surviving stage and calls prepareExitSplitScreen() followed by mSplitTransitions.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 focus
  • exitStage(@SplitPosition int stageToClose) — Closes one side
  • swapStages() — Invokes onDoubleTappedDivider() 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 root WindowContainerToken
  • prepareMovingSplitScreenRoot(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 transitions
  • PipFlags integration is imported for coordinating PIP-related behaviors

Desktop Mode Integration:

  • EXIT_REASON_DESKTOP_MODE (value 12) — Split exits when switching to desktop windowing
  • StageCoordinator holds Optional<DesktopTasksController>, Optional<DesktopUserRepositories>, and DesktopState references
  • Desktop mode’s DesktopTasksController.onDesktopSplitSelectChoice() allows moving a desktop task into split-screen
  • The SplitScreen.SplitSelectListener interface provides onRequestEnterSplitSelect() for desktop→split transitions

Bubble Integration:

  • EXIT_REASON_CHILD_TASK_ENTER_BUBBLE (value 14) — When a task converts to a bubble
  • StageTaskListener.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 front
  • moveToFullscreen(taskInfo, transitionSource) — Exits task from desktop to fullscreen
  • moveToFullscreenWithAnimation() — Animated fullscreen transition
  • moveTaskToFront(taskInfo) — Z-order reordering
  • minimizeTask(taskInfo, minimizeReason) — Sends task to minimized state
  • minimizeMultiActivityPipTask() — Special PIP+minimize handling

Window Management:

  • toggleDesktopTaskSize(taskInfo, interaction) — Toggle between default and maximized bounds
  • snapToHalfScreen(taskInfo, position) — Snap-tile to left/right half
  • onDesktopSplitSelectChoice(taskInfo) — Enter split from desktop mode

Desk Management:

  • moveTaskToDesk(taskId, deskId) — Move task between virtual desks
  • moveTaskToDefaultDeskAndActivate(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() and getResizeHandleEdgeInset()
  • 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 buttons
  • AppHandleController — 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 mode
  • MultiDesktopData — 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 updates DesktopRepository state and starts a TRANSIT_MINIMIZE (Transitions.TRANSIT_FIRST_CUSTOM + 20) transition
  • DesktopMinimizationTransitionHandler — Handles the minimize animation, playing a scale-down + fade-out effect using MinimizeAnimator.create(). This handler only handles animation — it does not initiate transitions (its handleRequest() returns null)
  • DesksOrganizer.minimizeTask() / unminimizeTask() — Manages the desk-level container reordering for minimized tasks
  • DesktopWindowLimitRemoteHandler — Handles the TRANSIT_DESKTOP_MODE_TASK_LIMIT_MINIMIZE transition 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 minimized
  • boundsBeforeMinimizeByTaskId: 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:

  1. TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP — User starts dragging from the app handle; task scales down using MoveToDesktopAnimator with DRAG_FREEFORM_SCALE
  2. TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP — User releases in the desktop area; task finalizes position with spring physics (SpringForce, PhysicsAnimator)
  3. TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP — User drags back to status bar area; task returns to fullscreen with ReturnToDragStartAnimator

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:

  1. Find focused windowwmService.getFocusedWindowLocked(), with fallback to top task
  2. Check callback infowindow.getOnBackInvokedCallbackInfo() determines if the app registered an OnBackInvokedCallback
  3. 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 task
    • TYPE_CROSS_TASK — returning to a different task
    • TYPE_RETURN_TO_HOME — going back to the launcher
  4. Prepare animation targets via AnimationHandler.composeAnimations() which creates BackWindowAnimationAdaptor instances 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 BackTargetTypeBackAnimationRunner
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:

  1. ACTION_DOWNonGestureStarted() → creates BackTouchTracker, calls startBackNavigation() which invokes mActivityTaskManager.startBackNavigation()
  2. ACTION_MOVEonMove() → dispatches onBackProgressed() to the active callback
  3. ACTION_UPonGestureFinished() → either startPostCommitAnimation() (trigger) or dispatches onBackCancelled() (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 mInterWindowMargin gap
  • Vertical movement follows a DecelerateInterpolator for natural feel

Phase 2 — Post-Commit Animation:

  • Triggered by onGestureCommitted() when gesture crosses threshold
  • Uses SpringAnimation with fling velocity from gesture for physics-based motion
  • ValueAnimator of 500ms with EMPHASIZED interpolator 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:

  • BackAnimationController registers as a KeyGestureEventHandler via InputManager.registerKeyGestureEventHandler() for KEY_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():

  1. Creates a RemoteAnimationFinishedStub weak-reference callback to avoid leaks
  2. Starts jank monitoring via InteractionJankMonitor.begin() using the CUJ type (e.g., CUJ_PREDICTIVE_BACK_CROSS_TASK or CUJ_PREDICTIVE_BACK_HOME)
  3. Delegates to the IRemoteAnimationRunner.onAnimationStart() of the concrete animation
  4. 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 when mSyncSeqId hasn’t caught up. Returns a SurfaceControl and 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:

  1. Updates mWinFrameInScreen with the new frame bounds
  2. Copies the SurfaceControl to the local mSurface for drawing
  3. Notifies ThreadedRenderer of the new surface via setSurfaceControl()
  4. Processes returned InsetsState for 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 the RenderNode tree and enqueues work for RenderThread
  • Software fallback: drawSoftware() — locks the Surface canvas 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:

  1. Checks for changes — size, format, visibility, position, alpha, z-order
  2. Creates surface controls if needed via createBlastSurfaceControls()
  3. Applies geometry through performSurfaceTransaction() — sets position, size, crop, corner radius, z-order, and buffer transform hint
  4. Manages BLASTBufferQueue — creates or updates the buffer queue for BLAST mode
  5. Fires SurfaceHolder callbackssurfaceCreated(), surfaceChanged(), surfaceDestroyed() to notify app code

BLAST Mode:

All SurfaceViews use BLAST (Buffer Lifecycle Across Surface and Thread) mode:

  • BLASTBufferQueue replaces the legacy buffer queue path
  • Buffers are submitted via SurfaceControl.Transaction for 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:

  1. Activity.setContentView()PhoneWindow.setContentView() → creates DecorView
  2. WindowManagerImpl.addView(decorView, layoutParams) → creates ViewRootImpl
  3. ViewRootImpl.setView(decorView, attrs, ...):
    • Stores mView = decorView
    • Calls requestLayout() to trigger first traversal
    • Creates InputChannel for input delivery
    • Calls mWindowSession.addToDisplayAsUser() → WMS creates WindowState
    • Registers back callback via registerBackCallbackOnWindow()

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, calls mView.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:

  1. Resolve — transform requested overrides into resolved overrides
  2. Merge — full config = parent config + resolved overrides
  3. Notify — change listeners receive onMergedOverrideConfigurationChanged
  4. 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.

TaskFragmentcomputeConfigResourceOverrides() (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.

ActivityRecordresolveOverrideConfiguration() (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 UiModeManagerServiceATMS.updateGlobalConfigurationLocked() CONFIG_UI_MODE
Display connected/disconnected DisplayManagerServiceDisplayContent.onDisplayChanged() CONFIG_SCREEN_SIZE, CONFIG_DENSITY
Task resize (freeform / split) Shell / TaskOrganizerTask.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 onSaveInstanceStateonDestroyonCreate 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:

  1. Fixed orientation on mismatched display — Activity declares screenOrientation="portrait" but the display is landscape. resolveFixedOrientationConfiguration() applies letterboxing.

  2. Non-resizable activity in multi-window — Activity has resizeableActivity=false. AppCompatDisplayInsets captures the activity’s preferred display metrics at launch time and freezes them.

  3. Aspect ratio violation — Activity declares maxAspectRatio or minAspectRatio and the task bounds exceed them. AppCompatAspectRatioPolicy.resolveAspectRatioRestrictionIfNeeded() constrains the resolved bounds.

Lifecycle:

  • shouldCreateAppCompatDisplayInsets() (line 7555) — checks if compat insets are needed
  • updateAppCompatDisplayInsets() — called from ensureActivityConfiguration() when visible
  • resolveSizeCompatModeConfiguration() — applies the frozen insets to the resolved config
  • inSizeCompatMode() (line 7541) — returns true when the activity is operating in scaled mode
  • hasSizeCompatBounds() (line 7560) — returns true when 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:

  1. System resourcesmSystemThread.applyConfigurationToResources(tempConfig)
  2. All processes — Every WindowProcessController receives onConfigurationChanged(tempConfig) (iterating the pid map)
  3. Default display — The display configuration is recomputed and cascaded
  4. UsageStatsreportConfigurationChange() is called for telemetry
  5. 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 ownerTask.mUserId records which user created it.
  • Visibility is gated on userIdshowToCurrentUser() 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:

  1. Remove PiP root tasks — PiP mode is dismissed on user switch since PiP activities belong to the previous user.

  2. 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.

  3. Update mCurrentUser — Sets the new active user ID.

  4. Propagate to all root tasks — Calls rootTask.switchUser(userId) on every root task, which sets mCurrentUser on the task and re-positions tasks owned by the new user to the top.

  5. 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.

  6. 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.

  7. 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 searchTask.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;
}

DumpingTask.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 userWindowManagerService.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:

  1. Only applies immediately if userId matches the current user
  2. Always persists via DisplayWindowSettings.setForcedDensity(info, density, userId)
  3. On subsequent user switch, the stored density is loaded and applied

Font scale per userATMS.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() returns true for 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 of ActivityRecord.java)

Process-level isolationWindowProcessController.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):

  1. PiP windows are dismissed (removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED))
  2. All visible root-task IDs for the outgoing user are saved in mUserVisibleRootTasks
  3. The focused root-task ID is saved in mUserRootTaskInFront

During switch:

  1. mCurrentUser is updated at both WMS and RootWindowContainer level
  2. DisplayWindowSettingsProvider.setOverrideSettingsForUser(newUserId) loads the new user’s display preferences (density, size overrides)
  3. mPolicy.setCurrentUserLw(newUserId) updates the window policy
  4. Every root task receives switchUser(userId) — tasks belonging to the new user are repositioned to the top

After switch (restoring state):

  1. Saved visible root tasks for the new user are restored to the front
  2. If no saved state exists, the home activity is launched
  3. ensureActivitiesVisible() runs, which evaluates showToCurrentUser() 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:

  1. Voice sessions are never added
  2. Already at top — no-op if the task is already at index 0
  3. Affiliated tasks — groups of affiliated tasks move together
  4. Existing task repositioning — if task.inRecents is true, the task is moved to its correct z-order position via findIndexToAdd()
  5. New task insertionremoveForAddTask() removes any duplicate, then the task is inserted at index 0 (or at the removed index if the list is frozen)

TrimmingtrimInactiveRecentTasks() 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 changed
  • loadRecentTasksIfNeeded(userId) — lazy-loads persisted task metadata from disk
  • mUsersWithRecentsLoaded (SparseArray<AtomicBoolean>) tracks which users are loaded
  • mPersistedTaskIds (SparseArray<SparseBooleanArray>) tracks task IDs on disk per user

Listener notifications — The Callbacks interface provides:

  • onRecentTaskAdded(Task) — new task added to recents
  • onRecentTaskRemoved(Task, wasTrimmed, killProcess) — task removed

Additionally, TaskChangeNotificationController fires:

  • notifyTaskListUpdated() — list content changed
  • notifyTaskStackChanged() — stack order changed
  • notifyTaskListFrozen(boolean) — freeze/unfreeze events
  • notifyRecentTaskRemovedForAddTask(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 snapshots
  • mActivitySnapshotController — handles activity-level snapshots
  • mSnapshotPersistQueue — 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 retrievalgetTaskSnapshotInner() 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:

  1. PreparationprepareTaskSnapshot() computes the crop rectangle and populates a TaskSnapshot.Builder with metadata (orientation, rotation, task size, insets, appearance, windowing mode, etc.)
  2. Screen capturecreateSnapshot() calls ScreenCaptureInternal.captureLayersExcluding() with the source’s SurfaceControl, the computed crop, and a scale factor
  3. Buffer validationisInvalidHardwareBuffer() checks the returned HardwareBuffer is non-null and larger than 1×1
  4. Snapshot construction — The HardwareBuffer is set on the builder via builder.setSnapshot(buffer), producing a TaskSnapshot
  5. 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 resolution
  • config_lowResTaskSnapshotScale — e.g., 0.5 for reduced resolution
  • config_use16BitTaskSnapshotPixelFormat — RGBA_8888 vs RGB_565

Task vs. Activity snapshots:

  • TaskSnapshotController captures 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/.
  • ActivitySnapshotController captures 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 to activity_snapshots/ directory. It uses mOnBackPressedActivities to track activities participating in the predictive back gesture.

ActivitySnapshotController lifecycle:

  • beginSnapshotProcess() — clears temporary fields before a transition batch
  • endSnapshotProcess() — processes all accumulated changes
  • postProcess() — loads snapshots to cache, removes obsolete snapshots from cache/disk
  • recordSnapshot(ArrayList<ActivityRecord>) — captures one or more activities as a single snapshot
  • loadActivitySnapshot() — enqueues LoadActivitySnapshotItem to load from disk to cache on the persist thread
  • Activities that opt in to onBackInvoked dispatch 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 operation
  • persistSnapshotAndConvert(taskId, userId, snapshot, consumer) — enqueues write + low-res conversion
  • removeSnapshot(taskId, userId) — enqueues a delete operation
  • removeObsoleteFiles(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 limiting
  • ensureStoreQueueDepthLocked() evicts oldest store items when queue gets too deep
  • WriteQueueItem.isReady() checks if the user is unlocked via UserManagerInternal
  • isDuplicateOrExclusiveItem() prevents redundant writes for the same task ID
  • setPaused(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 held HardwareBuffer
  • getSnapshot(taskId, resolution, usage) — returns cached snapshot, supports resolution filtering (high, low, any)
  • getSnapshotFromDisk(taskId, userId, isLowRes, usage) — fallback to disk via AppSnapshotLoader
  • removeRunningEntry(id) — evicts entry and safely releases the HardwareBuffer via a releaser callback
  • onAppRemoved(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:

  1. ActivityRecord creates a SnapshotStartingData containing the TaskSnapshot
  2. StartingSurfaceController.createTaskSnapshotSurface() is called
  3. The Shell-side StartingWindowController receives the snapshot and renders it as a TaskSnapshotSurface — a window whose content is the captured HardwareBuffer
  4. This surface is displayed immediately while the app draws its first frame
  5. 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 mDeathHandler field/lambda to handle Launcher crashes during recents

Mixer supportRecentsMixedHandler interface allows other transition handlers (e.g., PiP, split-screen) to participate in recents transitions. Mixers are checked in registration order.

State listenersRecentsTransitionStateListener receives state changes:

  • TRANSITION_STATE_NOT_RUNNING
  • TRANSITION_STATE_REQUESTED
  • TRANSITION_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 transition
  • mClosingTasks — tasks closing (becoming invisible), typically the current foreground task
  • mPausingTasks — 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 task
  • setWillFinishToHome(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:

  1. App-levelFLAG_SECURE set in window layout params (banking apps, password fields)
  2. System-levelSensitiveContentPackages runtime blocking (sensitive notifications, OTP screens)
  3. Enterprise-levelDevicePolicyCache.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 gatecanScreenshotIme() 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:

  1. sensitiveContentAppProtection — blocks a specific window where sensitive content is rendered (keyed by window token)
  2. 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 name
  • mUid — process UID
  • mWindowToken — optional IBinder for window-level targeting (null for package-level)

This mechanism is gated behind two feature flags:

  • sensitiveContentAppProtection() — from android.view.flags
  • sensitiveNotificationAppProtection() — from android.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 implementing IDisplayHashingService)
  • 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 algorithm
  • DISPLAY_HASH_ERROR_TOO_MANY_REQUESTS — rate limited
  • DISPLAY_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:

  1. App registers via register(IScreenRecordingCallback) — returns current recording state
  2. Controller ensures MediaProjectionWatcherCallback is registered with MediaProjectionManager
  3. Callbacks are stored in ArrayMap<IBinder, Callback> with UID tracking
  4. DeathRecipient auto-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 deduplicationmLastInvokedStateByUid (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_SECURE nor SensitiveContentPackages has already blocked capture
  • Policy is cached in DevicePolicyCache singleton for fast lock-free reads
  • MDM changes propagate via DevicePolicyManagerService updating 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:

  1. WindowState.setSecureLocked()getPendingTransaction().setSecure(mSurfaceControl, isSecure) — sets the secure flag on the window’s SurfaceControl layer
  2. SurfaceFlinger’s composition pipeline checks each layer’s secure flag during frame composition
  3. When compositing for a non-secure output (screen capture, virtual display), layers marked secure are excluded or rendered as opaque black
  4. 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 display
  • fitInsetsTypes = 0 — no automatic inset fitting
  • layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
  • privateFlags |= PRIVATE_FLAG_OPTIMIZE_MEASURE
  • title = "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 progress
  • mTracking (boolean) — whether a user touch is actively dragging
  • mCollapsedOnDown — panel was fully collapsed when touch started

Gesture Flow

Touch events flow through a multi-stage pipeline:

  1. initDownStates() — captures initial state on ACTION_DOWN
  2. flingExpands() / flingExpandsQs() — determines if fling velocity opens or closes
  3. fling() — triggers animated open/close with FlingAnimationUtils
  4. onFlingEnd() — 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 KeyguardStateController for lock state changes
  • Coordinates with KeyguardTransitionInteractor for animation states
  • Uses FalsingManager to classify touch interactions (QUICK_SETTINGS, UNLOCK, BOUNCER_UNLOCK)
  • Collapses panel behavior differs between StatusBarState.SHADE and KEYGUARD

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_FOCUSABLE without conditional override
  • Insets provider — provides statusBars(), tappableElement(), and mandatorySystemGestures() insets to the system
  • Per-rotation paramsparamsForRotation[4] stores layout params for all rotations
  • Color space agnosticPRIVATE_FLAG_COLOR_SPACE_AGNOSTIC always 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_WALLPAPER is set if on keyguard/AOD
  • During doze, SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS prevents overlays
  • The bouncer activates FLAG_SECURE to prevent screenshots
  • Heads-up notifications set FLAG_NOT_TOUCH_MODAL for 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 pixels
  • computeExpansionFraction() — returns 0.0–1.0 QS expansion fraction
  • getMinExpansionHeight() — 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) — sets panelVisible = true on the window controller
  • makeExpandedInvisible() — collapses panel, resets visibility, trims memory
  • animateCollapseShade() — releases focus first, then collapses with animation
  • Listens for OpenCloseListener callbacks 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 PowerManagerServiceDisplayPowerControllerDisplayPolicy, 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_AWAKE and the dream is stopped via stopDreamLocked()
  • 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-on
  • MODE_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 for STATE_OFF only
  • isOnState(state) — true for STATE_ON, STATE_VR, STATE_ON_SUSPEND
  • isDozeState(state) — true for STATE_DOZE, STATE_DOZE_SUSPEND
  • isSuspendedState(state) — true for STATE_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:

  1. Hide overlay windows: SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS prevents third-party overlays from drawing during doze
  2. Screen brightness override: screenBrightness = mScreenBrightnessDoze forces very low brightness appropriate for AOD
  3. Wallpaper display: FLAG_SHOW_WALLPAPER is set during doze on keyguard when AOD is enabled: state.dozing && mDozeParameters.getAlwaysOn()
  4. Input feature: INPUT_FEATURE_DISABLE_USER_ACTIVITY is set on keyguard to prevent accidental screen wakes
  5. Refresh rate capping: preferredMaxDisplayRefreshRate may be set to mKeyguardMaxRefreshRate during doze to save power
  6. Screen orientation lock: Orientation is conditionally forced to SCREEN_ORIENTATION_NOSENSOR during 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:

  1. Switching disabled → Reset (no preference)
  2. Insets animation running → Reset (let VRI control)
  3. App set preferredDisplayModeId → Exact match with OVERRIDE_CHILDREN strategy
  4. App set preferredRefreshRate → Default compatibility with OVERRIDE_CHILDREN
  5. App on high-refresh-rate denylist → Force low refresh rate mode
  6. 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:

  1. nativeRegister() — Creates a native listener that hooks into SF’s per-layer frame stats, filtered by the task’s layer ID
  2. SurfaceFlinger reports frame completion times for surfaces belonging to the specified task
  3. The native listener invokes ITaskFpsCallback.onFpsReported() via JNI back to Java
  4. 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:

  1. GameManagerService updates the game mode setting and applies interventions
  2. Resolution downscaling is applied via CompatScale through the window manager
  3. FPS override is communicated to SurfaceFlinger
  4. RefreshRatePolicy picks up the app’s preferred refresh rate and votes for high mode
  5. SystemPerformanceHinter may be used for additional SF early wakeup and ADPF boosts
  6. 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:

  1. setInitialState() — Initializes launch parameters from the request
  2. computeLaunchingTaskFlags() — Resolves final launch flags based on mode and source
  3. resolveReusableTask() — Finds an existing task to reuse
  4. computeTargetTask() — Falls back to computing a target if no reuse found
  5. recycleTask() — Reuses an existing task, clearing/updating as needed
  6. deliverToCurrentTopIfNeeded() — Handles singleTop delivery
  7. setNewTask() or addOrReparentStartingActivity() — 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:

  1. Validates user ID matches (cross-user tasks cannot be recycled)
  2. Updates the task’s base intent if it was affinity-created
  3. Calls setTargetRootTaskIfNeeded() to ensure proper root task positioning
  4. Delegates to complyActivityFlags() for clearing/reordering
  5. 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:

  1. startOneHanded() checks isLockedDisabled(), mKeyguardShowing, mDisplayAreaOrganizer.isReady(), mState.isTransitioning(), mState.isInOneHanded(), and isLandscape(), rejecting if any fails.
  2. State transitions to STATE_ENTERING.
  3. Offset fraction is read from persist.debug.one_handed_offset_percentage (default ~0.5, meaning 50 % of screen height).
  4. OneHandedDisplayAreaOrganizer.scheduleOffset(0, yOffset) is called.
  5. 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):

  1. Determines the animation direction (TRANSITION_DIRECTION_TRIGGER if yOffset > 0, else TRANSITION_DIRECTION_EXIT).
  2. Begins a CUJ trace via InteractionJankMonitor for either CUJ_ONE_HANDED_ENTER_TRANSITION or CUJ_ONE_HANDED_EXIT_TRANSITION.
  3. Iterates all entries in mDisplayAreaTokenMap and calls animateWindows() for each.
  4. animateWindows() obtains an animator from OneHandedAnimationController, registers mOneHandedAnimationCallback, sets the animation duration (read from the system property persist.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 direct SurfaceControl.Transaction.
  • Updates mLastVisualOffset and mLastVisualDisplayBounds.
  • Notifies every registered OneHandedTransitionCallback via onStartFinished() or onStopFinished().

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:

  1. Computes currentValue = startValue * (1 - fraction) + endValue * fraction + 0.5f.
  2. Updates a temporary Rect with the rounded offset.
  3. 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 supportdetectTrackpadThreeFingerSwipe() 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 supportACTION_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&lt;Rect&gt;)"]
    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 from mSystemGestureExclusionLimitDp (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 the mCanSetUnrestrictedGestureExclusion permission.
  • 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 the WindowState for TYPE_NAVIGATION_BAR.
  • navigationBarCanMove() determines if the nav bar can relocate to the side on landscape displays (tablets vs. phones).
  • Window type TYPE_NAVIGATION_BAR receives special treatment in validateAddingWindowLw() — only one per display is allowed.
  • TYPE_NAVIGATION_BAR_PANEL windows 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’s WindowToken. Coordinates with AsyncRotationController to 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 parent SurfaceControl and sets layer to Integer.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:

  1. User swipes from bottom edge → SystemGesturesPointerEventListener fires onSwipeFromBottom().
  2. DisplayPolicy.requestTransientBars(swipeTarget, isGestureOnSystemBar) is called with the bottom gesture host window.
  3. After validation (not lockscreen, not dream, user setup complete), InsetsPolicy.showTransient(SHOW_TYPES_FOR_SWIPE, isGestureOnSystemBar) is invoked.
  4. showTransient() iterates all insets sources, finds hidden ones of matching types, and marks them as mShowingTransientTypes.
  5. Control target is set to mShowingTransientControlTarget — a special ControlTarget that shows bars without affecting layout.
  6. 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 resolutiongetNavControlTargetInner():

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) returns true — this includes scenarios where the display override requires it.
  • Legacy policy fallback — when no display-level overrides exist and showSystemBarsByLegacyPolicy is true, both status and navigation bars are forcibly shown.
  • Force-consumed types prevent apps from being obscured by bars they cannot control.

Panic gestureDisplayPolicy 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 pathUnfoldAnimationController receives progress events directly and applies surface transforms each frame. Used when Shell transitions are disabled.
  • Modern pathUnfoldTransitionHandler integrates with the Shell Transitions framework, 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) — iterates mAnimators in order; the first animator for which isApplicableTask(taskInfo) is true claims the task. This ordering ensures split-screen tasks are matched by SplitTaskUnfoldAnimator before FullscreenUnfoldTaskAnimator.
  • 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 claiminghandleRequest(IBinder transition, TransitionRequestInfo request):

  1. Calls shouldPlayUnfoldAnimation(request) which checks:
    • ValueAnimator.areAnimatorsEnabled() is true.
    • Transition type is TRANSIT_CHANGE.
    • Display area increased (unfold, not fold) — detected via a DefaultDisplayChange annotation with values DEFAULT_DISPLAY_UNFOLD (1) and DEFAULT_DISPLAY_FOLD (2).
  2. Stores mTransition = transition and returns a non-null WindowContainerTransaction to claim the transition.

Animation startupstartAnimation(IBinder, TransitionInfo, startT, finishT, callback):

  1. Clears all animators’ task lists.
  2. Iterates every TransitionInfo.Change — for applicable tasks (TRANSIT_CHANGE or opening), calls animator.onTaskAppeared(taskInfo, leash).
  3. For animators with active tasks: applies prepareStartTransaction(startT) and prepareFinishTransaction(finishT), then starts the animator.
  4. Applies startT.
  5. 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 via BubbleTaskUnfoldTransitionMerger.
  • tryMergeTaskFragmentClose() — merges TRANSIT_CLOSE for task fragments occurring during unfold.

Fold detectiononFoldStateChanged(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 a SurfaceControl color 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 between mBackgroundColor and mSplitScreenBackgroundColor.

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, mRootStageBounds via SplitScreenListener callbacks.
  • 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 legacy UnfoldAnimationController path.
  • @UnfoldShellTransition — marks animators for the modern UnfoldTransitionHandler path.

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 with VrManagerInternal and returns true if 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 when FLAG_PERSISTENT_VR_MODE is 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 the TOP_APP process 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 from ActivityInfo.requestedVrComponent during activity initialization. Used by VrController to determine VR mode.
  • LaunchParamsController — Checks activity.requestedVrComponent != null to 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 — Tracks is_xr_activity for 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:

  1. Declarative spatial layout — Applications describe spatial intent; the system determines physical placement and rendering strategy.
  2. Safety-first boundaries — The system controls physical safety boundaries, with apps only able to recommend—not override—safety parameters.
  3. Unified permission model — XR sensor permissions (eye, face, hand, head, scene tracking) follow the standard app-ops model, ensuring consistent privacy controls.
  4. Backward-compatible migration — The HOME_SPACE mode provides a compatibility path for existing 2D applications to function in XR environments without modification.
  5. 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: TRIM level, FRAME frequency

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 regions
  • TRACE_COMPOSITION — Composition type and state
  • TRACE_EXTRA — Additional metadata
  • TRACE_HWC — Hardware Composer state
  • TRACE_BUFFERS — Buffer queue state
  • TRACE_VIRTUAL_DISPLAYS — Virtual display layers
  • TRACE_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-free LocklessStack)
  • 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:

  1. On-device continuous tracing — Always-on low-overhead monitoring
  2. Triggered dumps — Single snapshots for specific debugging
  3. Bugreport integration — Automatic trace inclusion in bug reports
  4. 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 ActivityRecord by 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:

  1. Skip if recording is not active or no callback registered for the UID
  2. Skip if the state hasn’t changed from last invocation
  3. 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:

  1. updateRecording() — Entry point called on display changes. Pauses if display has own content; starts recording if needed.
  2. startRecordingIfNeeded() — Validates preconditions (display off, already recording, waiting for consent, PIP mode), retrieves the target WindowContainer, creates a mirrored surface hierarchy via SurfaceControl.mirrorSurface().
  3. updateMirroredSurface() — Computes scaling to fit the recorded content into the virtual display output surface, applying letterboxing as needed.
  4. pauseRecording() — Removes the mirrored surface, restores windowing and overlay layers.
  5. stopRecording() — Full stop including clearContentRecordingSession() 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 name
  • mUid — Application UID
  • mWindowToken — Specific window token (null for package-level blocking)

Management Methods:

  • addBlockScreenCaptureForApps() — Adds packages to the protection set
  • removeBlockScreenCaptureForApps() — Removes packages from protection
  • clearBlockedApps() — 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:

  1. Mirror Creation: SurfaceControl.mirrorSurface(sourceSurface, stopAt) creates a live mirror of the source hierarchy. The stopAt parameter (when recording overlay flag is enabled) excludes content above the owner’s top overlay window.

  2. Layer Isolation: The virtual display’s own windowingLayer and overlayLayer are reparented to null, preventing any directly-launched content from appearing in the recording.

  3. Scaling: computeScaling() calculates the appropriate scale to fit the recorded content into the output surface, maintaining aspect ratio with letterboxing.

  4. Consent Flow: Recording sessions can be created in a waitingForConsent state. Recording does not begin until consent is granted and the session is updated.

  5. 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 display
  • contentToRecord — DISPLAY, TASK, or BELOW_OVERLAY
  • tokenToRecord — Window token for task recording
  • displayToRecord — Display ID for display recording
  • targetUid — UID of the recording target
  • isWaitingForConsent — Whether consent has been granted

Window System Hooks:

  • DisplayContent.setContentRecordingSession() — Assigns a recording session to a display
  • DisplayContent.updateRecording() — Triggers recording state evaluation
  • DisplayContent.pauseRecording() — Pauses recording on the display
  • WindowState.isSecureLocked() — Determines if a window should be black in captures
  • WindowState.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 tracking
  • frameworks/native/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h — Incremental snapshot generation
  • frameworks/native/services/surfaceflinger/FrontEnd/RequestedLayerState.h — Client-requested layer state with 21 change flag types
  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java — Shell transition coordinator
  • frameworks/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 |