Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
QtWindowInsetsController.java
Go to the documentation of this file.
1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4package org.qtproject.qt.android;
5
6import android.app.Activity;
7import android.content.Context;
8import android.content.res.Resources;
9import android.content.res.TypedArray;
10import android.os.Build;
11import android.view.View;
12import android.view.WindowInsets;
13import android.view.WindowManager;
14import android.view.WindowInsetsController;
15import android.view.Window;
16
17import android.graphics.Color;
18import android.util.TypedValue;
19import android.content.res.Resources.Theme;
20
21class QtWindowInsetsController
22{
23 /*
24 * Convenience method to call the API deprecated in Android V (35),
25 * a no-op when the system enforces edge-to-edge for this app.
26 */
27 @SuppressWarnings ("deprecation")
28 private static void setDecorFitsSystemWindows(Window window, boolean enable)
29 {
30 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
31 return;
32 if (isEdgeToEdgeEnforced(window.getContext()))
33 return;
34 window.setDecorFitsSystemWindows(enable);
35 }
36
40 private static int themeCutoutMode(Context context)
41 {
42 int[] attrs = new int[] { android.R.attr.windowLayoutInDisplayCutoutMode };
43 TypedArray ta = context.getTheme().obtainStyledAttributes(attrs);
44 try {
45 return ta.getInt(0, WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT);
46 } finally {
47 ta.recycle();
48 }
49 }
50
51 private static void useCutoutShortEdges(Window window, boolean enabled)
52 {
53 if (window == null)
54 return;
55
56 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
57 WindowManager.LayoutParams layoutParams = window.getAttributes();
58 layoutParams.layoutInDisplayCutoutMode = enabled
59 ? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
60 : themeCutoutMode(window.getContext());
61 window.setAttributes(layoutParams);
62 }
63 }
64
65 @UsedFromNativeCode
66 static void showNormal(Activity activity)
67 {
68 Window window = activity.getWindow();
69 if (window == null)
70 return;
71
72 final View decor = window.getDecorView();
73 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
74 setDecorFitsSystemWindows(window, true);
75 WindowInsetsController ctrl = window.getInsetsController();
76 if (ctrl != null) {
77 ctrl.show(WindowInsets.Type.systemBars());
78 ctrl.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_DEFAULT);
79 }
80 } else {
81 @SuppressWarnings("deprecation")
82 int flags = View.SYSTEM_UI_FLAG_VISIBLE; // clear all flags
83 setSystemUiVisibility(decor, flags);
84 }
85
86 setTransparentSystemBars(activity, false);
87 useCutoutShortEdges(window, false);
88
89 QtRootLayout root = QtRootLayout.findIn(activity);
90 if (root != null)
91 root.setContentInsetEnabled(true);
92
93 decor.post(() -> decor.requestApplyInsets());
94 }
95
96 /*
97 * Make system bars transparent for Andorid versions prior to Android 15.
98 */
99 @SuppressWarnings("deprecation")
100 private static void setTransparentSystemBars(Activity activity, boolean transparent)
101 {
102 Window window = activity.getWindow();
103 if (window == null)
104 return;
105
106 if (isEdgeToEdgeEnforced(activity))
107 return;
108
109 // These are needed to operate on system bar colors
110 window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
111 | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
112 window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
113
114 if (transparent) {
115 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
116 window.setStatusBarColor(Color.TRANSPARENT);
117 window.setNavigationBarColor(Color.TRANSPARENT);
118 } else {
119 // Android 9 and prior doesn't add the semi-transparent bars
120 // to avoid low contrast system icons, so try to mimick it
121 // by taking the current color and only increase the opacity.
122 int statusBarColor = window.getStatusBarColor();
123 int transparentStatusBar = statusBarColor & 0x00FFFFFF;
124 window.setStatusBarColor(transparentStatusBar);
125
126 int navigationBarColor = window.getNavigationBarColor();
127 int semiTransparentNavigationBar = navigationBarColor & 0x7FFFFFFF;
128 window.setNavigationBarColor(semiTransparentNavigationBar);
129 }
130 } else {
131 // Restore theme's system bars colors
132 int defaultStatusBarColor = getThemeDefaultStatusBarColor(activity);
133 window.setStatusBarColor(defaultStatusBarColor);
134
135 int defaultNavigationBarColor = getThemeDefaultNavigationBarColor(activity);
136 window.setNavigationBarColor(defaultNavigationBarColor);
137 }
138 }
139
140 @UsedFromNativeCode
141 static void showExpanded(Activity activity)
142 {
143 Window window = activity.getWindow();
144 if (window == null)
145 return;
146
147 final View decor = window.getDecorView();
148 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
149 setDecorFitsSystemWindows(window, false);
150 WindowInsetsController ctrl = window.getInsetsController();
151 if (ctrl != null) {
152 ctrl.show(WindowInsets.Type.systemBars());
153 ctrl.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_DEFAULT);
154 }
155 } else {
156 @SuppressWarnings("deprecation")
157 int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
158 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
159 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
160 setSystemUiVisibility(decor, flags);
161 }
162
163 setTransparentSystemBars(activity, true);
164 useCutoutShortEdges(window, true);
165
166 QtRootLayout root = QtRootLayout.findIn(activity);
167 if (root != null)
168 root.setContentInsetEnabled(false);
169
170 decor.post(() -> decor.requestApplyInsets());
171 }
172
173 @UsedFromNativeCode
174 public static void showFullScreen(Activity activity)
175 {
176 Window window = activity.getWindow();
177 if (window == null)
178 return;
179
180 final View decor = window.getDecorView();
181 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
182 setDecorFitsSystemWindows(window, false);
183 WindowInsetsController ctrl = window.getInsetsController();
184 if (ctrl != null) {
185 ctrl.hide(WindowInsets.Type.systemBars());
186 ctrl.setSystemBarsBehavior(
187 WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
188 }
189 } else {
190 @SuppressWarnings("deprecation")
191 int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
192 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
193 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
194 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
195 | View.SYSTEM_UI_FLAG_FULLSCREEN
196 | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
197 setSystemUiVisibility(decor, flags);
198 }
199
200 useCutoutShortEdges(window, true);
201
202 QtRootLayout root = QtRootLayout.findIn(activity);
203 if (root != null)
204 root.setContentInsetEnabled(false);
205
206 decor.post(() -> decor.requestApplyInsets());
207 }
208
209 /*
210 * The system enforces edge-to-edge only for apps targeting Android V (35)
211 * or later, regardless of the version the device runs.
212 */
213 static boolean isEdgeToEdgeEnforced(Context context) {
214 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM)
215 return false;
216
217 final int targetSdk = context.getApplicationInfo().targetSdkVersion;
218 if (targetSdk < Build.VERSION_CODES.VANILLA_ICE_CREAM)
219 return false;
220 if (targetSdk > Build.VERSION_CODES.VANILLA_ICE_CREAM)
221 return true;
222
223 // Apps targeting exactly Android V (35) can still opt out
224 int[] attrs = new int[] { android.R.attr.windowOptOutEdgeToEdgeEnforcement };
225 TypedArray ta = context.getTheme().obtainStyledAttributes(attrs);
226 try {
227 return !ta.getBoolean(0, false);
228 } finally {
229 ta.recycle();
230 }
231 }
232
233 static boolean isFullScreen(Activity activity)
234 {
235 Window window = activity.getWindow();
236 if (window == null)
237 return false;
238
239 final View decor = window.getDecorView();
240 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
241 WindowInsetsController controller = window.getInsetsController();
242 if (controller != null) {
243 final int behavior = controller.getSystemBarsBehavior();
244 return behavior == WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
245 }
246 } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
247 WindowInsets insets = decor.getRootWindowInsets();
248 if (insets != null)
249 return !insets.isVisible(WindowInsets.Type.statusBars());
250 } else {
251 @SuppressWarnings("deprecation")
252 int flags = decor.getSystemUiVisibility();
253 @SuppressWarnings("deprecation")
254 int immersiveMask = View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
255 return (flags & immersiveMask) == immersiveMask;
256 }
257
258 return false;
259 }
260
261 static boolean isExpandedClientArea(Activity activity)
262 {
263 if (isEdgeToEdgeEnforced(activity))
264 return true;
265
266 @SuppressWarnings("deprecation")
267 int statusBarColor = activity.getWindow().getStatusBarColor();
268 // If the status bar is not fully opaque assume we have expanded client
269 // area and we're drawing under it.
270 int statusBarAlpha = statusBarColor >>> 24;
271 return statusBarAlpha != 0xFF;
272 }
273
274 static boolean decorFitsSystemWindows(Activity activity)
275 {
276 return !isFullScreen(activity) && !isExpandedClientArea(activity);
277 }
278
279 private static native void applySystemUiNative();
280
281 /*
282 * Some lifecycle events reset window state on the OS side, let the
283 * Qt side re-assert the mode it owns, if any beyond the default.
284 */
285 static void applySystemUi()
286 {
287 if (QtNative.getStateDetails().isStarted)
288 applySystemUiNative();
289 }
290
291 /*
292 * Convenience method to call deprecated API prior to Android R (30).
293 */
294 @SuppressWarnings ("deprecation")
295 private static void setSystemUiVisibility(View decorView, int flags)
296 {
297 decorView.setSystemUiVisibility(flags);
298 }
299
300 /*
301 * Set the status bar color scheme hint so that the system decides how to color the icons.
302 */
303 @UsedFromNativeCode
304 static void setStatusBarColorHint(Activity activity, boolean isLight)
305 {
306 Window window = activity.getWindow();
307 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
308 WindowInsetsController controller = window.getInsetsController();
309 if (controller != null) {
310 int lightStatusBarMask = WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
311 int appearance = isLight ? lightStatusBarMask : 0;
312 controller.setSystemBarsAppearance(appearance, lightStatusBarMask);
313 }
314 } else {
315 @SuppressWarnings("deprecation")
316 int currentFlags = window.getDecorView().getSystemUiVisibility();
317 @SuppressWarnings("deprecation")
318 int lightStatusBarMask = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
319 int appearance = isLight
320 ? currentFlags | lightStatusBarMask
321 : currentFlags & ~lightStatusBarMask;
322 setSystemUiVisibility(window.getDecorView(), appearance);
323 }
324 }
325
326 /*
327 * Set the navigation bar color scheme hint so that the system decides how to color the icons.
328 */
329 @UsedFromNativeCode
330 static void setNavigationBarColorHint(Activity activity, boolean isLight)
331 {
332 Window window = activity.getWindow();
333 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
334 WindowInsetsController controller = window.getInsetsController();
335 if (controller != null) {
336 int lightNavigationBarMask = WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
337 int appearance = isLight ? lightNavigationBarMask : 0;
338 controller.setSystemBarsAppearance(appearance, lightNavigationBarMask);
339 }
340 } else {
341 @SuppressWarnings("deprecation")
342 int currentFlags = window.getDecorView().getSystemUiVisibility();
343 @SuppressWarnings("deprecation")
344 int lightNavigationBarMask = View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
345 int appearance = isLight
346 ? currentFlags | lightNavigationBarMask
347 : currentFlags & ~lightNavigationBarMask;
348 setSystemUiVisibility(window.getDecorView(), appearance);
349 }
350 }
351
352 private static int resolveColorAttribute(Activity activity, int attribute)
353 {
354 Theme theme = activity.getTheme();
355 Resources resources = activity.getResources();
356 TypedValue tv = new TypedValue();
357
358 if (theme.resolveAttribute(attribute, tv, true)) {
359 if (tv.resourceId != 0)
360 return resources.getColor(tv.resourceId, theme);
361 if (tv.type >= TypedValue.TYPE_FIRST_COLOR_INT && tv.type <= TypedValue.TYPE_LAST_COLOR_INT)
362 return tv.data;
363 }
364
365 return -1;
366 }
367
368 @SuppressWarnings("deprecation")
369 static int getThemeDefaultStatusBarColor(Activity activity)
370 {
371 if (isEdgeToEdgeEnforced(activity))
372 return -1;
373 return resolveColorAttribute(activity, android.R.attr.statusBarColor);
374 }
375
376 @SuppressWarnings("deprecation")
377 static int getThemeDefaultNavigationBarColor(Activity activity)
378 {
379 if (isEdgeToEdgeEnforced(activity))
380 return -1;
381 return resolveColorAttribute(activity, android.R.attr.navigationBarColor);
382 }
383
384 static void enableSystemBarsBackgroundDrawing(Window window)
385 {
386 // These are needed to operate on system bar colors
387 window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
388 @SuppressWarnings("deprecation")
389 final int translucentFlags = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
390 | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
391 window.clearFlags(translucentFlags);
392 }
393
394 @SuppressWarnings("deprecation")
395 static void setStatusBarColor(Window window, int color)
396 {
397 if (isEdgeToEdgeEnforced(window.getContext()))
398 return;
399 window.setStatusBarColor(color);
400 }
401
402 @SuppressWarnings("deprecation")
403 static void setNavigationBarColor(Window window, int color)
404 {
405 if (isEdgeToEdgeEnforced(window.getContext()))
406 return;
407 window.setNavigationBarColor(color);
408 }
409}
QPainter Context
Napi::TypedArray TypedArray
Definition qnapi_p.h:76
Q_CORE_EXPORT QtJniTypes::Activity activity()
@ Window
Definition qnamespace.h:219
self window
[2]
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage return
EGLOutputLayerEXT EGLint attribute
GLbitfield flags
GLuint color
[setDefaultFactory]
GLboolean enable
#define enabled
manager post(request, myJson, this, [this](QRestReply &reply) { if(!reply.isSuccess()) { } if(std::optional json=reply.readJson()) { } })
if(foo.startsWith("("+type+") 0x")) ... QString hello("hello")
[0]