Understanding Window Management in Android - Part 2: Z-Order, Blur Effects, and Advanced Window Properties.
We’ve reached the twelfth stage of our journey through the deep, hidden, and intricate world of the Android OS.
In this article, we’ll continue exploring the Android Window Management System.
In the previous article, we saw that Android’s window management is built on three key components: Window, WindowManager, and WindowManagerService (WMS). We also introduced a slightly unusual, but fun, analogy: "think of the Window Management System as an office. Window is the hardworking employee, WindowManager is the manager who keeps things organized, and WindowManagerService is the boss who gives the final approvals. WindowManager follows the boss’s orders and tells the Window what to do".
But to help the boss manage everything more smoothly, some company rules need to be defined. These "rules" or protocols are actually properties of the Window, defined in an inner class of "WindowManager" called "LayoutParams".
Understanding these window properties helps us to understand the internal logic of WindowManagerService. There are many types of window properties, but three are especially important for app development:
The first part of this article focuses on analyzing these properties in detail. In the second part, we’ll dive into the structure of WindowManagerService and its role in the Android startup process. Finally, I'll dedicate the last part of the article to talking about the limitations of the Blur visual effect in Android 12, and how these limitations can be worked around.
The code snippets I will show you are sometimes exact copies and sometimes simplified versions of the original AOSP code. Please note that the code is from Android 12. For each snippet, I will always provide the name of the file it was taken from, and a link to the original file in the AOSP project, version 12.1.0.r7.
So, let’s get started, once again with Will, Wendy, and Berta. The story continues!
📖 We need rules to make sure everything runs smoothly.
After working together for a while, Will the employee started noticing that things in the office weren’t as random as they seemed. Every time he needed to present something, Wendy his manager always had a set of rules to follow. Curious, Will asked one day:
"Wendy, how do you always know where, when, and how I should present?"
Wendy smiled and said, "Ah, that’s because Berta, the big boss, has created a set of company rules to help manage all of you properly."
These "company rules" are like instructions attached to each employee and in technical terms, they are called window properties. These properties live in a special document known as LayoutParams, carefully defined inside the WindowManager department.
Wendy explained: "Every Window, like you Will, comes with a LayoutParams file. It tells us everything about how you should behave, where you should be placed, how visible you are, and even whether you should be full-screen or not!"
Some of the most important properties in LayoutParams are:
Wendy added with a wink, "There are over thirty window flags, Will. That’s why it’s so important to fill out your LayoutParams carefully before asking to present!"
Behind it all, Berta WindowManagerService is still the one in charge. She not only decides who might present, but also makes sure windows don’t overlap in strange ways, manage input, animations, and even keep track of window sizes and layers.
As Wendy summed it up: "Will, you may be the star of the presentation, but without LayoutParams and Berta’s final approval, you wouldn’t even make it to the meeting room!"
Will nodded, impressed. "Wow. I had no idea so much went on behind the scenes. I’ll be sure to check my LayoutParams next time!"
And with that, the office ran a little more smoothly, all thanks to rules, order, and a whole lot of behind-the-scenes coordination.
With this short story, I hope I’ve found a good real-life metaphor to help explain the main topic of this article: how window properties are used to support the WindowManagerService in managing and building what you see on the screen of an Android device.
To effectively manage windows in Android, the WindowManagerService relies on specific properties defined for each window. These properties are encapsulated within the WindowManager.LayoutParams class, which serves as a blueprint detailing how a window should behave and appear. Understanding these parameters is crucial for developers aiming to control window behavior and appearance accurately.
Alright then, take a deep breath and let’s dive into the depths of Android to explore the meaning and mechanics behind Window properties.
🔍 Window Types.
The type property in LayoutParams determines the category of the window, influencing its placement in the Z-order and its interaction with other windows. Windows are broadly categorized into:
Each category encompasses various types, defined by constants like TYPE_APPLICATION, TYPE_APPLICATION_PANEL, TYPE_SYSTEM_ALERT, etc. These constants are assigned specific integer values that determine their stacking order.
here is just a short list:
TYPE_BASE_APPLICATION (1) the main application window, TYPE_APPLICATION (2) a typical application window, TYPE_APPLICATION_STARTING (3) a temporary window shown while the application starts, TYPE_APPLICATION_PANEL (1000) a panel on top of an application window, TYPE_APPLICATION_SUB_PANEL (1002) a sub-panel on top of an application window, TYPE_APPLICATION_ATTACHED_DIALOG (1003) a dialog attached to an application window, TYPE_PHONE (2002) a window for phone interactions and these windows are normally placed above all applications, but behind the status bar, TYPE_SYSTEM_ALERT (2003) a system-level alert window, TYPE_KEYGUARD (2004) the lock screen window, TYPE_INPUT_METHOD (2011) the input method (keyboard) window, TYPE_INPUT_METHOD_DIALOG (2012) a dialog for the input method.
Here is an example:
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_FULLSCREEN,
PixelFormat.TRANSLUCENT
);
Let’s talk about how Android decides the display order of windows, basically which window appears on top of the others.
When a process asks the WindowManagerService (WMS) to show a window, WMS determines where that window should be placed on the screen. To help manage this order, you can imagine the phone screen as a 3D space with X, Y, and Z axes. The Z axis goes in and out of the screen, so higher along the Z axis means closer to the user.
This placement is known as Z-order. It defines which windows are on top and which are behind. The window's type, which we looked at earlier, plays a key role in this.
In general, windows with higher type values are placed above those with lower values. So, system windows always appear on top of application or sub-windows, just like you've seen with things like dialogs, notifications, or the system UI.
That’s a simple way to understand how Android stacks different types of windows on your screen. Just picture the Z axis pointing outward from the screen, as I said, closer to the user means higher up in the Z-order!
So in reality, the actual implementation of this order is much more complicated.
The stacking order (Z-order) of windows is influenced by their type and certain flags. For instance using flags like FLAG_NOT_TOUCH_MODAL can allow touches outside the window to be sent to underlying windows, affecting interaction layers.
Understanding and correctly setting these properties ensures that your application's windows behave as intended, providing a seamless user experience.
🔍 Window Flags.
Flags in LayoutParams modify the default behavior of windows. They can control aspects like focusability, touchability, visibility, and more. Some commonly used flags include:
These flags can be combined using the bitwise OR operator to achieve the desired behavior.
Here is an example:
params.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
🔍 Soft Input (Keyboard) Properties.
Handling the soft keyboard's interaction with your window is managed by the softInputMode property in LayoutParams. This property combines two aspects: the state of the soft keyboard and how the window adjusts when the keyboard is displayed.
Soft Input States:
Adjustment Options:
Here is an example:
params.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
| WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
Alternatively, you can set this behavior in your AndroidManifest.xml:
<activity android:name=".MainActivity"
android:windowSoftInputMode="stateVisible|adjustResize">
</activity>
That’s all for the window properties, now it’s time to dive into the heart of the system: the WindowManagerService. We’ll take a closer look at how it starts up and explore its internal structure in detail. Take a deep breath… and let’s get started (2.),(3.).
🔍 Diving Into WindowManagerService in Android 12: Startup and Internal Structure.
WindowManagerService is a system service responsible for the entire window system on Android. It manages:
WMS Startup Process: Where It All Begins.
All core Android system services start from the SystemServer class. Let’s trace how WMS gets started. Inside SystemServer.java:
private void startOtherServices() {
...
wm = WindowManagerService.main(context, inputManager,
!mFirstBoot, mOnlyCore, new PhoneWindowManager(),
mActivityTaskManager);
ServiceManager.addService(Context.WINDOW_SERVICE, wm);
...
}
So WindowManagerService.main() is the entry point. Let’s look into that method.
public static WindowManagerService main(...) {
DisplayThread.getHandler().runWithScissors(() -> {
sInstance = new WindowManagerService(context, ...);
}, 0);
return sInstance;
}
WMS runs on the DisplayThread, a dedicated thread for managing display related tasks. runWithScissors() is used here to synchronously run the initialization logic, ensuring that WMS is properly set up before continuing.
Here is the sequence diagram of the WMS startup.
Constructor of WMS: Core Initialization.
Now let’s dive into the WMS constructor:
final ArraySet<Session> mSessions = new ArraySet<>();
final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();
final ArrayList<WindowState> mResizingWindows = new ArrayList<>();
WindowManagerPolicy mPolicy;
final InputManagerService mInputManager;
final WindowAnimator mAnimator;
final DisplayManager mDisplayManager;
private WindowManagerService(Context context, ...) {
mContext = context;
mInputManager = inputManager;
mPolicy = policy;
mAnimator = new WindowAnimator(this);
mDisplayManager = context.getSystemService(Context.DISPLAY_SERVICE);
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
mSurfaceAnimationRunner = new SurfaceAnimationRunner(mAnimationHandler);
mBlurController = new BlurController(...);
...
}
This constructor sets up all the major internal objects WMS needs:
Here’s a list of the most important data members of WindowManagerService (WMS) along with their roles:
mPolicy : Handles display policies (e.g., rotation, key handling).
mInputManager : Coordinates with InputManagerService to handle touch and key events. WMS collaborates with InputManagerService to decide which window should handle input. WMS tells InputManager which window is focused, visible, and touchable.
mAnimator : Manages window animations and transitions. Used to animate windows smoothly during transition.
mWindowMap : Stores all active windows using IBinder -> WindowState.
mSessions : Tracks client sessions. Each app or system component that opens a window has a session.
mBlurController : Handles window blur effects. Android 12 introduced more sophisticated window blur effects. WMS manages this using BlurController. In the next section we will look in depth at the Blur management chain in Android.
mDisplayManager : Manages display configurations (including multiple displays).
We have seen a detailed description of the interaction of these components in (1.)
Let's recap what we've seen in this section:
In the next and final section of this article, we'll talk about the blur effect, its limitations in Android 12, and how we managed to work around them. Once again, we'll roll up our sleeves and dive into the Android AOSP code.
🔍 What is the Blur Effect? 🌀
In Android, blur effects are used to soften parts of the UI, typically to create a frosted glass effect. Starting from Android 12 (API 31), Android introduced support for real-time window blur using the GPU.
There are two types of window blurs, which can be used to achieve different visual effects:
The two effects can be used separately or combined, as shown in the following figure:
⚠️ Android12 supports only "Blur Behind" natively, "Background Blur" is not available in Android12.
At the end of this article, I’ll show you how to make Background Blur work on Android 12 with just a few small changes to the source code of AOSP Project. For this, I’ll be using a solution made by my colleague Alan Casalboni .
But first, let’s start with the simplest thing: how to create a floating window using WindowManager that applies blur behind in Android. Here is a snippet of code.
@RequiresApi(Build.VERSION_CODES.S)
private fun showBlurWindow() {
val wm = getSystemService(WINDOW_SERVICE) as WindowManager
val view = LinearLayout(this).apply {
setBackgroundColor(0xFFFFFFFF.toInt())
setPadding(80, 80, 80, 80)
addView(TextView(context).apply {
text = "floating window"
textSize = 30f
})
}
val params = WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
WindowManager.LayoutParams.FLAG_BLUR_BEHIND or
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT
).apply {
gravity = Gravity.CENTER
blurBehindRadius = 50 // [1 - 100]
}
wm.addView(view, params)
}
It is the addition of the FLAG_BLUR_BEHIND flag to the window flags, to enable blur behind.
Here’s what it looks like when you apply blur behind to the image on the right.
Let me walk you through what happens starting from the addWindow() method in WMS, with a focus on how the Blur Behind effect is handled in Android12. We’ll take a clear look at the class hierarchy and the key method calls involved.
I’ll skip the part before the addWindow() method in WMS, since we already covered that in the previous article (1.). In that section, we followed the flow from the addView call on the WindowManager interface, through WindowManagerImpl, then to the WindowManagerGlobal singleton, and finally, via Binder, to the addWindow function inside WMS (4.).
From addWindow() to prepareSurfaces(), The Full Journey of Blur behind in Android12.
1 - WindowManagerService.addWindow(...)
This is the starting point. Located in WindowManagerService.java, this method is responsible for registering the new window in the system.
public int addWindow(Session session, IWindow client, ...) {
...
// Create the internal representation of the window
final WindowState win = new WindowState(...);
// Attach the window to its token (representing app or system type)
win.attach();
...
}
In Android12, the addWindow() method in WindowManagerService does not directly call WindowSurfacePlacer in order to prepare surfaces. Instead, the process involves several steps, and the layout and surface placement are handled later in the sequence. Here's a simplified overview:
2 - WindowSurfacePlacer.performSurfacePlacement().
This method delegates the work to the RootWindowContainer, which is the container for all windows on all displays.
void performSurfacePlacement() {
...
performSurfacePlacementLoop();
...
}
private void performSurfacePlacementLoop() {
...
mService.mRoot.performSurfacePlacement();
...
}
3 - RootWindowContainer.performSurfacePlacement().
This method also, after a chain of internal references, delegates the work to another object of type DisplayContent.
void performSurfacePlacement() {
...
performSurfacePlacementNoTrace();
...
}
void performSurfacePlacementNoTrace() {
...
applySurfaceChangesTransaction();
...
}
private void applySurfaceChangesTransaction() {
...
// dc is a DisplayContent
dc.applySurfaceChangesTransaction();
...
}
4 - DisplayContent.applySurfaceChangesTransaction().
void applySurfaceChangesTransaction() {
...
prepareSurfaces();
...
}
This is where the actual preparation of the surface trees begins.
5 - DisplayContent.prepareSurfaces().
void prepareSurfaces() {
super.prepareSurfaces();
}
🔍 Notice the call to super.prepareSurfaces(). This is the key part: DisplayContent extends WindowContainer, and this is where the automatic recursion comes into play.
6 - WindowContainer.prepareSurfaces(): la ricorsione.
This is the method where the magic happens:
void prepareSurfaces() {
for (int i = 0; i < mChildren.size(); ++i) {
mChildren.get(i).prepareSurfaces();
}
}
💡 So DisplayContent.prepareSurfaces() inherits this logic and runs it on all of its mChildren elements. In DisplayContent, the children are different types of window containers, like: TaskDisplayArea, Task, ActivityRecord and WindowState (the actual window).
This means the prepareSurfaces() call goes down through the entire hierarchy, all the way to each WindowState, where the window prepares its own SurfaceControl."
7 - WindowState.prepareSurfaces().
WindowState.java (inside WMS) : This method determines if blur should be applied:
@Override
void prepareSurfaces() {
...
applyDims();
...
// Prepares the SurfaceControl for rendering, including blur effects
mWinAnimator.prepareSurfaceLocked(getSyncTransaction());
super.prepareSurfaces();
}
private void applyDims() {
...
final float dimAmount = (mAttrs.flags & FLAG_DIM_BEHIND) != 0 ?
mAttrs.dimAmount : 0;
final int blurRadius = shouldDrawBlurBehind() ?
mAttrs.getBlurBehindRadius() : 0;
// this is a simplification, the flow is more nested
surfaceController.setBlurBehindRadius(blurBehindRadius);
}
This method configures the SurfaceControl associated with the window. This is the point where blur behind is handled, by setting flags and properties on the SurfaceControl. The method of SurfaceControl is setBackgroundBlurRadius.
The blur is passed to SurfaceFlinger via SurfaceControl.nativeSetBackgroundBlurRadius() method.
public Transaction setBackgroundBlurRadius(SurfaceControl sc, int radius) {
...
nativeSetBackgroundBlurRadius(mNativeObject, sc.mNativeObject, radius);
return this;
}
SurfaceFlinger (native C++).
In SurfaceFlinger, the blur radius is interpreted and applied as part of the rendering pipeline with the GPU.
Here’s the sequence diagram of what we’ve seen so far.
Now let’s move on to the most challenging, but also the most fun, part of this section.
💪 Let’s get our hands dirty with background blur.
And now we get to the most interesting part of this section: How to enable Background Blur on Android12 ? I’m going to show you the result I got by making a small change to the Android12 AOSP source code.
Here’s what it looks like when you apply background blur to the image on the right.
I’ll show you the change made by Alan Casalboni because it’s better than the one I originally came up with. His solution only modifies the WindowManagerGlobal singleton class, which as we saw in detail in the previous article (1.), is called from the client-side process. The big advantage is that it doesn’t require any changes to WindowManagerService or to any classes in its domain.
Here’s the code that was added to the WindowManagerGlobal class:
public void addView(...) {
...
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// start code added
if (wparams.type == TYPE_APPLICATION_OVERLAY &&
(wparams.flags & LayoutParams.FLAG_DITHER) != 0)
{
try{
BackgroundBlurDrawable viewBackground =
root.createBackgroundBlurDrawable();
viewBackground.setColor(Color.parseColor("#C71F232B"));
viewBackground.setCornerRadius(40);
viewBackground.setBlurRadius(15);
view.setBackground(viewBackground);
}
catch(Exception e){
//Log.v("WMGLOBAL", "exception " + e.getMessage());
}
}
// end code added
...
}
The idea behind this implementation is simple: if we set the flag FLAG_DITHER which is defined but unused in the AOSP source code for version 12.1.0.r7, then we trigger the custom code we added to the WindowManagerGlobal singleton, and we get the background blur effect.
In the client-side code (the same one we used earlier for blur behind), we just need to set the LayoutParams flag to FLAG_DITHER instead of FLAG_BLUR_BEHIND.
And if we set both flags at the same time, here’s what we get:
val params = WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
WindowManager.LayoutParams.FLAG_DITHER or
WindowManager.LayoutParams.FLAG_BLUR_BEHIND or
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT
).apply {
gravity = Gravity.CENTER
blurBehindRadius = 50 // [1 - 100]
}
Here’s what it looks like when you apply background blur and blur behind to the image on the right.
That's all for this topic, this article ends here.
In the next episode we will continue our journey into the deep, hidden and intricate world of Android OS.
I remind you my newsletter "Sw Design & Clean Architecture": https://lnkd.in/eUzYBuEX where you can find my previous articles and where you can register, if you have not already done, so you will be notified when I publish new articles.
Thanks for reading my article, and I hope you have found the topic useful,
Feel free to leave any feedback.
Your feedback is very appreciated.
Thanks again.
Stefano
References:
1. S.Santilli: Understanding Window Management in Android: A Deep Dive.
3. S.Santilli: Android Boot Process Part 2: From Zygote to SystemServer, SystemUI, and Launcher Initialization.