From 17f6e8f9bdd3c4b49f903ce809bdf3bc8cd2a90d Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 16 Dec 2025 10:38:21 +0300 Subject: [PATCH 1/7] feat: config option to disable restart --- .../java/ly/count/android/sdk/CountlyConfig.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/sdk/src/main/java/ly/count/android/sdk/CountlyConfig.java b/sdk/src/main/java/ly/count/android/sdk/CountlyConfig.java index 1ca2b10ca..3b2131826 100644 --- a/sdk/src/main/java/ly/count/android/sdk/CountlyConfig.java +++ b/sdk/src/main/java/ly/count/android/sdk/CountlyConfig.java @@ -216,6 +216,9 @@ public class CountlyConfig { // If set to true, the SDK will not store the default push consent state on initialization for not requiring consent boolean disableStoringDefaultPushConsent = false; + // If set to true, the SDK will not restart manual views while switching between foreground and background + boolean disableViewRestartForManualRecording = false; + /** * THIS VARIABLE SHOULD NOT BE USED * IT IS ONLY FOR INTERNAL TESTING @@ -1116,6 +1119,19 @@ public synchronized CountlyConfig disableStoringDefaultPushConsent() { return this; } + /** + * Disable view restart when manual view recording is done. + * By default, if automatic view tracking is not enabled and a manual view is recorded, + * the SDK was restarting the views to properly track the view duration in bg/fg transitions. + * Now, with this option enabled, the SDK will not restart the views on manual view recording. + * + * @return Returns the same config object for convenient linking + */ + public synchronized CountlyConfig disableViewRestartForManualRecording() { + this.disableViewRestartForManualRecording = true; + return this; + } + /** * APM configuration interface to be used with CountlyConfig */ From 4862c839bf0acb53d70679e0459e8fd3bfeb92ff Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 16 Dec 2025 10:38:40 +0300 Subject: [PATCH 2/7] feat: usage of the option --- sdk/src/main/java/ly/count/android/sdk/ModuleViews.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sdk/src/main/java/ly/count/android/sdk/ModuleViews.java b/sdk/src/main/java/ly/count/android/sdk/ModuleViews.java index 05da30220..69d3d9b40 100644 --- a/sdk/src/main/java/ly/count/android/sdk/ModuleViews.java +++ b/sdk/src/main/java/ly/count/android/sdk/ModuleViews.java @@ -20,6 +20,7 @@ public class ModuleViews extends ModuleBase implements ViewIdProvider { String currentViewName = ""; private boolean firstView = true; boolean autoViewTracker = false; + boolean restartManualViews = true; boolean automaticTrackingShouldUseShortName = false; //track orientation changes @@ -84,6 +85,7 @@ static class ViewData { setGlobalViewSegmentationInternal(config.globalViewSegmentation); autoTrackingActivityExceptions = config.automaticViewTrackingExceptions; trackOrientationChanges = config.trackOrientationChange; + restartManualViews = !config.disableViewRestartForManualRecording; viewsInterface = new Views(); } @@ -552,7 +554,7 @@ void onActivityStopped(int updatedActivityCount) { } } - if (updatedActivityCount <= 0) { + if (updatedActivityCount <= 0 && (autoViewTracker || restartManualViews)) { //if we go to the background, stop all running views stopRunningViewsAndSend(); } @@ -587,8 +589,7 @@ void onActivityStarted(Activity activity, int updatedActivityCount) { } } - if (updatedActivityCount == 1) { - //if we go to the background, stop all running views + if (updatedActivityCount == 1 && (autoViewTracker || restartManualViews)) { startStoppedViews(); } } From 157fe115bb4d256cd0a5e0d7f9289bd544665fac Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 16 Dec 2025 11:25:47 +0300 Subject: [PATCH 3/7] feat: test cases for it --- .../count/android/sdk/ModuleViewsTests.java | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/sdk/src/androidTest/java/ly/count/android/sdk/ModuleViewsTests.java b/sdk/src/androidTest/java/ly/count/android/sdk/ModuleViewsTests.java index cd9158f66..5eda400cc 100644 --- a/sdk/src/androidTest/java/ly/count/android/sdk/ModuleViewsTests.java +++ b/sdk/src/androidTest/java/ly/count/android/sdk/ModuleViewsTests.java @@ -2062,6 +2062,92 @@ public void autoViewTracking_consentRemoval() throws JSONException { Assert.assertEquals(9, TestUtils.getCurrentRQ().length); } + /** + * "startView" with bg/fg switch case + * - Validate that after an auto stopped view is started and app gone to background + * running view should not stop because behavior is disabled + * - After coming from the background to foreground no view should be started because behavior is disabled + * + * @throws InterruptedException if the thread is interrupted + * @throws JSONException if the JSON is not valid + */ + @Test + public void startView_restartAfterActivityComesFromForeground_behaviorDisabled() throws InterruptedException, JSONException { + CountlyConfig countlyConfig = TestUtils.createScenarioEventIDConfig(TestUtils.incrementalViewIdGenerator(), TestUtils.incrementalEventIdGenerator()); + countlyConfig.setApplication(null); + countlyConfig.setContext(TestUtils.getContext()); + countlyConfig.disableViewRestartForManualRecording(); + countlyConfig.setEventQueueSizeToSend(1); + Countly countly = new Countly().init(countlyConfig); + + Activity activity = mock(Activity.class); + + TestUtils.assertRQSize(0); + + countly.onStart(activity); + countly.views().startView("test"); + + ModuleSessionsTests.validateSessionBeginRequest(0, TestUtils.commonDeviceId); + ModuleEventsTests.validateEventInRQ(TestUtils.commonDeviceId, ModuleViews.ORIENTATION_EVENT_KEY, null, 1, 0.0, 0.0, "ide1", "_CLY_", "", "_CLY_", 1, 3, 0, 1); + validateView("test", 0.0, 2, 3, true, true, null, "idv1", ""); + + Thread.sleep(1000); + + countly.onStop(); + ModuleSessionsTests.validateSessionEndRequest(3, 1, TestUtils.commonDeviceId); + Thread.sleep(1000); + countly.onStart(activity); + + ModuleSessionsTests.validateSessionBeginRequest(4, TestUtils.commonDeviceId); + ModuleEventsTests.validateEventInRQ(TestUtils.commonDeviceId, ModuleViews.ORIENTATION_EVENT_KEY, null, 1, 0.0, 0.0, "ide2", "_CLY_", "idv1", "_CLY_", 5, 6, 0, 1); + + Thread.sleep(1000); + + countly.views().stopViewWithName("test"); + validateView("test", 3.0, 6, 7, false, false, null, "idv1", ""); + countly.onStop(); + + ModuleSessionsTests.validateSessionEndRequest(7, 1, TestUtils.commonDeviceId); + TestUtils.assertRQSize(8); + } + + /** + * Auto view tracking with restart is disabled for manual views + * Validate that after an auto stopped view is started and app gone to background + * running view should stop because auto views are not affected by the disabled behavior + * + * @throws JSONException if the JSON is not valid + */ + @Test + public void autoViewTracking_restartDisabledForManualViews() throws JSONException, InterruptedException { + CountlyConfig countlyConfig = TestUtils.createBaseConfig(TestUtils.getContext()); + countlyConfig.setLoggingEnabled(true); + countlyConfig.enableAutomaticViewTracking(); + countlyConfig.disableViewRestartForManualRecording(); + countlyConfig.setEventQueueSizeToSend(1); + + Countly countly = new Countly().init(countlyConfig); + + Activity activity = Mockito.mock(Activity.class); + countly.onStart(activity); + + ModuleSessionsTests.validateSessionBeginRequest(0, TestUtils.commonDeviceId); + ModuleEventsTests.validateEventInRQ("[CLY]_orientation", TestUtils.map("mode", "portrait"), 1, 0, 0, 1, 3); + validateView(activity.getClass().getName(), 0.0, 2, 3, true, true, TestUtils.map(), "_CLY_", "_CLY_", null); + Thread.sleep(1000); + countly.onStop(); + ModuleSessionsTests.validateSessionEndRequest(3, 1, TestUtils.commonDeviceId); + validateView(activity.getClass().getName(), 1.0, 4, 5, false, false, null, "_CLY_", "_CLY_"); + + countly.onStart(activity); + Thread.sleep(1000); + ModuleSessionsTests.validateSessionBeginRequest(5, TestUtils.commonDeviceId); + ModuleEventsTests.validateEventInRQ("[CLY]_orientation", TestUtils.map("mode", "portrait"), 1, 0, 0, 6, 8); + + validateView(activity.getClass().getName(), 0.0, 7, 8, true, true, TestUtils.map(), "_CLY_", "_CLY_", null); + Assert.assertEquals(8, TestUtils.getCurrentRQ().length); + } + static void validateView(String viewName, Double viewDuration, int idx, int size, boolean start, boolean visit, Map customSegmentation, String id, String pvid) throws JSONException { validateView(viewName, viewDuration, idx, size, start, visit, customSegmentation, id, pvid, null); } From 5c3feeeb473b1dd25fef00393e3c953ddb39f4e6 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 16 Dec 2025 11:28:24 +0300 Subject: [PATCH 4/7] feat: changelog for it --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1af367ea2..b885679b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 25.4.9 +* Added a new config option `disableViewRestartForManualRecording()` to change restart behavior of manual views in background/foreground switches. When enabled, they restarted automatically to measure view duration better. + ## 25.4.8 * Mitigated an issue where push notifications were not shown when consent was not required and app was killed. From f1bd6338b5250b7f27da692188b10ac67d6b22ff Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 16 Dec 2025 11:29:40 +0300 Subject: [PATCH 5/7] fix: proposition on changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b885679b2..daa16a4f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ ## 25.4.9 -* Added a new config option `disableViewRestartForManualRecording()` to change restart behavior of manual views in background/foreground switches. When enabled, they restarted automatically to measure view duration better. +* Added a new config option `disableViewRestartForManualRecording()` to change restart behavior of manual views on background/foreground switches. When enabled, they restarted automatically to measure view duration better. ## 25.4.8 * Mitigated an issue where push notifications were not shown when consent was not required and app was killed. From 3b2e739a10ba8c3955db464965ac13bfab6fb883 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 16 Dec 2025 11:31:15 +0300 Subject: [PATCH 6/7] feat: 24.5.9 --- gradle.properties | 2 +- sdk/src/androidTest/java/ly/count/android/sdk/TestUtils.java | 2 +- sdk/src/main/java/ly/count/android/sdk/Countly.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 56f0bc5d8..ebc66a0eb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -22,7 +22,7 @@ org.gradle.configureondemand=true android.useAndroidX=true android.enableJetifier=true # RELEASE FIELD SECTION -VERSION_NAME=25.4.8 +VERSION_NAME=25.4.9 GROUP=ly.count.android POM_URL=https://github.com/Countly/countly-sdk-android POM_SCM_URL=https://github.com/Countly/countly-sdk-android diff --git a/sdk/src/androidTest/java/ly/count/android/sdk/TestUtils.java b/sdk/src/androidTest/java/ly/count/android/sdk/TestUtils.java index e3de884af..97aae656c 100644 --- a/sdk/src/androidTest/java/ly/count/android/sdk/TestUtils.java +++ b/sdk/src/androidTest/java/ly/count/android/sdk/TestUtils.java @@ -44,7 +44,7 @@ public class TestUtils { public final static String commonAppKey = "appkey"; public final static String commonDeviceId = "1234"; public final static String SDK_NAME = "java-native-android"; - public final static String SDK_VERSION = "25.4.8"; + public final static String SDK_VERSION = "25.4.9"; public static final int MAX_THREAD_COUNT_PER_STACK_TRACE = 50; public static class Activity2 extends Activity { diff --git a/sdk/src/main/java/ly/count/android/sdk/Countly.java b/sdk/src/main/java/ly/count/android/sdk/Countly.java index eeb3859c8..61c7bba70 100644 --- a/sdk/src/main/java/ly/count/android/sdk/Countly.java +++ b/sdk/src/main/java/ly/count/android/sdk/Countly.java @@ -47,7 +47,7 @@ of this software and associated documentation files (the "Software"), to deal */ public class Countly { - private final String DEFAULT_COUNTLY_SDK_VERSION_STRING = "25.4.8"; + private final String DEFAULT_COUNTLY_SDK_VERSION_STRING = "25.4.9"; /** * Used as request meta data on every request */ From 482e6a30f19009c4888ecbe4167924891ed98973 Mon Sep 17 00:00:00 2001 From: turtledreams <62231246+turtledreams@users.noreply.github.com> Date: Tue, 16 Dec 2025 17:53:25 +0900 Subject: [PATCH 7/7] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index daa16a4f6..7c1aaaf62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ ## 25.4.9 -* Added a new config option `disableViewRestartForManualRecording()` to change restart behavior of manual views on background/foreground switches. When enabled, they restarted automatically to measure view duration better. +* Added a new config option `disableViewRestartForManualRecording()` to disable auto close/restart behavior of manual views on app background/foreground actions. ## 25.4.8 * Mitigated an issue where push notifications were not shown when consent was not required and app was killed.