From c1fc768c0ed78780c1dc0d5a77e8a8fcfaea27c8 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 11 Sep 2025 17:17:30 +0300 Subject: [PATCH 01/13] fix: size --- .../ly/count/android/demo/HybridAppTest.java | 72 ++++++++++++++++++ .../main/res/xml/network_security_config.xml | 9 +++ .../java/ly/count/android/sdk/DeviceInfo.java | 13 +--- .../ly/count/android/sdk/ModuleContent.java | 1 - .../android/sdk/TransparentActivity.java | 45 ++++++----- .../ly/count/android/sdk/UtilsDevice.java | 74 +++++++++++++++++++ 6 files changed, 178 insertions(+), 36 deletions(-) create mode 100644 app/src/androidTest/java/ly/count/android/demo/HybridAppTest.java create mode 100644 app/src/main/res/xml/network_security_config.xml create mode 100644 sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java diff --git a/app/src/androidTest/java/ly/count/android/demo/HybridAppTest.java b/app/src/androidTest/java/ly/count/android/demo/HybridAppTest.java new file mode 100644 index 000000000..d0db92aa6 --- /dev/null +++ b/app/src/androidTest/java/ly/count/android/demo/HybridAppTest.java @@ -0,0 +1,72 @@ +// This sample code supports Appium Java client >=9 +// https://github.com/appium/java-client +package ly.count.android.demo; + +import io.appium.java_client.AppiumBy; +import io.appium.java_client.android.AndroidDriver; +import io.appium.java_client.remote.options.BaseOptions; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Random; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.openqa.selenium.Capabilities; +import org.openqa.selenium.WebElement; + +public class HybridAppTest { + + private AndroidDriver driver; + + @Before + public void setUp() { + Capabilities options = new BaseOptions() + .amend("platformName", "Android") + .amend("appium:deviceName", "emulator-5554") + .amend("appium:appPackage", "ly.count.android.demo") + .amend("appium:appActivity", ".MainActivity") + .amend("appium:automationName", "UiAutomator2") + .amend("appium:ensureWebviewsHavePages", true) + .amend("appium:nativeWebScreenshot", true) + .amend("appium:newCommandTimeout", 3600) + .amend("appium:connectHardwareKeyboard", true); + + driver = new AndroidDriver(this.getUrl(), options); + } + + @Test + public void sampleTest() throws InterruptedException { + WebElement el1 = driver.findElement(AppiumBy.id("ly.count.android.demo:id/button71")); + el1.click(); + // Code generation for action 'elementClick' is not currently supported + WebElement el2 = driver.findElement(AppiumBy.id("ly.count.android.demo:id/editTextDeviceIdContentZone")); + int randomInt = new Random().nextInt(1000); + el2.sendKeys("sticky_appium_" + randomInt); + // Code generation for action 'elementSendKeys' is not currently supported + WebElement el3 = driver.findElement(AppiumBy.id("ly.count.android.demo:id/button80")); + el3.click(); + // Code generation for action 'elementClick' is not currently supported + WebElement el4 = driver.findElement(AppiumBy.id("ly.count.android.demo:id/button74")); + el4.click(); + Thread.sleep(1000); + // Code generation for action 'elementClick' is not currently supported + driver.context("WEBVIEW_ly.count.android.demo"); + WebElement el5 = driver.findElement(AppiumBy.xpath("//div[@id=\"app\"]/div/div[2]/button[2]")); + el5.click(); + // Code generation for action 'elementClick' is not currently supported + } + + @After + public void tearDown() { + driver.quit(); + } + + private URL getUrl() { + try { + return new URL("http://127.0.0.1:4723"); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 000000000..7cdea9f6b --- /dev/null +++ b/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/sdk/src/main/java/ly/count/android/sdk/DeviceInfo.java b/sdk/src/main/java/ly/count/android/sdk/DeviceInfo.java index 29e2b08e6..dc8432f6e 100644 --- a/sdk/src/main/java/ly/count/android/sdk/DeviceInfo.java +++ b/sdk/src/main/java/ly/count/android/sdk/DeviceInfo.java @@ -38,8 +38,6 @@ of this software and associated documentation files (the "Software"), to deal import android.os.StatFs; import android.telephony.TelephonyManager; import android.util.DisplayMetrics; -import android.view.Display; -import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import java.io.File; @@ -129,19 +127,10 @@ public String getResolution(@NonNull final Context context) { return resolution; } - /** - * Return the display metrics collected from the WindowManager in the specified context. - * @param context context to use to retrieve the current WindowManager - * @return the display metrics of the current default display - */ @NonNull @Override public DisplayMetrics getDisplayMetrics(@NonNull final Context context) { - final WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - final Display display = wm.getDefaultDisplay(); - final DisplayMetrics metrics = new DisplayMetrics(); - display.getMetrics(metrics); - return metrics; + return UtilsDevice.getDisplayMetrics(context); } /** diff --git a/sdk/src/main/java/ly/count/android/sdk/ModuleContent.java b/sdk/src/main/java/ly/count/android/sdk/ModuleContent.java index 47bc6df8f..ddbcce994 100644 --- a/sdk/src/main/java/ly/count/android/sdk/ModuleContent.java +++ b/sdk/src/main/java/ly/count/android/sdk/ModuleContent.java @@ -226,7 +226,6 @@ private TransparentActivityConfig extractOrientationPlacements(@NonNull JSONObje int w = orientationPlacements.optInt("w"); int h = orientationPlacements.optInt("h"); L.d("[ModuleContent] extractOrientationPlacements, orientation: [" + orientation + "], x: [" + x + "], y: [" + y + "], w: [" + w + "], h: [" + h + "]"); - TransparentActivityConfig config = new TransparentActivityConfig((int) Math.ceil(x * density), (int) Math.ceil(y * density), (int) Math.ceil(w * density), (int) Math.ceil(h * density)); config.url = content; return config; diff --git a/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java b/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java index 49c4635c7..2edc4517d 100644 --- a/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java +++ b/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java @@ -2,7 +2,6 @@ import android.annotation.SuppressLint; import android.app.Activity; -import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Color; @@ -10,7 +9,6 @@ import android.os.Bundle; import android.util.DisplayMetrics; import android.util.Log; -import android.view.Display; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; @@ -94,10 +92,7 @@ protected void onCreate(Bundle savedInstanceState) { } private TransparentActivityConfig setupConfig(@Nullable TransparentActivityConfig config) { - final WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); - final Display display = wm.getDefaultDisplay(); - final DisplayMetrics metrics = new DisplayMetrics(); // this gets all - display.getMetrics(metrics); + final DisplayMetrics metrics = UtilsDevice.getDisplayMetrics(this); if (config == null) { Log.w(Countly.TAG, "[TransparentActivity] setupConfig, Config is null, using default values with full screen size"); @@ -149,11 +144,7 @@ public void onConfigurationChanged(android.content.res.Configuration newConfig) } // CHANGE SCREEN SIZE - final WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); - final Display display = wm.getDefaultDisplay(); - final DisplayMetrics metrics = new DisplayMetrics(); - display.getMetrics(metrics); - + final DisplayMetrics metrics = UtilsDevice.getDisplayMetrics(this); int scaledWidth = (int) Math.ceil(metrics.widthPixels / metrics.density); int scaledHeight = (int) Math.ceil(metrics.heightPixels / metrics.density); @@ -161,6 +152,16 @@ public void onConfigurationChanged(android.content.res.Configuration newConfig) webView.loadUrl("javascript:window.postMessage({type: 'resize', width: " + scaledWidth + ", height: " + scaledHeight + "}, '*');"); } + @Override + public void onDestroy() { + super.onDestroy(); + close(new HashMap<>()); + + if (Countly.sharedInstance().isInitialized()) { + Countly.sharedInstance().moduleContent.notifyAfterContentIsClosed(); + } + } + private void resizeContentInternal() { switch (currentOrientation) { case Configuration.ORIENTATION_LANDSCAPE: @@ -274,11 +275,7 @@ private void resizeMeAction(Map query) { return; } try { - final WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); - final Display display = wm.getDefaultDisplay(); - final DisplayMetrics metrics = new DisplayMetrics(); - display.getMetrics(metrics); - + final DisplayMetrics metrics = UtilsDevice.getDisplayMetrics(this); float density = metrics.density; JSONObject resizeMeJson = (JSONObject) resizeMe; @@ -414,15 +411,17 @@ private WebView createWebView(TransparentActivityConfig config) { return false; } }); - client.afterPageFinished = (closeIt) -> { - if (closeIt) { - close(new HashMap<>()); + client.afterPageFinished = new WebViewPageLoadedListener() { + @Override public void onPageLoaded(boolean timedOut) { + if (timedOut) { + close(new HashMap<>()); - if (Countly.sharedInstance().isInitialized()) { - Countly.sharedInstance().moduleContent.notifyAfterContentIsClosed(); + if (Countly.sharedInstance().isInitialized()) { + Countly.sharedInstance().moduleContent.notifyAfterContentIsClosed(); + } + } else { + webView.setVisibility(View.VISIBLE); } - } else { - webView.setVisibility(View.VISIBLE); } }; webView.setWebViewClient(client); diff --git a/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java b/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java new file mode 100644 index 000000000..78f12520e --- /dev/null +++ b/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java @@ -0,0 +1,74 @@ +package ly.count.android.sdk; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Insets; +import android.graphics.Rect; +import android.os.Build; +import android.util.DisplayMetrics; +import android.view.Display; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.view.WindowMetrics; +import androidx.annotation.NonNull; + +class UtilsDevice { + private UtilsDevice() { + } + + @NonNull + static DisplayMetrics getDisplayMetrics(@NonNull final Context context) { + final WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + final DisplayMetrics metrics = new DisplayMetrics(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + applyWindowMetrics(context, wm, metrics); + } else { + applyLegacyMetrics(context, wm, metrics); + } + return metrics; + } + + @TargetApi(Build.VERSION_CODES.R) + private static void applyWindowMetrics(@NonNull Context context, + @NonNull WindowManager wm, + @NonNull DisplayMetrics outMetrics) { + final WindowMetrics windowMetrics = wm.getCurrentWindowMetrics(); + + // Exclude system insets (status bar, nav bar, cutout) + final Insets insets = windowMetrics.getWindowInsets() + .getInsetsIgnoringVisibility( + WindowInsets.Type.navigationBars() + | WindowInsets.Type.displayCutout() + | WindowInsets.Type.statusBars() + ); + + final Rect bounds = windowMetrics.getBounds(); + final int width = bounds.width() - insets.left - insets.right; + final int height = bounds.height() - insets.top - insets.bottom; + + outMetrics.widthPixels = width; + outMetrics.heightPixels = height; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + outMetrics.density = windowMetrics.getDensity(); + } else { + // Fallback: use resource-based density + outMetrics.density = context.getResources().getDisplayMetrics().density; + } + } + + @SuppressWarnings("deprecation") + private static void applyLegacyMetrics(@NonNull final Context context, + @NonNull WindowManager wm, + @NonNull DisplayMetrics outMetrics) { + final Display display = wm.getDefaultDisplay(); + display.getMetrics(outMetrics); + + // pre-api level 30 does not include status bar in heightPixels + int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); + if (resourceId > 0) { + outMetrics.heightPixels -= context.getResources().getDimensionPixelSize(resourceId); + } + } +} From cfa04e61b7894b8756e9f56af1542c18a642ee96 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray <57103426+arifBurakDemiray@users.noreply.github.com> Date: Thu, 11 Sep 2025 17:18:17 +0300 Subject: [PATCH 02/13] Delete app/src/androidTest/java/ly/count/android/demo/HybridAppTest.java --- .../ly/count/android/demo/HybridAppTest.java | 72 ------------------- 1 file changed, 72 deletions(-) delete mode 100644 app/src/androidTest/java/ly/count/android/demo/HybridAppTest.java diff --git a/app/src/androidTest/java/ly/count/android/demo/HybridAppTest.java b/app/src/androidTest/java/ly/count/android/demo/HybridAppTest.java deleted file mode 100644 index d0db92aa6..000000000 --- a/app/src/androidTest/java/ly/count/android/demo/HybridAppTest.java +++ /dev/null @@ -1,72 +0,0 @@ -// This sample code supports Appium Java client >=9 -// https://github.com/appium/java-client -package ly.count.android.demo; - -import io.appium.java_client.AppiumBy; -import io.appium.java_client.android.AndroidDriver; -import io.appium.java_client.remote.options.BaseOptions; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Random; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.openqa.selenium.Capabilities; -import org.openqa.selenium.WebElement; - -public class HybridAppTest { - - private AndroidDriver driver; - - @Before - public void setUp() { - Capabilities options = new BaseOptions() - .amend("platformName", "Android") - .amend("appium:deviceName", "emulator-5554") - .amend("appium:appPackage", "ly.count.android.demo") - .amend("appium:appActivity", ".MainActivity") - .amend("appium:automationName", "UiAutomator2") - .amend("appium:ensureWebviewsHavePages", true) - .amend("appium:nativeWebScreenshot", true) - .amend("appium:newCommandTimeout", 3600) - .amend("appium:connectHardwareKeyboard", true); - - driver = new AndroidDriver(this.getUrl(), options); - } - - @Test - public void sampleTest() throws InterruptedException { - WebElement el1 = driver.findElement(AppiumBy.id("ly.count.android.demo:id/button71")); - el1.click(); - // Code generation for action 'elementClick' is not currently supported - WebElement el2 = driver.findElement(AppiumBy.id("ly.count.android.demo:id/editTextDeviceIdContentZone")); - int randomInt = new Random().nextInt(1000); - el2.sendKeys("sticky_appium_" + randomInt); - // Code generation for action 'elementSendKeys' is not currently supported - WebElement el3 = driver.findElement(AppiumBy.id("ly.count.android.demo:id/button80")); - el3.click(); - // Code generation for action 'elementClick' is not currently supported - WebElement el4 = driver.findElement(AppiumBy.id("ly.count.android.demo:id/button74")); - el4.click(); - Thread.sleep(1000); - // Code generation for action 'elementClick' is not currently supported - driver.context("WEBVIEW_ly.count.android.demo"); - WebElement el5 = driver.findElement(AppiumBy.xpath("//div[@id=\"app\"]/div/div[2]/button[2]")); - el5.click(); - // Code generation for action 'elementClick' is not currently supported - } - - @After - public void tearDown() { - driver.quit(); - } - - private URL getUrl() { - try { - return new URL("http://127.0.0.1:4723"); - } catch (MalformedURLException e) { - e.printStackTrace(); - } - return null; - } -} From 8a1d369bf635006c42b00cf12b7d78f49f158621 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray <57103426+arifBurakDemiray@users.noreply.github.com> Date: Thu, 11 Sep 2025 17:18:29 +0300 Subject: [PATCH 03/13] Delete app/src/main/res/xml/network_security_config.xml --- app/src/main/res/xml/network_security_config.xml | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 app/src/main/res/xml/network_security_config.xml diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml deleted file mode 100644 index 7cdea9f6b..000000000 --- a/app/src/main/res/xml/network_security_config.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file From c58b2b6406c2cb24a156cace8f3b874687a7bad1 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 11 Sep 2025 17:21:07 +0300 Subject: [PATCH 04/13] fix: size changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0132a231a..922b9c698 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## XX.XX.XX +* Mitigated an issue where content sizing was handled incorrectly on API level 35 and above. + ## 25.4.3 * Improved Health Check metric information. * Improved Content display mechanics. From 97f88dde9fdfe076cedfd21865056cd3c31dd4ec Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Fri, 12 Sep 2025 13:03:00 +0300 Subject: [PATCH 05/13] feat: handle hideable navigations --- .../java/ly/count/android/sdk/DeviceInfo.java | 2 +- .../android/sdk/TransparentActivity.java | 45 +++++++++++++++++-- .../ly/count/android/sdk/UtilsDevice.java | 41 +++++++++-------- 3 files changed, 64 insertions(+), 24 deletions(-) diff --git a/sdk/src/main/java/ly/count/android/sdk/DeviceInfo.java b/sdk/src/main/java/ly/count/android/sdk/DeviceInfo.java index dc8432f6e..86b85a328 100644 --- a/sdk/src/main/java/ly/count/android/sdk/DeviceInfo.java +++ b/sdk/src/main/java/ly/count/android/sdk/DeviceInfo.java @@ -130,7 +130,7 @@ public String getResolution(@NonNull final Context context) { @NonNull @Override public DisplayMetrics getDisplayMetrics(@NonNull final Context context) { - return UtilsDevice.getDisplayMetrics(context); + return UtilsDevice.getDisplayMetrics(context, true); } /** diff --git a/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java b/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java index 2edc4517d..c4f287330 100644 --- a/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java +++ b/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java @@ -6,12 +6,14 @@ import android.content.res.Configuration; import android.graphics.Color; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.util.DisplayMetrics; import android.util.Log; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; +import android.view.WindowInsets; import android.view.WindowManager; import android.webkit.WebSettings; import android.webkit.WebView; @@ -45,7 +47,8 @@ protected void onCreate(Bundle savedInstanceState) { // there is a stripe at the top of the screen for contents // we eliminate it with hiding the system ui - getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + hideSystemUI(); + subscribeForSystemUiChanges(); super.onCreate(savedInstanceState); overridePendingTransition(0, 0); @@ -92,7 +95,7 @@ protected void onCreate(Bundle savedInstanceState) { } private TransparentActivityConfig setupConfig(@Nullable TransparentActivityConfig config) { - final DisplayMetrics metrics = UtilsDevice.getDisplayMetrics(this); + final DisplayMetrics metrics = UtilsDevice.getDisplayMetrics(this, false); if (config == null) { Log.w(Countly.TAG, "[TransparentActivity] setupConfig, Config is null, using default values with full screen size"); @@ -143,8 +146,12 @@ public void onConfigurationChanged(android.content.res.Configuration newConfig) currentOrientation = newConfig.orientation; } + resizeContent(); + } + + private void resizeContent() { // CHANGE SCREEN SIZE - final DisplayMetrics metrics = UtilsDevice.getDisplayMetrics(this); + final DisplayMetrics metrics = UtilsDevice.getDisplayMetrics(this, false); int scaledWidth = (int) Math.ceil(metrics.widthPixels / metrics.density); int scaledHeight = (int) Math.ceil(metrics.heightPixels / metrics.density); @@ -162,6 +169,36 @@ public void onDestroy() { } } + private void hideSystemUI() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + getWindow().setDecorFitsSystemWindows(false); + if (getWindow().getDecorView().getWindowInsetsController() != null) { + getWindow().getDecorView().getWindowInsetsController().hide(WindowInsets.Type.statusBars()); + } + } else { + getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_FULLSCREEN + ); + } + } + + private void subscribeForSystemUiChanges() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + View root = getWindow().getDecorView(); + root.setOnApplyWindowInsetsListener((v, insets) -> { + resizeContent(); + return insets; + }); + } else { + View decorView = getWindow().getDecorView(); + decorView.setOnSystemUiVisibilityChangeListener(visibility -> { + resizeContent(); + }); + } + } + private void resizeContentInternal() { switch (currentOrientation) { case Configuration.ORIENTATION_LANDSCAPE: @@ -275,7 +312,7 @@ private void resizeMeAction(Map query) { return; } try { - final DisplayMetrics metrics = UtilsDevice.getDisplayMetrics(this); + final DisplayMetrics metrics = UtilsDevice.getDisplayMetrics(this, false); float density = metrics.density; JSONObject resizeMeJson = (JSONObject) resizeMe; diff --git a/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java b/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java index 78f12520e..82b0bb625 100644 --- a/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java +++ b/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java @@ -17,14 +17,14 @@ private UtilsDevice() { } @NonNull - static DisplayMetrics getDisplayMetrics(@NonNull final Context context) { + static DisplayMetrics getDisplayMetrics(@NonNull final Context context, boolean ignoreStatusBar) { final WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); final DisplayMetrics metrics = new DisplayMetrics(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - applyWindowMetrics(context, wm, metrics); + applyWindowMetrics(context, wm, metrics, ignoreStatusBar); } else { - applyLegacyMetrics(context, wm, metrics); + applyLegacyMetrics(wm, metrics); } return metrics; } @@ -32,17 +32,27 @@ static DisplayMetrics getDisplayMetrics(@NonNull final Context context) { @TargetApi(Build.VERSION_CODES.R) private static void applyWindowMetrics(@NonNull Context context, @NonNull WindowManager wm, - @NonNull DisplayMetrics outMetrics) { + @NonNull DisplayMetrics outMetrics, boolean ignoreStatusBar) { final WindowMetrics windowMetrics = wm.getCurrentWindowMetrics(); + final WindowInsets windowInsets = windowMetrics.getWindowInsets(); + + // Always respect status bar & cutout (they affect safe area even in fullscreen) + int types = 0; + + // Only subtract navigation bar insets when navigation bar is actually visible + if (windowInsets.isVisible(WindowInsets.Type.navigationBars())) { + types |= WindowInsets.Type.navigationBars(); + } + + if (!ignoreStatusBar && windowInsets.isVisible(WindowInsets.Type.statusBars())) { + types |= WindowInsets.Type.statusBars(); + } - // Exclude system insets (status bar, nav bar, cutout) - final Insets insets = windowMetrics.getWindowInsets() - .getInsetsIgnoringVisibility( - WindowInsets.Type.navigationBars() - | WindowInsets.Type.displayCutout() - | WindowInsets.Type.statusBars() - ); + if (windowInsets.isVisible(WindowInsets.Type.displayCutout())) { + types |= WindowInsets.Type.displayCutout(); + } + final Insets insets = windowInsets.getInsets(types); final Rect bounds = windowMetrics.getBounds(); final int width = bounds.width() - insets.left - insets.right; final int height = bounds.height() - insets.top - insets.bottom; @@ -59,16 +69,9 @@ private static void applyWindowMetrics(@NonNull Context context, } @SuppressWarnings("deprecation") - private static void applyLegacyMetrics(@NonNull final Context context, - @NonNull WindowManager wm, + private static void applyLegacyMetrics(@NonNull WindowManager wm, @NonNull DisplayMetrics outMetrics) { final Display display = wm.getDefaultDisplay(); display.getMetrics(outMetrics); - - // pre-api level 30 does not include status bar in heightPixels - int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); - if (resourceId > 0) { - outMetrics.heightPixels -= context.getResources().getDimensionPixelSize(resourceId); - } } } From 9be11df51f3708123aaff2b1e1d0f0c1e5006fb8 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Mon, 15 Sep 2025 12:40:43 +0300 Subject: [PATCH 06/13] feat: add some cutout support --- .../main/java/ly/count/android/sdk/UtilsDevice.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java b/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java index 82b0bb625..585b84cd1 100644 --- a/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java +++ b/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java @@ -1,6 +1,7 @@ package ly.count.android.sdk; import android.annotation.TargetApi; +import android.app.Activity; import android.content.Context; import android.graphics.Insets; import android.graphics.Rect; @@ -48,7 +49,14 @@ private static void applyWindowMetrics(@NonNull Context context, types |= WindowInsets.Type.statusBars(); } - if (windowInsets.isVisible(WindowInsets.Type.displayCutout())) { + boolean drawUnderCutout = false; + if (context instanceof Activity) { + WindowManager.LayoutParams params = ((Activity) context).getWindow().getAttributes(); + drawUnderCutout = params.layoutInDisplayCutoutMode + == WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + } + + if (!drawUnderCutout && windowInsets.isVisible(WindowInsets.Type.displayCutout())) { types |= WindowInsets.Type.displayCutout(); } @@ -72,6 +80,7 @@ private static void applyWindowMetrics(@NonNull Context context, private static void applyLegacyMetrics(@NonNull WindowManager wm, @NonNull DisplayMetrics outMetrics) { final Display display = wm.getDefaultDisplay(); - display.getMetrics(outMetrics); + display.getRealMetrics(outMetrics); + //getMetrics gives us size minus navigation bar } } From d0cc24cfd0b99fe843d5319857b48b9055f91724 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Mon, 15 Sep 2025 15:53:48 +0300 Subject: [PATCH 07/13] feat: more reliable screen size gathering --- .../ly/count/android/sdk/UtilsDevice.java | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java b/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java index 585b84cd1..c41dca888 100644 --- a/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java +++ b/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java @@ -18,12 +18,12 @@ private UtilsDevice() { } @NonNull - static DisplayMetrics getDisplayMetrics(@NonNull final Context context, boolean ignoreStatusBar) { + static DisplayMetrics getDisplayMetrics(@NonNull final Context context) { final WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); final DisplayMetrics metrics = new DisplayMetrics(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - applyWindowMetrics(context, wm, metrics, ignoreStatusBar); + applyWindowMetrics(context, wm, metrics); } else { applyLegacyMetrics(wm, metrics); } @@ -33,31 +33,34 @@ static DisplayMetrics getDisplayMetrics(@NonNull final Context context, boolean @TargetApi(Build.VERSION_CODES.R) private static void applyWindowMetrics(@NonNull Context context, @NonNull WindowManager wm, - @NonNull DisplayMetrics outMetrics, boolean ignoreStatusBar) { + @NonNull DisplayMetrics outMetrics) { final WindowMetrics windowMetrics = wm.getCurrentWindowMetrics(); final WindowInsets windowInsets = windowMetrics.getWindowInsets(); // Always respect status bar & cutout (they affect safe area even in fullscreen) int types = 0; + boolean usePhysicalScreenSize = !(context instanceof Activity); - // Only subtract navigation bar insets when navigation bar is actually visible - if (windowInsets.isVisible(WindowInsets.Type.navigationBars())) { - types |= WindowInsets.Type.navigationBars(); - } + // If not activity, we can't know system UI visibility, so always use physical screen size + if (!usePhysicalScreenSize) { + // Only subtract navigation bar insets when navigation bar is actually visible + if (windowInsets.isVisible(WindowInsets.Type.navigationBars())) { + types |= WindowInsets.Type.navigationBars(); + } - if (!ignoreStatusBar && windowInsets.isVisible(WindowInsets.Type.statusBars())) { - types |= WindowInsets.Type.statusBars(); - } + if (windowInsets.isVisible(WindowInsets.Type.statusBars())) { + types |= WindowInsets.Type.statusBars(); + } - boolean drawUnderCutout = false; - if (context instanceof Activity) { + boolean drawUnderCutout; WindowManager.LayoutParams params = ((Activity) context).getWindow().getAttributes(); drawUnderCutout = params.layoutInDisplayCutoutMode == WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; - } - if (!drawUnderCutout && windowInsets.isVisible(WindowInsets.Type.displayCutout())) { - types |= WindowInsets.Type.displayCutout(); + // Only subtract display cutout insets when not allowed to draw under the cutout + if (!drawUnderCutout && windowInsets.isVisible(WindowInsets.Type.displayCutout())) { + types |= WindowInsets.Type.displayCutout(); + } } final Insets insets = windowInsets.getInsets(types); From fdefddcc8800100fc9bc3d0f240ef2ce30969c4a Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Mon, 15 Sep 2025 15:54:47 +0300 Subject: [PATCH 08/13] feat: hide system ui after page load --- .../java/ly/count/android/sdk/DeviceInfo.java | 2 +- .../android/sdk/TransparentActivity.java | 50 ++++++------------- 2 files changed, 17 insertions(+), 35 deletions(-) diff --git a/sdk/src/main/java/ly/count/android/sdk/DeviceInfo.java b/sdk/src/main/java/ly/count/android/sdk/DeviceInfo.java index 86b85a328..dc8432f6e 100644 --- a/sdk/src/main/java/ly/count/android/sdk/DeviceInfo.java +++ b/sdk/src/main/java/ly/count/android/sdk/DeviceInfo.java @@ -130,7 +130,7 @@ public String getResolution(@NonNull final Context context) { @NonNull @Override public DisplayMetrics getDisplayMetrics(@NonNull final Context context) { - return UtilsDevice.getDisplayMetrics(context, true); + return UtilsDevice.getDisplayMetrics(context); } /** diff --git a/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java b/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java index c4f287330..7602b0735 100644 --- a/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java +++ b/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java @@ -13,7 +13,6 @@ import android.view.Gravity; import android.view.View; import android.view.ViewGroup; -import android.view.WindowInsets; import android.view.WindowManager; import android.webkit.WebSettings; import android.webkit.WebView; @@ -47,8 +46,6 @@ protected void onCreate(Bundle savedInstanceState) { // there is a stripe at the top of the screen for contents // we eliminate it with hiding the system ui - hideSystemUI(); - subscribeForSystemUiChanges(); super.onCreate(savedInstanceState); overridePendingTransition(0, 0); @@ -73,13 +70,16 @@ protected void onCreate(Bundle savedInstanceState) { // Configure window layout parameters WindowManager.LayoutParams params = new WindowManager.LayoutParams(); - params.gravity = Gravity.TOP | Gravity.LEFT; // try out START + params.gravity = Gravity.TOP | Gravity.START; // try out START params.x = config.x; params.y = config.y; params.height = config.height; params.width = config.width; params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + } getWindow().setAttributes(params); getWindow().setBackgroundDrawableResource(android.R.color.transparent); @@ -95,7 +95,7 @@ protected void onCreate(Bundle savedInstanceState) { } private TransparentActivityConfig setupConfig(@Nullable TransparentActivityConfig config) { - final DisplayMetrics metrics = UtilsDevice.getDisplayMetrics(this, false); + final DisplayMetrics metrics = UtilsDevice.getDisplayMetrics(this); if (config == null) { Log.w(Countly.TAG, "[TransparentActivity] setupConfig, Config is null, using default values with full screen size"); @@ -151,7 +151,7 @@ public void onConfigurationChanged(android.content.res.Configuration newConfig) private void resizeContent() { // CHANGE SCREEN SIZE - final DisplayMetrics metrics = UtilsDevice.getDisplayMetrics(this, false); + final DisplayMetrics metrics = UtilsDevice.getDisplayMetrics(this); int scaledWidth = (int) Math.ceil(metrics.widthPixels / metrics.density); int scaledHeight = (int) Math.ceil(metrics.heightPixels / metrics.density); @@ -170,33 +170,14 @@ public void onDestroy() { } private void hideSystemUI() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - getWindow().setDecorFitsSystemWindows(false); - if (getWindow().getDecorView().getWindowInsetsController() != null) { - getWindow().getDecorView().getWindowInsetsController().hide(WindowInsets.Type.statusBars()); - } - } else { - getWindow().getDecorView().setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_FULLSCREEN - ); - } - } - - private void subscribeForSystemUiChanges() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - View root = getWindow().getDecorView(); - root.setOnApplyWindowInsetsListener((v, insets) -> { - resizeContent(); - return insets; - }); - } else { - View decorView = getWindow().getDecorView(); - decorView.setOnSystemUiVisibilityChangeListener(visibility -> { - resizeContent(); - }); - } + getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | 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 + ); } private void resizeContentInternal() { @@ -312,7 +293,7 @@ private void resizeMeAction(Map query) { return; } try { - final DisplayMetrics metrics = UtilsDevice.getDisplayMetrics(this, false); + final DisplayMetrics metrics = UtilsDevice.getDisplayMetrics(this); float density = metrics.density; JSONObject resizeMeJson = (JSONObject) resizeMe; @@ -457,6 +438,7 @@ private WebView createWebView(TransparentActivityConfig config) { Countly.sharedInstance().moduleContent.notifyAfterContentIsClosed(); } } else { + hideSystemUI(); webView.setVisibility(View.VISIBLE); } } From c4e07f8d43791ca58fbbf0a0e3174c3fe1274e93 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Mon, 15 Sep 2025 16:04:38 +0300 Subject: [PATCH 09/13] feat: cutout warning --- sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java b/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java index 7602b0735..23b9d6f5c 100644 --- a/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java +++ b/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java @@ -79,6 +79,7 @@ protected void onCreate(Bundle savedInstanceState) { | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + // If this is disabled, UtilsDevice line 61 needs to be changed to subtract cutout always } getWindow().setAttributes(params); getWindow().setBackgroundDrawableResource(android.R.color.transparent); From 2fdc7088d74421634f7cf1f6cc7e842d9f0bdb12 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 16 Sep 2025 18:31:52 +0300 Subject: [PATCH 10/13] feat: notch calculation revert --- .../android/sdk/TransparentActivity.java | 11 +++++------ .../ly/count/android/sdk/UtilsDevice.java | 19 ++++++++++++------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java b/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java index 23b9d6f5c..0252f7ceb 100644 --- a/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java +++ b/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java @@ -6,7 +6,6 @@ import android.content.res.Configuration; import android.graphics.Color; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import android.util.DisplayMetrics; import android.util.Log; @@ -77,10 +76,10 @@ protected void onCreate(Bundle savedInstanceState) { params.width = config.width; params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; - // If this is disabled, UtilsDevice line 61 needs to be changed to subtract cutout always - } + //if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + // params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + // If this is disabled, UtilsDevice line 61 needs to be changed to subtract cutout always + //} getWindow().setAttributes(params); getWindow().setBackgroundDrawableResource(android.R.color.transparent); @@ -162,12 +161,12 @@ private void resizeContent() { @Override public void onDestroy() { - super.onDestroy(); close(new HashMap<>()); if (Countly.sharedInstance().isInitialized()) { Countly.sharedInstance().moduleContent.notifyAfterContentIsClosed(); } + super.onDestroy(); } private void hideSystemUI() { diff --git a/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java b/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java index c41dca888..ea9997b38 100644 --- a/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java +++ b/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java @@ -52,15 +52,20 @@ private static void applyWindowMetrics(@NonNull Context context, types |= WindowInsets.Type.statusBars(); } - boolean drawUnderCutout; - WindowManager.LayoutParams params = ((Activity) context).getWindow().getAttributes(); - drawUnderCutout = params.layoutInDisplayCutoutMode - == WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + //boolean drawUnderCutout; + //WindowManager.LayoutParams params = ((Activity) context).getWindow().getAttributes(); + //drawUnderCutout = params.layoutInDisplayCutoutMode + // == WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; // Only subtract display cutout insets when not allowed to draw under the cutout - if (!drawUnderCutout && windowInsets.isVisible(WindowInsets.Type.displayCutout())) { - types |= WindowInsets.Type.displayCutout(); - } + //if (!drawUnderCutout && windowInsets.isVisible(WindowInsets.Type.displayCutout())) { + // types |= WindowInsets.Type.displayCutout(); + //} Cutout is always respected as safe area for now even in fullscreen mode + } + + // Only subtract display cutout insets when not allowed to draw under the cutout + if (windowInsets.isVisible(WindowInsets.Type.displayCutout())) { + types |= WindowInsets.Type.displayCutout(); } final Insets insets = windowInsets.getInsets(types); From fdc09ae00d6eea145ed6c4f554139ff698595d05 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 18 Sep 2025 10:27:57 +0300 Subject: [PATCH 11/13] fix: pre-api 29 fix --- .../ly/count/android/sdk/TransparentActivity.java | 13 ++++++++----- .../main/java/ly/count/android/sdk/UtilsDevice.java | 8 ++++++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java b/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java index 0252f7ceb..b790f051f 100644 --- a/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java +++ b/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java @@ -6,6 +6,7 @@ import android.content.res.Configuration; import android.graphics.Color; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.util.DisplayMetrics; import android.util.Log; @@ -170,14 +171,16 @@ public void onDestroy() { } private void hideSystemUI() { - getWindow().getDecorView().setSystemUiVisibility( - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - | View.SYSTEM_UI_FLAG_LAYOUT_STABLE + int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE; + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { + flags = View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | 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_FULLSCREEN; + } + + getWindow().getDecorView().setSystemUiVisibility(flags); } private void resizeContentInternal() { diff --git a/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java b/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java index ea9997b38..f7e0035bc 100644 --- a/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java +++ b/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java @@ -13,7 +13,7 @@ import android.view.WindowMetrics; import androidx.annotation.NonNull; -class UtilsDevice { +final class UtilsDevice { private UtilsDevice() { } @@ -88,7 +88,11 @@ private static void applyWindowMetrics(@NonNull Context context, private static void applyLegacyMetrics(@NonNull WindowManager wm, @NonNull DisplayMetrics outMetrics) { final Display display = wm.getDefaultDisplay(); - display.getRealMetrics(outMetrics); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + display.getMetrics(outMetrics); + } else { + display.getRealMetrics(outMetrics); + } //getMetrics gives us size minus navigation bar } } From 4810ed4f89533beca2e49991dc4f107cfdd2983c Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 18 Sep 2025 11:45:25 +0300 Subject: [PATCH 12/13] feat: enable notch support --- .../android/sdk/TransparentActivity.java | 19 +++++------ .../ly/count/android/sdk/UtilsDevice.java | 32 ++++++++----------- 2 files changed, 22 insertions(+), 29 deletions(-) diff --git a/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java b/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java index b790f051f..c16f1c7e2 100644 --- a/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java +++ b/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java @@ -77,10 +77,10 @@ protected void onCreate(Bundle savedInstanceState) { params.width = config.width; params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; - //if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - // params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; - // If this is disabled, UtilsDevice line 61 needs to be changed to subtract cutout always - //} + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + // If this is disabled, UtilsDevice line 61 needs to be changed to subtract cutout always + } getWindow().setAttributes(params); getWindow().setBackgroundDrawableResource(android.R.color.transparent); @@ -171,16 +171,13 @@ public void onDestroy() { } private void hideSystemUI() { - int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE; - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { - flags = View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE | + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_FULLSCREEN; - } - - getWindow().getDecorView().setSystemUiVisibility(flags); + | View.SYSTEM_UI_FLAG_FULLSCREEN); } private void resizeContentInternal() { diff --git a/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java b/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java index f7e0035bc..09e9cd244 100644 --- a/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java +++ b/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java @@ -13,7 +13,7 @@ import android.view.WindowMetrics; import androidx.annotation.NonNull; -final class UtilsDevice { +class UtilsDevice { private UtilsDevice() { } @@ -52,20 +52,20 @@ private static void applyWindowMetrics(@NonNull Context context, types |= WindowInsets.Type.statusBars(); } - //boolean drawUnderCutout; - //WindowManager.LayoutParams params = ((Activity) context).getWindow().getAttributes(); - //drawUnderCutout = params.layoutInDisplayCutoutMode - // == WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + boolean drawUnderCutout; + WindowManager.LayoutParams params = ((Activity) context).getWindow().getAttributes(); + drawUnderCutout = params.layoutInDisplayCutoutMode + == WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; // Only subtract display cutout insets when not allowed to draw under the cutout - //if (!drawUnderCutout && windowInsets.isVisible(WindowInsets.Type.displayCutout())) { - // types |= WindowInsets.Type.displayCutout(); - //} Cutout is always respected as safe area for now even in fullscreen mode - } - - // Only subtract display cutout insets when not allowed to draw under the cutout - if (windowInsets.isVisible(WindowInsets.Type.displayCutout())) { - types |= WindowInsets.Type.displayCutout(); + if (!drawUnderCutout && windowInsets.isVisible(WindowInsets.Type.displayCutout())) { + types |= WindowInsets.Type.displayCutout(); + } + // Cutout is always respected as safe area for now even in fullscreen mode + // Only subtract display cutout insets when not allowed to draw under the cutout + if (windowInsets.isVisible(WindowInsets.Type.displayCutout())) { + types |= WindowInsets.Type.displayCutout(); + } } final Insets insets = windowInsets.getInsets(types); @@ -88,11 +88,7 @@ private static void applyWindowMetrics(@NonNull Context context, private static void applyLegacyMetrics(@NonNull WindowManager wm, @NonNull DisplayMetrics outMetrics) { final Display display = wm.getDefaultDisplay(); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - display.getMetrics(outMetrics); - } else { - display.getRealMetrics(outMetrics); - } + display.getRealMetrics(outMetrics); //getMetrics gives us size minus navigation bar } } From 780a398fdb16d8d54dd0ae03f8849b77213ded13 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 18 Sep 2025 12:24:49 +0300 Subject: [PATCH 13/13] feat: cutout area support --- .../android/sdk/ConnectionProcessorTests.java | 4 +++ .../ly/count/android/sdk/ConfigContent.java | 13 ++++++++ .../android/sdk/ConfigurationProvider.java | 2 ++ .../android/sdk/ModuleConfiguration.java | 4 +++ .../ly/count/android/sdk/ModuleContent.java | 1 + .../android/sdk/TransparentActivity.java | 5 ++- .../ly/count/android/sdk/UtilsDevice.java | 33 ++++++++++++++----- 7 files changed, 52 insertions(+), 10 deletions(-) diff --git a/sdk/src/androidTest/java/ly/count/android/sdk/ConnectionProcessorTests.java b/sdk/src/androidTest/java/ly/count/android/sdk/ConnectionProcessorTests.java index c9fe0621b..8a98efaa3 100644 --- a/sdk/src/androidTest/java/ly/count/android/sdk/ConnectionProcessorTests.java +++ b/sdk/src/androidTest/java/ly/count/android/sdk/ConnectionProcessorTests.java @@ -126,6 +126,10 @@ public void setUp() { @Override public int getBOMDuration() { return 60; } + + @Override public boolean getUseCutoutArea() { + return false; + } }; Countly.sharedInstance().setLoggingEnabled(true); diff --git a/sdk/src/main/java/ly/count/android/sdk/ConfigContent.java b/sdk/src/main/java/ly/count/android/sdk/ConfigContent.java index 870f0f3c1..9b258ba10 100644 --- a/sdk/src/main/java/ly/count/android/sdk/ConfigContent.java +++ b/sdk/src/main/java/ly/count/android/sdk/ConfigContent.java @@ -4,6 +4,7 @@ public class ConfigContent { int zoneTimerInterval = 30; ContentCallback globalContentCallback = null; + Boolean cutoutArea = false; /** * Set the interval for the automatic content update calls @@ -30,4 +31,16 @@ public synchronized ConfigContent setGlobalContentCallback(ContentCallback callb this.globalContentCallback = callback; return this; } + + /** + * Enable cutout area support for content + * When enabled, SDK will use cutout area to show content + * + * @return config content to chain calls + * @apiNote This is an EXPERIMENTAL feature, and it can have breaking changes + */ + public synchronized ConfigContent useCutoutArea() { + this.cutoutArea = true; + return this; + } } diff --git a/sdk/src/main/java/ly/count/android/sdk/ConfigurationProvider.java b/sdk/src/main/java/ly/count/android/sdk/ConfigurationProvider.java index bdb3ec50b..a390b1a4f 100644 --- a/sdk/src/main/java/ly/count/android/sdk/ConfigurationProvider.java +++ b/sdk/src/main/java/ly/count/android/sdk/ConfigurationProvider.java @@ -29,4 +29,6 @@ interface ConfigurationProvider { int getBOMRequestAge(); int getBOMDuration(); + + boolean getUseCutoutArea(); } diff --git a/sdk/src/main/java/ly/count/android/sdk/ModuleConfiguration.java b/sdk/src/main/java/ly/count/android/sdk/ModuleConfiguration.java index a3b2f137a..e6c7ecb59 100644 --- a/sdk/src/main/java/ly/count/android/sdk/ModuleConfiguration.java +++ b/sdk/src/main/java/ly/count/android/sdk/ModuleConfiguration.java @@ -494,4 +494,8 @@ public boolean getTrackingEnabled() { @Override public int getBOMDuration() { return currentVBOMDuration; } + + @Override public boolean getUseCutoutArea() { + return _cly.config_.content.cutoutArea; + } } diff --git a/sdk/src/main/java/ly/count/android/sdk/ModuleContent.java b/sdk/src/main/java/ly/count/android/sdk/ModuleContent.java index ddbcce994..838dfe474 100644 --- a/sdk/src/main/java/ly/count/android/sdk/ModuleContent.java +++ b/sdk/src/main/java/ly/count/android/sdk/ModuleContent.java @@ -94,6 +94,7 @@ void fetchContentsInternal(@NonNull String[] categories) { intent.putExtra(TransparentActivity.CONFIGURATION_LANDSCAPE, placementCoordinates.get(Configuration.ORIENTATION_LANDSCAPE)); intent.putExtra(TransparentActivity.CONFIGURATION_PORTRAIT, placementCoordinates.get(Configuration.ORIENTATION_PORTRAIT)); intent.putExtra(TransparentActivity.ORIENTATION, _cly.context_.getResources().getConfiguration().orientation); + intent.putExtra(TransparentActivity.USE_CUTOUT, configProvider.getUseCutoutArea()); Long id = System.currentTimeMillis(); intent.putExtra(TransparentActivity.ID_CALLBACK, id); diff --git a/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java b/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java index c16f1c7e2..386dbe09d 100644 --- a/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java +++ b/sdk/src/main/java/ly/count/android/sdk/TransparentActivity.java @@ -32,6 +32,7 @@ public class TransparentActivity extends Activity { static final String ORIENTATION = "orientation"; static final String WIDGET_INFO = "widget_info"; static final String ID_CALLBACK = "id_callback"; + static final String USE_CUTOUT = "use_cutout"; int currentOrientation = 0; long ID = -1; TransparentActivityConfig configLandscape = null; @@ -77,7 +78,9 @@ protected void onCreate(Bundle savedInstanceState) { params.width = config.width; params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + + boolean useCutoutArea = intent.getBooleanExtra(USE_CUTOUT, false); + if (useCutoutArea && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; // If this is disabled, UtilsDevice line 61 needs to be changed to subtract cutout always } diff --git a/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java b/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java index 09e9cd244..3c18a47af 100644 --- a/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java +++ b/sdk/src/main/java/ly/count/android/sdk/UtilsDevice.java @@ -13,7 +13,7 @@ import android.view.WindowMetrics; import androidx.annotation.NonNull; -class UtilsDevice { +final class UtilsDevice { private UtilsDevice() { } @@ -37,6 +37,12 @@ private static void applyWindowMetrics(@NonNull Context context, final WindowMetrics windowMetrics = wm.getCurrentWindowMetrics(); final WindowInsets windowInsets = windowMetrics.getWindowInsets(); + boolean useCutoutArea = false; + + if (Countly.sharedInstance().isInitialized()) { + useCutoutArea = Countly.sharedInstance().config_.configProvider.getUseCutoutArea(); + } + // Always respect status bar & cutout (they affect safe area even in fullscreen) int types = 0; boolean usePhysicalScreenSize = !(context instanceof Activity); @@ -52,17 +58,26 @@ private static void applyWindowMetrics(@NonNull Context context, types |= WindowInsets.Type.statusBars(); } - boolean drawUnderCutout; - WindowManager.LayoutParams params = ((Activity) context).getWindow().getAttributes(); - drawUnderCutout = params.layoutInDisplayCutoutMode - == WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + if (useCutoutArea) { + boolean drawUnderCutout; + WindowManager.LayoutParams params = ((Activity) context).getWindow().getAttributes(); + drawUnderCutout = params.layoutInDisplayCutoutMode + == WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; - // Only subtract display cutout insets when not allowed to draw under the cutout - if (!drawUnderCutout && windowInsets.isVisible(WindowInsets.Type.displayCutout())) { - types |= WindowInsets.Type.displayCutout(); + // Only subtract display cutout insets when not allowed to draw under the cutout + if (!drawUnderCutout && windowInsets.isVisible(WindowInsets.Type.displayCutout())) { + types |= WindowInsets.Type.displayCutout(); + } + + // Only subtract display cutout insets when not allowed to draw under the cutout + if (windowInsets.isVisible(WindowInsets.Type.displayCutout())) { + types |= WindowInsets.Type.displayCutout(); + } } + } + + if (!useCutoutArea) { // Cutout is always respected as safe area for now even in fullscreen mode - // Only subtract display cutout insets when not allowed to draw under the cutout if (windowInsets.isVisible(WindowInsets.Type.displayCutout())) { types |= WindowInsets.Type.displayCutout(); }