diff options
Diffstat (limited to 'src/android/jar')
11 files changed, 429 insertions, 322 deletions
diff --git a/src/android/jar/CMakeLists.txt b/src/android/jar/CMakeLists.txt index b073dbea0ea..e0a675ab463 100644 --- a/src/android/jar/CMakeLists.txt +++ b/src/android/jar/CMakeLists.txt @@ -53,6 +53,7 @@ set(java_sources src/org/qtproject/qt/android/BackgroundActionsTracker.java src/org/qtproject/qt/android/QtApkFileEngine.java src/org/qtproject/qt/android/QtContentFileEngine.java + src/org/qtproject/qt/android/QtWindowInsetsController.java ) qt_internal_add_jar(Qt${QtBase_VERSION_MAJOR}Android diff --git a/src/android/jar/src/org/qtproject/qt/android/QtActivityBase.java b/src/android/jar/src/org/qtproject/qt/android/QtActivityBase.java index aca9e3d3c11..78763e65905 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtActivityBase.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtActivityBase.java @@ -102,9 +102,11 @@ public class QtActivityBase extends Activity requestWindowFeature(Window.FEATURE_ACTION_BAR); if (!m_isCustomThemeSet) { - setTheme(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ? - android.R.style.Theme_DeviceDefault_DayNight : - android.R.style.Theme_Holo_Light); + @SuppressWarnings("deprecation") + int themeId = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q + ? android.R.style.Theme_DeviceDefault_DayNight + : android.R.style.Theme_Holo_Light; + setTheme(themeId); } if (QtNative.getStateDetails().isStarted) { @@ -168,7 +170,7 @@ public class QtActivityBase extends Activity m_delegate.displayManager().registerDisplayListener(); QtWindow.updateWindows(); // Suspending the app clears the immersive mode, so we need to set it again. - m_delegate.displayManager().reinstateFullScreen(); + QtWindowInsetsController.restoreFullScreenVisibility(this); } } @@ -311,9 +313,7 @@ public class QtActivityBase extends Activity return; QtNative.setStarted(savedInstanceState.getBoolean("Started")); - boolean isFullScreen = savedInstanceState.getBoolean("isFullScreen"); - boolean expandedToCutout = savedInstanceState.getBoolean("expandedToCutout"); - m_delegate.displayManager().setSystemUiVisibility(isFullScreen, expandedToCutout); + QtWindowInsetsController.restoreFullScreenVisibility(this); // FIXME restore all surfaces } @@ -329,8 +329,6 @@ public class QtActivityBase extends Activity protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putBoolean("isFullScreen", m_delegate.displayManager().isFullScreen()); - outState.putBoolean("expandedToCutout", m_delegate.displayManager().expandedToCutout()); outState.putBoolean("Started", QtNative.getStateDetails().isStarted); } @@ -339,7 +337,7 @@ public class QtActivityBase extends Activity { super.onWindowFocusChanged(hasFocus); if (hasFocus) - m_delegate.displayManager().reinstateFullScreen(); + QtWindowInsetsController.restoreFullScreenVisibility(this); } @Override diff --git a/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java b/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java index 118845efcec..aa2964c3ae9 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java @@ -86,21 +86,6 @@ class QtActivityDelegate extends QtActivityDelegateBase } @Override - public void setSystemUiVisibility(boolean isFullScreen, boolean expandedToCutout) - { - if (m_layout == null) - return; - - QtNative.runAction(() -> { - if (m_layout != null) { - m_displayManager.setSystemUiVisibility(isFullScreen, expandedToCutout); - QtWindow.updateWindows(); - } - }); - } - - - @Override final public void onAppStateDetailsChanged(QtNative.ApplicationStateDetails details) { if (details.isStarted) registerBackends(); @@ -138,9 +123,9 @@ class QtActivityDelegate extends QtActivityDelegateBase int orientation = m_activity.getResources().getConfiguration().orientation; setUpSplashScreen(orientation); m_activity.registerForContextMenu(m_layout); - m_activity.setContentView(m_layout, - new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT)); + ViewGroup.LayoutParams rootParams = new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + m_activity.setContentView(m_layout, rootParams); handleUiModeChange(); diff --git a/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegateBase.java b/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegateBase.java index 35f519ca9a4..7f6163f164d 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegateBase.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegateBase.java @@ -46,7 +46,9 @@ abstract class QtActivityDelegateBase m_activity = activity; QtNative.setActivity(m_activity); m_displayManager = new QtDisplayManager(m_activity); - m_inputDelegate = new QtInputDelegate(m_displayManager::reinstateFullScreen); + m_inputDelegate = new QtInputDelegate(() -> { + QtWindowInsetsController.restoreFullScreenVisibility(m_activity); + }); m_accessibilityDelegate = new QtAccessibilityDelegate(); } @@ -105,20 +107,20 @@ abstract class QtActivityDelegateBase Configuration config = resources.getConfiguration(); int uiMode = config.uiMode & Configuration.UI_MODE_NIGHT_MASK; - if (m_displayManager.decorFitsSystemWindows()) { + if (QtWindowInsetsController.decorFitsSystemWindows(m_activity)) { Window window = m_activity.getWindow(); - QtDisplayManager.enableSystemBarsBackgroundDrawing(window); - int status = QtDisplayManager.getThemeDefaultStatusBarColor(m_activity); - QtDisplayManager.setStatusBarColor(window, status); - int nav = QtDisplayManager.getThemeDefaultNavigationBarColor(m_activity); - QtDisplayManager.setNavigationBarColor(window, nav); + QtWindowInsetsController.enableSystemBarsBackgroundDrawing(window); + int status = QtWindowInsetsController.getThemeDefaultStatusBarColor(m_activity); + QtWindowInsetsController.setStatusBarColor(window, status); + int nav = QtWindowInsetsController.getThemeDefaultNavigationBarColor(m_activity); + QtWindowInsetsController.setNavigationBarColor(window, nav); } // Don't override color scheme if the app has it set explicitly. if (canOverrideColorSchemeHint()) { boolean isLight = uiMode == Configuration.UI_MODE_NIGHT_NO; - QtDisplayManager.setStatusBarColorHint(m_activity, isLight); - QtDisplayManager.setNavigationBarColorHint(m_activity, isLight); + QtWindowInsetsController.setStatusBarColorHint(m_activity, isLight); + QtWindowInsetsController.setNavigationBarColorHint(m_activity, isLight); } switch (uiMode) { diff --git a/src/android/jar/src/org/qtproject/qt/android/QtDisplayManager.java b/src/android/jar/src/org/qtproject/qt/android/QtDisplayManager.java index 8736dfab771..2cabb951813 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtDisplayManager.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtDisplayManager.java @@ -7,7 +7,6 @@ import android.app.Activity; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; -import android.content.res.TypedArray; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.os.Build; @@ -15,22 +14,13 @@ import android.util.DisplayMetrics; import android.util.Size; import android.view.Display; import android.view.Surface; -import android.view.View; -import android.view.WindowInsets; import android.view.WindowManager; -import android.view.WindowManager.LayoutParams; import android.view.WindowMetrics; -import android.view.WindowInsetsController; -import android.view.Window; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import android.graphics.Color; -import android.util.TypedValue; -import android.content.res.Resources.Theme; - import android.util.Log; class QtDisplayManager @@ -47,9 +37,6 @@ class QtDisplayManager static native void handleScreenDensityChanged(double density); // screen methods - private boolean m_isFullScreen = false; - private boolean m_expandedToCutout = false; - private static int m_previousRotation = -1; private final DisplayManager.DisplayListener m_displayListener; @@ -136,264 +123,6 @@ class QtDisplayManager } @SuppressWarnings("deprecation") - void setSystemUiVisibilityPreAndroidR(View decorView) - { - int systemUiVisibility; - - if (m_isFullScreen || m_expandedToCutout) { - systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; - if (m_isFullScreen) { - systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_FULLSCREEN - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; - } - } else { - systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE; - } - - decorView.setSystemUiVisibility(systemUiVisibility); - } - - void setSystemUiVisibility(boolean isFullScreen, boolean expandedToCutout) - { - if (m_isFullScreen == isFullScreen && m_expandedToCutout == expandedToCutout) - return; - - m_isFullScreen = isFullScreen; - m_expandedToCutout = expandedToCutout; - - Window window = m_activity.getWindow(); - View decorView = window.getDecorView(); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - int cutoutMode; - if (m_isFullScreen || m_expandedToCutout) { - window.setDecorFitsSystemWindows(false); - cutoutMode = LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; - } else { - window.setDecorFitsSystemWindows(true); - cutoutMode = LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; - } - LayoutParams layoutParams = window.getAttributes(); - layoutParams.layoutInDisplayCutoutMode = cutoutMode; - window.setAttributes(layoutParams); - - final WindowInsetsController insetsControl = window.getInsetsController(); - if (insetsControl != null) { - int sysBarsBehavior; - if (m_isFullScreen) { - insetsControl.hide(WindowInsets.Type.systemBars()); - sysBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; - } else { - insetsControl.show(WindowInsets.Type.systemBars()); - sysBarsBehavior = WindowInsetsController.BEHAVIOR_DEFAULT; - } - insetsControl.setSystemBarsBehavior(sysBarsBehavior); - } - } else { - setSystemUiVisibilityPreAndroidR(decorView); - } - - if (!isFullScreen && !edgeToEdgeEnabled(m_activity)) { - // These are needed to operate on system bar colors - window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS - | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); - window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - - // Handle transparent status and navigation bars - if (m_expandedToCutout) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - window.setStatusBarColor(Color.TRANSPARENT); - window.setNavigationBarColor(Color.TRANSPARENT); - } else { - // Android 9 and prior doesn't add the semi-transparent bars - // to avoid low contrast system icons, so try to mimick it - // by taking the current color and only increase the opacity. - int statusBarColor = window.getStatusBarColor(); - int transparentStatusBar = statusBarColor & 0x00FFFFFF; - window.setStatusBarColor(transparentStatusBar); - - int navigationBarColor = window.getNavigationBarColor(); - int semiTransparentNavigationBar = navigationBarColor & 0x7FFFFFFF; - window.setNavigationBarColor(semiTransparentNavigationBar); - } - } else { - // Restore theme's system bars colors - Theme theme = m_activity.getTheme(); - TypedValue typedValue = new TypedValue(); - - theme.resolveAttribute(android.R.attr.statusBarColor, typedValue, true); - int defaultStatusBarColor = typedValue.data; - window.setStatusBarColor(defaultStatusBarColor); - - theme.resolveAttribute(android.R.attr.navigationBarColor, typedValue, true); - int defaultNavigationBarColor = typedValue.data; - window.setNavigationBarColor(defaultNavigationBarColor); - } - } - - decorView.post(() -> decorView.requestApplyInsets()); - } - - private static boolean edgeToEdgeEnabled(Activity activity) { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.VANILLA_ICE_CREAM) - return true; - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) - return false; - int[] attrs = new int[] { android.R.attr.windowOptOutEdgeToEdgeEnforcement }; - TypedArray ta = activity.getTheme().obtainStyledAttributes(attrs); - try { - return !ta.getBoolean(0, false); - } finally { - ta.recycle(); - } - } - - boolean isFullScreen() - { - return m_isFullScreen; - } - - boolean expandedToCutout() - { - return m_expandedToCutout; - } - - boolean decorFitsSystemWindows() - { - return !isFullScreen() && !expandedToCutout(); - } - - void reinstateFullScreen() - { - if (m_isFullScreen) { - m_isFullScreen = false; - setSystemUiVisibility(true, m_expandedToCutout); - } - } - - /* - * Convenience method to call deprecated API prior to Android R (30). - */ - @SuppressWarnings ("deprecation") - private static void setSystemUiVisibility(View decorView, int flags) - { - decorView.setSystemUiVisibility(flags); - } - - /* - * Set the status bar color scheme hint so that the system decides how to color the icons. - */ - @UsedFromNativeCode - static void setStatusBarColorHint(Activity activity, boolean isLight) - { - Window window = activity.getWindow(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - WindowInsetsController controller = window.getInsetsController(); - if (controller != null) { - int lightStatusBarMask = WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; - int appearance = isLight ? lightStatusBarMask : 0; - controller.setSystemBarsAppearance(appearance, lightStatusBarMask); - } - } else { - @SuppressWarnings("deprecation") - int currentFlags = window.getDecorView().getSystemUiVisibility(); - @SuppressWarnings("deprecation") - int lightStatusBarMask = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; - int appearance = isLight - ? currentFlags | lightStatusBarMask - : currentFlags & ~lightStatusBarMask; - setSystemUiVisibility(window.getDecorView(), appearance); - } - } - - /* - * Set the navigation bar color scheme hint so that the system decides how to color the icons. - */ - @UsedFromNativeCode - static void setNavigationBarColorHint(Activity activity, boolean isLight) - { - Window window = activity.getWindow(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - WindowInsetsController controller = window.getInsetsController(); - if (controller != null) { - int lightNavigationBarMask = WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; - int appearance = isLight ? lightNavigationBarMask : 0; - controller.setSystemBarsAppearance(appearance, lightNavigationBarMask); - } - } else { - @SuppressWarnings("deprecation") - int currentFlags = window.getDecorView().getSystemUiVisibility(); - @SuppressWarnings("deprecation") - int lightNavigationBarMask = View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; - int appearance = isLight - ? currentFlags | lightNavigationBarMask - : currentFlags & ~lightNavigationBarMask; - setSystemUiVisibility(window.getDecorView(), appearance); - } - } - - static int resolveColorAttribute(Activity activity, int attribute) - { - Theme theme = activity.getTheme(); - Resources resources = activity.getResources(); - TypedValue tv = new TypedValue(); - - if (theme.resolveAttribute(attribute, tv, true)) { - if (tv.resourceId != 0) - return resources.getColor(tv.resourceId, theme); - if (tv.type >= TypedValue.TYPE_FIRST_COLOR_INT && tv.type <= TypedValue.TYPE_LAST_COLOR_INT) - return tv.data; - } - - return -1; - } - - @SuppressWarnings("deprecation") - static int getThemeDefaultStatusBarColor(Activity activity) - { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) - return -1; - return resolveColorAttribute(activity, android.R.attr.statusBarColor); - } - - @SuppressWarnings("deprecation") - static int getThemeDefaultNavigationBarColor(Activity activity) - { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) - return -1; - return resolveColorAttribute(activity, android.R.attr.navigationBarColor); - } - - static void enableSystemBarsBackgroundDrawing(Window window) - { - // These are needed to operate on system bar colors - window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - @SuppressWarnings("deprecation") - final int translucentFlags = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS - | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; - window.clearFlags(translucentFlags); - } - - @SuppressWarnings("deprecation") - static void setStatusBarColor(Window window, int color) - { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) - return; - window.setStatusBarColor(color); - } - - @SuppressWarnings("deprecation") - static void setNavigationBarColor(Window window, int color) - { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) - return; - window.setNavigationBarColor(color); - } - - @SuppressWarnings("deprecation") static Display getDisplay(Context context) { Activity activity = (Activity) context; diff --git a/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java b/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java index ca271c5dd62..e0c6c00a8fd 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java @@ -9,6 +9,7 @@ import android.graphics.Rect; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.Looper; import android.os.ResultReceiver; import android.text.method.MetaKeyKeyListener; import android.util.DisplayMetrics; @@ -166,7 +167,7 @@ class QtInputDelegate implements QtInputConnection.QtInputConnectionListener, Qt } else { if (m_imm == null) return; - m_imm.showSoftInput(m_currentEditText, 0, new ResultReceiver(new Handler()) { + m_imm.showSoftInput(m_currentEditText, 0, new ResultReceiver(new Handler(Looper.getMainLooper())) { @Override @SuppressWarnings("fallthrough") protected void onReceiveResult(int resultCode, Bundle resultData) { @@ -278,7 +279,7 @@ class QtInputDelegate implements QtInputConnection.QtInputConnectionListener, Qt activity.getWindow().getInsetsController().hide(Type.ime()); } else { m_imm.hideSoftInputFromWindow(m_currentEditText.getWindowToken(), 0, - new ResultReceiver(new Handler()) { + new ResultReceiver(new Handler(Looper.getMainLooper())) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { switch (resultCode) { diff --git a/src/android/jar/src/org/qtproject/qt/android/QtLoader.java b/src/android/jar/src/org/qtproject/qt/android/QtLoader.java index 8fd0213eb91..6d7c0624272 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtLoader.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtLoader.java @@ -345,7 +345,7 @@ abstract class QtLoader { if (metadata == null || !metadata.containsKey(key)) return ""; - return String.valueOf(metadata.get(key)); + return String.valueOf(metadata.getString(key)); } @SuppressLint("DiscouragedApi") diff --git a/src/android/jar/src/org/qtproject/qt/android/QtNative.java b/src/android/jar/src/org/qtproject/qt/android/QtNative.java index 080bb787a2d..7e4632f6792 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtNative.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtNative.java @@ -361,8 +361,11 @@ public class QtNative if (m_stateDetails.isStarted) return; + getQtThread().run(() -> initAndroidQpaPlugin()); final String qtParams = mainLib + " " + params; getQtThread().post(() -> { startQtNativeApplication(qtParams); }); + waitForServiceSetup(); + setStarted(true); } @UsedFromNativeCode @@ -428,7 +431,9 @@ public class QtNative } // application methods + static native boolean initAndroidQpaPlugin(); static native void startQtNativeApplication(String params); + static native void waitForServiceSetup(); static native void terminateQtNativeApplication(); static native boolean updateNativeActivity(); // application methods diff --git a/src/android/jar/src/org/qtproject/qt/android/QtView.java b/src/android/jar/src/org/qtproject/qt/android/QtView.java index b60c58013f5..e245f731ce8 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtView.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtView.java @@ -25,11 +25,11 @@ abstract class QtView extends ViewGroup implements QtNative.AppStateDetailsListe void onQtWindowLoaded(); } - protected QtWindow m_window; - protected long m_windowReference; - protected long m_parentWindowReference; - protected QtWindowListener m_windowListener; - protected final QtEmbeddedViewInterface m_viewInterface; + private QtWindow m_window; + private long m_windowReference; + private long m_parentWindowReference; + private QtWindowListener m_windowListener; + private final QtEmbeddedViewInterface m_viewInterface; // Implement in subclass to handle the creation of the QWindow and its parent container. // TODO could we take care of the parent window creation and parenting outside of the // window creation method to simplify things if user would extend this? Preferably without @@ -164,10 +164,43 @@ abstract class QtView extends ViewGroup implements QtNative.AppStateDetailsListe m_windowReference = windowReference; } - long windowReference() { + long getWindowReference() { return m_windowReference; } + void setQtWindow(QtWindow qtWindow) { + m_window = qtWindow; + } + + QtWindow getQtWindow() { + return m_window; + } + + long getParentWindowReference() + { + return m_parentWindowReference; + } + + void setParentWindowReference(long reference) + { + m_parentWindowReference = reference; + } + + QtWindowListener getWindowListener() + { + return m_windowListener; + } + + void setWindowListener(QtWindowListener listner) + { + m_windowListener = listner; + } + + QtEmbeddedViewInterface getViewInterface() + { + return m_viewInterface; + } + // Set the visibility of the underlying QWindow. If visible is true, showNormal() is called. // If false, the window is hidden. void setWindowVisible(boolean visible) { @@ -203,10 +236,6 @@ abstract class QtView extends ViewGroup implements QtNative.AppStateDetailsListe setWindowReference(0L); } - QtWindow getQtWindow() { - return m_window; - } - @Override public void onAppStateDetailsChanged(QtNative.ApplicationStateDetails details) { if (!details.isStarted) { diff --git a/src/android/jar/src/org/qtproject/qt/android/QtWindowInsetsController.java b/src/android/jar/src/org/qtproject/qt/android/QtWindowInsetsController.java new file mode 100644 index 00000000000..a3d0a0df8e5 --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt/android/QtWindowInsetsController.java @@ -0,0 +1,358 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +package org.qtproject.qt.android; + +import android.app.Activity; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.os.Build; +import android.view.View; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.view.WindowInsetsController; +import android.view.Window; + +import android.graphics.Color; +import android.util.TypedValue; +import android.content.res.Resources.Theme; + +class QtWindowInsetsController +{ + /* + * Convenience method to call deprecated API prior to Android R (30). + */ + @SuppressWarnings ("deprecation") + private static void setDecorFitsSystemWindows(Window window, boolean enable) + { + final int sdk = Build.VERSION.SDK_INT; + if (sdk < Build.VERSION_CODES.R || sdk > Build.VERSION_CODES.VANILLA_ICE_CREAM) + return; + window.setDecorFitsSystemWindows(enable); + } + + private static void useCutoutShortEdges(Window window, boolean enabled) + { + if (window == null) + return; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + WindowManager.LayoutParams layoutParams = window.getAttributes(); + layoutParams.layoutInDisplayCutoutMode = enabled + ? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES + : WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; + window.setAttributes(layoutParams); + } + } + + @UsedFromNativeCode + static void showNormal(Activity activity) + { + Window window = activity.getWindow(); + if (window == null) + return; + + final View decor = window.getDecorView(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + setDecorFitsSystemWindows(window, true); + WindowInsetsController ctrl = window.getInsetsController(); + if (ctrl != null) { + ctrl.show(WindowInsets.Type.systemBars()); + ctrl.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_DEFAULT); + } + } else { + @SuppressWarnings("deprecation") + int flags = View.SYSTEM_UI_FLAG_VISIBLE; // clear all flags + setSystemUiVisibility(decor, flags); + } + + setTransparentSystemBars(activity, false); + useCutoutShortEdges(window, false); + + decor.post(() -> decor.requestApplyInsets()); + } + + /* + * Make system bars transparent for Andorid versions prior to Android 15. + */ + @SuppressWarnings("deprecation") + private static void setTransparentSystemBars(Activity activity, boolean transparent) + { + Window window = activity.getWindow(); + if (window == null) + return; + + if (edgeToEdgeEnabled(activity)) + return; + + // These are needed to operate on system bar colors + window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS + | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + + if (transparent) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + window.setStatusBarColor(Color.TRANSPARENT); + window.setNavigationBarColor(Color.TRANSPARENT); + } else { + // Android 9 and prior doesn't add the semi-transparent bars + // to avoid low contrast system icons, so try to mimick it + // by taking the current color and only increase the opacity. + int statusBarColor = window.getStatusBarColor(); + int transparentStatusBar = statusBarColor & 0x00FFFFFF; + window.setStatusBarColor(transparentStatusBar); + + int navigationBarColor = window.getNavigationBarColor(); + int semiTransparentNavigationBar = navigationBarColor & 0x7FFFFFFF; + window.setNavigationBarColor(semiTransparentNavigationBar); + } + } else { + // Restore theme's system bars colors + int defaultStatusBarColor = getThemeDefaultStatusBarColor(activity); + window.setStatusBarColor(defaultStatusBarColor); + + int defaultNavigationBarColor = getThemeDefaultNavigationBarColor(activity); + window.setNavigationBarColor(defaultNavigationBarColor); + } + } + + @UsedFromNativeCode + static void showExpanded(Activity activity) + { + Window window = activity.getWindow(); + if (window == null) + return; + + final View decor = window.getDecorView(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + setDecorFitsSystemWindows(window, false); + WindowInsetsController ctrl = window.getInsetsController(); + if (ctrl != null) { + ctrl.show(WindowInsets.Type.systemBars()); + ctrl.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_DEFAULT); + } + } else { + @SuppressWarnings("deprecation") + int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; + setSystemUiVisibility(decor, flags); + } + + setTransparentSystemBars(activity, true); + useCutoutShortEdges(window, true); + + decor.post(() -> decor.requestApplyInsets()); + } + + @UsedFromNativeCode + public static void showFullScreen(Activity activity) + { + Window window = activity.getWindow(); + if (window == null) + return; + + final View decor = window.getDecorView(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + setDecorFitsSystemWindows(window, false); + WindowInsetsController ctrl = window.getInsetsController(); + if (ctrl != null) { + ctrl.hide(WindowInsets.Type.systemBars()); + ctrl.setSystemBarsBehavior( + WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE); + } + } else { + @SuppressWarnings("deprecation") + int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; + setSystemUiVisibility(decor, flags); + } + + useCutoutShortEdges(window, true); + + decor.post(() -> decor.requestApplyInsets()); + } + + private static boolean edgeToEdgeEnabled(Activity activity) { + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.VANILLA_ICE_CREAM) + return true; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) + return false; + int[] attrs = new int[] { android.R.attr.windowOptOutEdgeToEdgeEnforcement }; + TypedArray ta = activity.getTheme().obtainStyledAttributes(attrs); + try { + return !ta.getBoolean(0, false); + } finally { + ta.recycle(); + } + } + + static boolean isFullScreen(Activity activity) + { + Window window = activity.getWindow(); + if (window == null) + return false; + + final View decor = window.getDecorView(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + WindowInsets insets = activity.getWindow().getDecorView().getRootWindowInsets(); + if (insets != null) + return !insets.isVisible(WindowInsets.Type.statusBars()); + } else { + @SuppressWarnings("deprecation") + int flags = decor.getSystemUiVisibility(); + @SuppressWarnings("deprecation") + int immersiveMask = View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; + return (flags & immersiveMask) == immersiveMask; + } + + return false; + } + + static boolean isExpandedClientArea(Activity activity) + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) + return edgeToEdgeEnabled(activity); + + @SuppressWarnings("deprecation") + int statusBarColor = activity.getWindow().getStatusBarColor(); + // If the status bar is not fully opaque assume we have expanded client + // area and we're drawing under it. + int statusBarAlpha = statusBarColor >>> 24; + return statusBarAlpha != 0xFF; + } + + static boolean decorFitsSystemWindows(Activity activity) + { + return !isFullScreen(activity) && !isExpandedClientArea(activity); + } + + static void restoreFullScreenVisibility(Activity activity) + { + if (isFullScreen(activity)) + showFullScreen(activity); + } + + /* + * Convenience method to call deprecated API prior to Android R (30). + */ + @SuppressWarnings ("deprecation") + private static void setSystemUiVisibility(View decorView, int flags) + { + decorView.setSystemUiVisibility(flags); + } + + /* + * Set the status bar color scheme hint so that the system decides how to color the icons. + */ + @UsedFromNativeCode + static void setStatusBarColorHint(Activity activity, boolean isLight) + { + Window window = activity.getWindow(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + WindowInsetsController controller = window.getInsetsController(); + if (controller != null) { + int lightStatusBarMask = WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; + int appearance = isLight ? lightStatusBarMask : 0; + controller.setSystemBarsAppearance(appearance, lightStatusBarMask); + } + } else { + @SuppressWarnings("deprecation") + int currentFlags = window.getDecorView().getSystemUiVisibility(); + @SuppressWarnings("deprecation") + int lightStatusBarMask = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + int appearance = isLight + ? currentFlags | lightStatusBarMask + : currentFlags & ~lightStatusBarMask; + setSystemUiVisibility(window.getDecorView(), appearance); + } + } + + /* + * Set the navigation bar color scheme hint so that the system decides how to color the icons. + */ + @UsedFromNativeCode + static void setNavigationBarColorHint(Activity activity, boolean isLight) + { + Window window = activity.getWindow(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + WindowInsetsController controller = window.getInsetsController(); + if (controller != null) { + int lightNavigationBarMask = WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; + int appearance = isLight ? lightNavigationBarMask : 0; + controller.setSystemBarsAppearance(appearance, lightNavigationBarMask); + } + } else { + @SuppressWarnings("deprecation") + int currentFlags = window.getDecorView().getSystemUiVisibility(); + @SuppressWarnings("deprecation") + int lightNavigationBarMask = View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; + int appearance = isLight + ? currentFlags | lightNavigationBarMask + : currentFlags & ~lightNavigationBarMask; + setSystemUiVisibility(window.getDecorView(), appearance); + } + } + + private static int resolveColorAttribute(Activity activity, int attribute) + { + Theme theme = activity.getTheme(); + Resources resources = activity.getResources(); + TypedValue tv = new TypedValue(); + + if (theme.resolveAttribute(attribute, tv, true)) { + if (tv.resourceId != 0) + return resources.getColor(tv.resourceId, theme); + if (tv.type >= TypedValue.TYPE_FIRST_COLOR_INT && tv.type <= TypedValue.TYPE_LAST_COLOR_INT) + return tv.data; + } + + return -1; + } + + @SuppressWarnings("deprecation") + static int getThemeDefaultStatusBarColor(Activity activity) + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) + return -1; + return resolveColorAttribute(activity, android.R.attr.statusBarColor); + } + + @SuppressWarnings("deprecation") + static int getThemeDefaultNavigationBarColor(Activity activity) + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) + return -1; + return resolveColorAttribute(activity, android.R.attr.navigationBarColor); + } + + static void enableSystemBarsBackgroundDrawing(Window window) + { + // These are needed to operate on system bar colors + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + @SuppressWarnings("deprecation") + final int translucentFlags = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS + | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; + window.clearFlags(translucentFlags); + } + + @SuppressWarnings("deprecation") + static void setStatusBarColor(Window window, int color) + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) + return; + window.setStatusBarColor(color); + } + + @SuppressWarnings("deprecation") + static void setNavigationBarColor(Window window, int color) + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) + return; + window.setNavigationBarColor(color); + } +} diff --git a/src/android/jar/src/org/qtproject/qt/android/QtWindowInterface.java b/src/android/jar/src/org/qtproject/qt/android/QtWindowInterface.java index d42b3e6821e..1cd36f06f5c 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtWindowInterface.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtWindowInterface.java @@ -7,5 +7,4 @@ interface QtWindowInterface { default void removeTopLevelWindow(final int id) { } default void bringChildToFront(final int id) { } default void bringChildToBack(int id) { } - default void setSystemUiVisibility(boolean isFullScreen, boolean expandedToCutout) { } } |