diff --git a/CHANGELOG.md b/CHANGELOG.md index 1af367ea2..7c1aaaf62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 25.4.9 +* 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. 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/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); } 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 */ 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 */ 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(); } }