From 1c3ff18e6453c92e1a899c8268cae1b993486ba1 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 8 Aug 2024 14:40:47 +0300 Subject: [PATCH 01/11] feat: stop views on bg and start them again --- .../ly/count/android/sdk/ModuleViews.java | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 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 8f7f7db96..6615572dc 100644 --- a/sdk/src/main/java/ly/count/android/sdk/ModuleViews.java +++ b/sdk/src/main/java/ly/count/android/sdk/ModuleViews.java @@ -7,6 +7,7 @@ import androidx.annotation.Nullable; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -51,8 +52,8 @@ static class ViewData { long viewStartTimeSeconds; // if this is 0 then the view is not started yet or was paused String viewName; boolean isAutoStoppedView = false;//views started with "startAutoStoppedView" would have this as "true". If set to "true" views should be automatically closed when another one is started. - boolean isAutoPaused = false;//this marks that this view automatically paused when going to the background Map viewSegmentation = null; // segmentation that can be updated while a view is on + boolean willStartAgain = false; // if this is true, the view will be started again when the app comes back to the foreground } //interface for SDK users @@ -271,7 +272,9 @@ void stopViewWithIDInternal(@Nullable String viewID, @Nullable Map customViewSegmentation, String viewRecordingSource) { @@ -304,7 +307,7 @@ void recordViewEndEvent(ViewData vd, @Nullable Map customViewSeg eventProvider.recordEventInternal(VIEW_EVENT_KEY, segments, 1, 0, viewDurationSeconds, null, vd.viewID); } - void pauseViewWithIDInternal(String viewID, boolean pausedAutomatically) { + void pauseViewWithIDInternal(String viewID) { if (viewID == null || viewID.isEmpty()) { L.e("[ModuleViews] pauseViewWithIDInternal, Trying to record view with null or empty view ID, ignoring request"); return; @@ -317,7 +320,7 @@ void pauseViewWithIDInternal(String viewID, boolean pausedAutomatically) { ViewData vd = viewDataMap.get(viewID); if (vd == null) { - L.e("[ModuleViews] pauseViewWithIDInternal, view id:[" + viewID + "] has a 'null' value. This should not be happening, auto paused:[" + pausedAutomatically + "]"); + L.e("[ModuleViews] pauseViewWithIDInternal, view id:[" + viewID + "] has a 'null' value. This should not be happening"); return; } @@ -332,8 +335,6 @@ void pauseViewWithIDInternal(String viewID, boolean pausedAutomatically) { return; } - vd.isAutoPaused = pausedAutomatically; - recordViewEndEvent(vd, null, "pauseViewWithIDInternal"); vd.viewStartTimeSeconds = 0; @@ -368,7 +369,6 @@ void resumeViewWithIDInternal(String viewID) { } vd.viewStartTimeSeconds = UtilsTime.currentTimestampSeconds(); - vd.isAutoPaused = false; } public void addSegmentationToViewWithIDInternal(@Nullable String viewID, @Nullable Map viewSegmentation) { @@ -467,26 +467,31 @@ void updateOrientation(int newOrientation) { updateOrientation(newOrientation, false); } - void pauseRunningViewsAndSend() { - L.d("[ModuleViews] pauseRunningViewsAndSend, going to the background and pausing"); + void stopRunningViewsAndSend() { + L.d("[ModuleViews] stopRunningViewsAndSend, going to the background and pausing"); for (Map.Entry entry : viewDataMap.entrySet()) { ViewData vd = entry.getValue(); + vd.willStartAgain = true; if (vd.viewStartTimeSeconds > 0) { //if the view is running - pauseViewWithIDInternal(vd.viewID, true); + stopViewWithIDInternal(vd.viewID, null); } } } - void resumeAutoPausedViews() { - L.d("[ModuleViews] resumeAutoPausedViews, going to the foreground and resuming"); - for (Map.Entry entry : viewDataMap.entrySet()) { - ViewData vd = entry.getValue(); - - if (vd.isAutoPaused) { - //if the view was automatically paused, resume it - resumeViewWithIDInternal(vd.viewID); + void startAutoStoppedViews() { + L.d("[ModuleViews] startAutoStoppedViews, app is coming back to the foreground, starting views that were paused"); + + Iterator> iterator = viewDataMap.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry value = iterator.next(); + iterator.remove(); + ViewData vd = value.getValue(); + if (vd.willStartAgain) { + //if the view is paused + iterator.remove(); + startViewInternal(vd.viewName, vd.viewSegmentation, vd.isAutoStoppedView); } } } @@ -513,8 +518,8 @@ void onActivityStopped(int updatedActivityCount) { } if (updatedActivityCount <= 0) { - //if we go to the background, pause all running views - pauseRunningViewsAndSend(); + //if we go to the background, stop all running views + stopRunningViewsAndSend(); } } @@ -549,7 +554,7 @@ void onActivityStarted(Activity activity, int updatedActivityCount) { if (updatedActivityCount == 1) { //if we go to the background, pause all running views - resumeAutoPausedViews(); + startAutoStoppedViews(); } } @@ -829,7 +834,7 @@ public void pauseViewWithID(@Nullable String viewID) { synchronized (_cly) { L.i("[Views] Calling pauseViewWithID vi[" + viewID + "]"); - pauseViewWithIDInternal(viewID, false); + pauseViewWithIDInternal(viewID); } } From a084a2da2b3f86c245864a08b0889e2818761a37 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 13 Aug 2024 15:35:33 +0300 Subject: [PATCH 02/11] fix: views testing --- .../ly/count/android/sdk/DeviceIdTests.java | 2 +- .../count/android/sdk/ModuleEventsTests.java | 84 ++++++-- .../android/sdk/ModuleUserProfileTests.java | 3 +- .../count/android/sdk/ModuleViewsTests.java | 181 +++++++----------- .../java/ly/count/android/sdk/TestUtils.java | 24 +++ .../android/sdk/scUP_UserProfileTests.java | 4 +- .../ly/count/android/sdk/ModuleViews.java | 9 +- 7 files changed, 176 insertions(+), 131 deletions(-) diff --git a/sdk/src/androidTest/java/ly/count/android/sdk/DeviceIdTests.java b/sdk/src/androidTest/java/ly/count/android/sdk/DeviceIdTests.java index 4c83743f5..4e8f01980 100644 --- a/sdk/src/androidTest/java/ly/count/android/sdk/DeviceIdTests.java +++ b/sdk/src/androidTest/java/ly/count/android/sdk/DeviceIdTests.java @@ -308,7 +308,7 @@ public void sessionDurationScenario_1() throws InterruptedException, JSONExcepti assertEquals(6, TestUtils.getCurrentRQ().length); // not 5 anymore, it will send orientation event as well TestUtils.validateRequest("ff_merge", TestUtils.map("old_device_id", "1234"), 1); - ModuleEventsTests.validateEventInRQ("ff_merge", "[CLY]_orientation", 1, 0.0d, 0.0d, 2, 0, 1, -1); + ModuleEventsTests.validateEventInRQ("ff_merge", "[CLY]_orientation", null, 1, 0.0d, 0.0d, "_CLY_", "_CLY_", "_CLY_", "_CLY_", 2, -1, 0, 1); TestUtils.validateRequest("ff_merge", TestUtils.map("user_details", "{\"custom\":{\"prop2\":123,\"prop1\":\"string\",\"prop3\":false}}"), 3); ModuleSessionsTests.validateSessionEndRequest(4, 3, "ff_merge"); diff --git a/sdk/src/androidTest/java/ly/count/android/sdk/ModuleEventsTests.java b/sdk/src/androidTest/java/ly/count/android/sdk/ModuleEventsTests.java index 04d24097b..a58f64c6b 100644 --- a/sdk/src/androidTest/java/ly/count/android/sdk/ModuleEventsTests.java +++ b/sdk/src/androidTest/java/ly/count/android/sdk/ModuleEventsTests.java @@ -6,6 +6,8 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import ly.count.android.sdk.messaging.ModulePush; import org.json.JSONArray; import org.json.JSONException; @@ -773,12 +775,23 @@ public void recordEvent_unsupportedDataTypesSegmentation() throws JSONException validateEventInRQ("key", TestUtils.map(), 1, 1.0d, 1.0d, 0); } - protected static JSONObject validateEventInRQ(String deviceId, String eventName, int count, double sum, double duration, int idx, int eventIdx, int eventCount, int rqCount) throws JSONException { + protected static void validateEventInRQ(String eventName, Map expectedSegmentation, int count, double sum, double duration, int idx) throws JSONException { + validateEventInRQ(eventName, expectedSegmentation, count, sum, duration, idx, idx + 1); + } + + protected static void validateEventInRQ(String eventName, Map expectedSegmentation, int count, double sum, double duration, int idx, int rqCount) throws JSONException { + validateEventInRQ(TestUtils.commonDeviceId, eventName, expectedSegmentation, count, sum, duration, "_CLY_", "_CLY_", "_CLY_", "_CLY_", idx, rqCount, 0, 1); + } + + protected static void validateEventInRQ(String deviceId, String eventName, Map expectedSegmentation, int count, Double sum, Double duration, String id, String pvid, String cvid, String peid, int idx, int rqCount, int eventIdx, int eventCount) throws JSONException { Map[] RQ = TestUtils.getCurrentRQ(); if (rqCount > -1) { Assert.assertEquals(rqCount, RQ.length); } TestUtils.validateRequiredParams(RQ[idx], deviceId); + if (!RQ[idx].containsKey("events")) { + Assert.fail("Not an event request idx:[" + idx + "], request:[" + RQ[idx] + "]"); + } JSONArray events = new JSONArray(RQ[idx].get("events")); Assert.assertEquals(eventCount, events.length()); JSONObject event = events.getJSONObject(eventIdx); @@ -786,21 +799,64 @@ protected static JSONObject validateEventInRQ(String deviceId, String eventName, Assert.assertEquals(count, event.getInt("count")); Assert.assertEquals(sum, event.optDouble("sum", 0.0d), 0.0001); Assert.assertEquals(duration, event.optDouble("dur", 0.0d), 0.0001); - return event; - } - - protected static void validateEventInRQ(String eventName, Map expectedSegmentation, int count, double sum, double duration, int idx) throws JSONException { - JSONObject event = validateEventInRQ(TestUtils.commonDeviceId, eventName, count, sum, duration, idx, 0, 1, idx + 1); - if (!expectedSegmentation.isEmpty()) { + if (expectedSegmentation != null && !expectedSegmentation.isEmpty()) { JSONObject segmentation = event.getJSONObject("segmentation"); - Assert.assertEquals(expectedSegmentation.size(), segmentation.length()); + Assert.assertEquals("Expected segmentation: " + expectedSegmentation + ", got: " + segmentation, expectedSegmentation.size(), segmentation.length()); for (Map.Entry entry : expectedSegmentation.entrySet()) { Assert.assertEquals(entry.getValue(), segmentation.get(entry.getKey())); } } - Assert.assertEquals(count, event.getInt("count")); - Assert.assertEquals(sum, event.optDouble("sum", 0.0d), 0.0001); - Assert.assertEquals(duration, event.optDouble("dur", 0.0d), 0.0001); + + int dow = event.getInt("dow"); + int hour = event.getInt("hour"); + long timestamp = event.getLong("timestamp"); + Assert.assertTrue(dow >= 0 && dow < 7); + Assert.assertTrue(hour >= 0 && hour < 24); + Assert.assertTrue(timestamp >= 0); + + System.out.println("Event: " + event.toString()); + + validateId(id, event.optString("id", ""), "Event ID"); + validateId(pvid, event.optString("pvid", ""), "Previous View ID"); + validateId(cvid, event.optString("cvid", ""), "Current View ID"); + validateId(peid, event.optString("peid", ""), "Previous Event ID"); + } + + private static void validateId(String id, String gonnaValidate, String name) { + if (id != null && id.equals("_CLY_")) { + if (gonnaValidate != null && !gonnaValidate.isEmpty()) { + validateSafeRandomVal(gonnaValidate); + } + } else { + Assert.assertEquals(name + " is not validated", id, gonnaValidate); + } + } + + /** + * Validates a random generated safe value, + * Value length should be 21 + * Value should contain a timestamp at the end + * Value should be base64 encoded and first 8 should be it + * + * @param val + */ + static void validateSafeRandomVal(String val) { + Assert.assertEquals(val, 21, val.length()); + + Pattern base64Pattern = Pattern.compile("^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$"); + + String timestampStr = val.substring(val.length() - 13); + String base64Str = val.substring(0, val.length() - 13); + + Matcher matcher = base64Pattern.matcher(base64Str); + if (matcher.matches()) { + UtilsTime.Instant instant = UtilsTime.Instant.get(Long.parseLong(timestampStr)); + Assert.assertTrue(instant.dow >= 0 && instant.dow < 7); + Assert.assertTrue(instant.hour >= 0 && instant.hour < 24); + Assert.assertTrue(instant.timestampMs > 0); + } else { + Assert.fail("No match for " + val); + } } protected static void validateEventInRQ(String eventName, Map expectedSegmentation, int idx) throws JSONException { @@ -808,7 +864,11 @@ protected static void validateEventInRQ(String eventName, Map ex } protected static void validateEventInRQ(String eventName, int rqIdx, int eventIdx, int eventCount) throws JSONException { - validateEventInRQ(TestUtils.commonDeviceId, eventName, 1, 0.0d, 0.0d, rqIdx, eventIdx, eventCount, -1); + validateEventInRQ(TestUtils.commonDeviceId, eventName, null, 1, 0.0d, 0.0d, "_CLY_", "_CLY_", "_CLY_", "_CLY_", rqIdx, -1, eventIdx, eventCount); + } + + protected static void validateEventInRQ(String deviceId, String eventName, int rqIdx, int eventIdx, int eventCount) throws JSONException { + validateEventInRQ(deviceId, eventName, null, 1, 0.0d, 0.0d, "_CLY_", "_CLY_", "_CLY_", "_CLY_", rqIdx, -1, eventIdx, eventCount); } /* diff --git a/sdk/src/androidTest/java/ly/count/android/sdk/ModuleUserProfileTests.java b/sdk/src/androidTest/java/ly/count/android/sdk/ModuleUserProfileTests.java index 003d26f20..1d2ee9619 100644 --- a/sdk/src/androidTest/java/ly/count/android/sdk/ModuleUserProfileTests.java +++ b/sdk/src/androidTest/java/ly/count/android/sdk/ModuleUserProfileTests.java @@ -598,8 +598,7 @@ public void internalLimit_setProperties_maxValueSizePicture() throws JSONExcepti Countly.sharedInstance().userProfile().setProperties(TestUtils.map(ModuleUserProfile.PICTURE_KEY, picture)); Countly.sharedInstance().userProfile().save(); - validateUserProfileRequest(TestUtils.map(ModuleUserProfile.PICTURE_KEY, picture.substring(0, 4096)), TestUtils.map() - ); + validateUserProfileRequest(TestUtils.map(ModuleUserProfile.PICTURE_KEY, picture.substring(0, 4096)), TestUtils.map()); } /** 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 ac8f4b834..bd6bf7a5e 100644 --- a/sdk/src/androidTest/java/ly/count/android/sdk/ModuleViewsTests.java +++ b/sdk/src/androidTest/java/ly/count/android/sdk/ModuleViewsTests.java @@ -258,7 +258,7 @@ public void onActivityStopped() { } @Test - public void onActivityStartedStopped() throws InterruptedException { + public void onActivityStartedStopped() throws InterruptedException, JSONException { Map globalSegm = new HashMap<>(); globalSegm.put("aa", "11"); globalSegm.put("aagfg", "1133"); @@ -267,30 +267,16 @@ public void onActivityStartedStopped() throws InterruptedException { globalSegm.put("3", true); @NonNull CountlyConfig cc = TestUtils.createViewCountlyConfig(false, true, true, safeViewIDGenerator, globalSegm); + cc.setEventQueueSizeToSend(1); Countly mCountly = new Countly().init(cc); - - @NonNull EventProvider ep = TestUtils.setEventProviderToMock(mCountly, mock(EventProvider.class)); - @NonNull Activity act = mock(Activity.class); - int start = UtilsTime.currentTimestampSeconds(); mCountly.moduleViews.onActivityStarted(act, 1);//activity count = 1 - Thread.sleep(100); + Thread.sleep(1000); mCountly.moduleViews.onActivityStopped(0);//activity count = 0 - double viewDuration = UtilsTime.currentTimestampSeconds() - start; - final Map segm = new HashMap<>(); - ClearFillSegmentationViewStart(segm, act.getClass().getSimpleName(), true, globalSegm); - segm.put("aa", "11"); - segm.put("aagfg", "1133"); - segm.put("1", 123); - segm.put("2", 234.0d); - segm.put("3", true); - - TestUtils.validateRecordEventInternalMock(ep, ModuleViews.VIEW_EVENT_KEY, segm, vals[0], 0, 2); - - ClearFillSegmentationViewEnd(segm, act.getClass().getSimpleName(), globalSegm); - TestUtils.validateRecordEventInternalMock(ep, ModuleViews.VIEW_EVENT_KEY, viewDuration, segm, vals[0], 1, 2); + validateView(act.getClass().getSimpleName(), 0.0, 0, 2, true, true, TestUtils.map("aa", "11", "aagfg", "1133", "1", 123, "2", 234, "3", true), "idv1", ""); + validateView(act.getClass().getSimpleName(), 1.0, 1, 2, false, false, TestUtils.map("aa", "11", "aagfg", "1133", "1", 123, "2", 234, "3", true), "idv1", ""); } /** @@ -589,67 +575,61 @@ public void recordViewWithActivitiesAfterwardsAutoDisabled() { * @throws InterruptedException */ @Test - public void autoSessionFlow_1() throws InterruptedException { + public void autoSessionFlow_1() throws InterruptedException, JSONException { @NonNull CountlyConfig cc = TestUtils.createViewCountlyConfig(false, true, true, safeViewIDGenerator, null); + cc.setEventQueueSizeToSend(1); Countly mCountly = new Countly().init(cc); - @NonNull EventProvider ep; - - ep = TestUtils.setEventProviderToMock(mCountly, mock(EventProvider.class)); @NonNull Activity act = mock(Activity.class); @NonNull Activity act2 = mock(TestUtils.Activity2.class); @NonNull Activity act3 = mock(TestUtils.Activity3.class); - String viewNames[] = { act.getClass().getSimpleName(), act2.getClass().getSimpleName(), act3.getClass().getSimpleName() }; + String[] viewNames = { act.getClass().getSimpleName(), act2.getClass().getSimpleName(), act3.getClass().getSimpleName() }; final Map segm = new HashMap<>(); + TestUtils.getCountyStore().clear(); + TestUtils.assertRQSize(0); //go from one activity to another in the expected way and then "go to background" ///////// 1 TestUtils.verifyCurrentPreviousViewID(mCountly.moduleViews, "", ""); mCountly.onStartInternal(act); TestUtils.verifyCurrentPreviousViewID(mCountly.moduleViews, vals[0], ""); + ModuleSessionsTests.validateSessionBeginRequest(0, TestUtils.commonDeviceId); + // there should be the first view start - ClearFillSegmentationViewStart(segm, viewNames[0], true); - TestUtils.validateRecordEventInternalMock(ep, ModuleViews.VIEW_EVENT_KEY, segm, vals[0], 0, 1); - clearInvocations(ep); + validateView(viewNames[0], 0.0, 1, 2, true, true, null, "idv1", ""); ///////// 2 Thread.sleep(1000); mCountly.onStartInternal(act2); TestUtils.verifyCurrentPreviousViewID(mCountly.moduleViews, vals[1], vals[0]); - mCountly.onStopInternal(); - TestUtils.verifyCurrentPreviousViewID(mCountly.moduleViews, vals[1], vals[0]); - //we are transitioning to the next view - //first the next activities 'onStart' is called - //we would report the duration of the first view and then start the next one - ClearFillSegmentationViewEnd(segm, viewNames[0], null); - TestUtils.validateRecordEventInternalMock(ep, ModuleViews.VIEW_EVENT_KEY, 1, segm, vals[0], 0, 2); + validateView(viewNames[0], 1.0, 2, 4, false, false, null, "idv1", "");// validate stop event of act1 + validateView(viewNames[1], 0.0, 3, 4, false, true, null, "idv2", "idv1"); // validate start event of act2 - ClearFillSegmentationViewStart(segm, viewNames[1], false); - TestUtils.validateRecordEventInternalMock(ep, ModuleViews.VIEW_EVENT_KEY, segm, vals[1], 1, 2); - clearInvocations(ep); + mCountly.onStopInternal(); // it does not end the view with this call but will end it on next start + TestUtils.verifyCurrentPreviousViewID(mCountly.moduleViews, vals[1], vals[0]); + //WARN - Possible error for next view starting, this view will not end Thread.sleep(2000); mCountly.onStartInternal(act3); TestUtils.verifyCurrentPreviousViewID(mCountly.moduleViews, vals[2], vals[1]); - mCountly.onStopInternal(); - TestUtils.verifyCurrentPreviousViewID(mCountly.moduleViews, vals[2], vals[1]); - ClearFillSegmentationViewEnd(segm, viewNames[1], null); - TestUtils.validateRecordEventInternalMock(ep, ModuleViews.VIEW_EVENT_KEY, 2, segm, vals[1], 0, 2); + validateView(viewNames[1], 2.0, 4, 6, false, false, null, "idv2", "idv1");// validate stop event of act2 + validateView(viewNames[2], 0.0, 5, 6, false, true, null, "idv3", "idv2"); // validate start event of act3 - ClearFillSegmentationViewStart(segm, viewNames[2], false); - TestUtils.validateRecordEventInternalMock(ep, ModuleViews.VIEW_EVENT_KEY, segm, vals[2], 1, 2); - clearInvocations(ep); + mCountly.onStopInternal(); // onStop call will not end previous view + TestUtils.verifyCurrentPreviousViewID(mCountly.moduleViews, vals[2], vals[1]); Thread.sleep(1000); - mCountly.onStopInternal(); + mCountly.onStopInternal(); // but this will end TestUtils.verifyCurrentPreviousViewID(mCountly.moduleViews, vals[2], vals[1]); - ClearFillSegmentationViewEnd(segm, viewNames[2], null); - TestUtils.validateRecordEventInternalMock(ep, ModuleViews.VIEW_EVENT_KEY, 1, segm, vals[2], 0, 1); + ModuleSessionsTests.validateSessionEndRequest(6, 4, TestUtils.commonDeviceId); + validateView(viewNames[2], 1.0, 7, 8, false, false, null, "idv3", "idv2");// validate stop event of act3 + + Assert.assertEquals(8, TestUtils.getCurrentRQ("events").length); } void ClearFillSegmentationViewStart(final Map segm, String viewName, boolean firstView) { @@ -1283,20 +1263,17 @@ public void overridingViewProtectedSegmentation() { TestUtils.validateRecordEventInternalMock(ep, ModuleViews.VIEW_EVENT_KEY, segm, vals[1], 0, 1); } - public void clearFirstViewFlagSessionEndBase(boolean manualSessions) throws InterruptedException { + public void clearFirstViewFlagSessionEndBase(boolean manualSessions) throws InterruptedException, JSONException { @NonNull CountlyConfig cc = TestUtils.createViewCountlyConfig(false, false, false, safeViewIDGenerator, null); + cc.setEventQueueSizeToSend(1); if (manualSessions) { cc.enableManualSessionControl(); } Countly mCountly = new Countly().init(cc); - @NonNull EventProvider ep = TestUtils.setEventProviderToMock(mCountly, mock(EventProvider.class)); + TestUtils.assertRQSize(0); mCountly.views().startView("a", null); - Map segm = new HashMap<>(); - ClearFillSegmentationViewStart(segm, "a", true); - - TestUtils.validateRecordEventInternalMock(ep, ModuleViews.VIEW_EVENT_KEY, segm, vals[0], 0, 1); - clearInvocations(ep); + validateView("a", 0.0, 0, 1, true, true, null, vals[0], ""); if (manualSessions) { mCountly.sessions().beginSession(); @@ -1304,78 +1281,66 @@ public void clearFirstViewFlagSessionEndBase(boolean manualSessions) throws Inte mCountly.onStartInternal(mock(TestUtils.Activity2.class)); } - mCountly.views().startView("b", null); - ClearFillSegmentationViewStart(segm, "b", false); - - TestUtils.validateRecordEventInternalMock(ep, ModuleViews.VIEW_EVENT_KEY, segm, vals[1], 0, 1); - clearInvocations(ep); + ModuleSessionsTests.validateSessionBeginRequest(1, TestUtils.commonDeviceId); + mCountly.views().startView("b", null); + validateView("b", 0.0, 2, 3, false, true, null, vals[1], vals[0]); Thread.sleep(1000); + int lastViewIdx = 4; if (manualSessions) { mCountly.sessions().endSession(); } else { mCountly.onStopInternal(); - - //in this situation we would pause all views - ClearFillSegmentationViewEnd(segm, "a", null); - TestUtils.validateRecordEventInternalMock(ep, ModuleViews.VIEW_EVENT_KEY, 1, segm, vals[0], 1, 2); - - ClearFillSegmentationViewEnd(segm, "b", null); - TestUtils.validateRecordEventInternalMock(ep, ModuleViews.VIEW_EVENT_KEY, 1, segm, vals[1], 0, 2); - - clearInvocations(ep); + lastViewIdx = 6; + //in this situation we would stop all views + validateView("b", 1.0, 4, 6, false, false, null, vals[1], vals[0]); + validateView("a", 1.0, 5, 6, false, false, null, vals[0], vals[0]); } + ModuleSessionsTests.validateSessionEndRequest(3, 1, TestUtils.commonDeviceId); mCountly.views().startView("c", null); - ClearFillSegmentationViewStart(segm, "c", true); - - TestUtils.validateRecordEventInternalMock(ep, ModuleViews.VIEW_EVENT_KEY, segm, vals[2], 0, 1); - clearInvocations(ep); + validateView("c", 0.0, lastViewIdx, lastViewIdx + 1, true, true, null, vals[2], vals[1]); } @Test - public void clearFirstViewFlagSessionEndManual() throws InterruptedException { + public void clearFirstViewFlagSessionEndManual() throws InterruptedException, JSONException { clearFirstViewFlagSessionEndBase(true); } @Test - public void clearFirstViewFlagSessionEndAutomatic() throws InterruptedException { + public void clearFirstViewFlagSessionEndAutomatic() throws InterruptedException, JSONException { clearFirstViewFlagSessionEndBase(false); } @Test - public void clearFirstViewFlagSessionConsentRemoved() { + public void clearFirstViewFlagSessionConsentRemoved() throws JSONException { @NonNull CountlyConfig cc = TestUtils.createViewCountlyConfig(false, false, false, safeViewIDGenerator, null); cc.setRequiresConsent(true); cc.setConsentEnabled(new String[] { Countly.CountlyFeatureNames.views }); + cc.setEventQueueSizeToSend(1); Countly mCountly = new Countly().init(cc); - @NonNull EventProvider ep = TestUtils.setEventProviderToMock(mCountly, mock(EventProvider.class)); mCountly.views().startView("a", null); - Map segm = new HashMap<>(); - ClearFillSegmentationViewStart(segm, "a", true); - TestUtils.validateRecordEventInternalMock(ep, ModuleViews.VIEW_EVENT_KEY, segm, vals[0], 0, 1); - clearInvocations(ep); + // 0 is consent request + ModuleConsentTests.validateConsentRequest(TestUtils.commonDeviceId, 0, new boolean[] { false, false, false, false, false, false, false, false, false, false, false, false, true, false }); + TestUtils.validateRequest(TestUtils.commonDeviceId, TestUtils.map("location", ""), 1); + validateView("a", 0.0, 2, 3, true, true, null, vals[0], ""); //nothing should happen when session consent is given mCountly.consent().giveConsent(new String[] { Countly.CountlyFeatureNames.sessions }); - mCountly.views().startView("b", null); - ClearFillSegmentationViewStart(segm, "b", false); - TestUtils.validateRecordEventInternalMock(ep, ModuleViews.VIEW_EVENT_KEY, segm, vals[1], 0, 1); - clearInvocations(ep); + ModuleConsentTests.validateConsentRequest(TestUtils.commonDeviceId, 3, new boolean[] { true, false, false, false, false, false, false, false, false, false, false, false, true, false }); + validateView("b", 0.0, 4, 5, false, true, null, vals[1], vals[0]); //internal flag should be reset whens session consent is removed mCountly.consent().removeConsent(new String[] { Countly.CountlyFeatureNames.sessions }); + ModuleConsentTests.validateConsentRequest(TestUtils.commonDeviceId, 5, new boolean[] { false, false, false, false, false, false, false, false, false, false, false, false, true, false }); mCountly.views().startView("c", null); - ClearFillSegmentationViewStart(segm, "c", true); - - TestUtils.validateRecordEventInternalMock(ep, ModuleViews.VIEW_EVENT_KEY, segm, vals[2], 0, 1); - clearInvocations(ep); + validateView("c", 0.0, 6, 7, true, true, null, vals[2], vals[1]); } /** @@ -1471,11 +1436,7 @@ public void internalLimit_recordViewsWithSegmentation() throws JSONException { givenStartSegm.put("sop", 4); String viewID = mCountly.views().startView("VIEW", givenStartSegm); - Map expectedSegm = new ConcurrentHashMap<>(); - ClearFillSegmentationViewStart(expectedSegm, "VI", true); - expectedSegm.putAll(TestUtils.map("av", "v1", "so", 4)); - - ModuleEventsTests.validateEventInRQ(ModuleViews.VIEW_EVENT_KEY, expectedSegm, 0); + validateView("VI", 0.0, 0, 1, true, true, TestUtils.map("av", "v1", "so", 4), "idv1", ""); mCountly.views().setGlobalViewSegmentation(TestUtils.map("sunburn", true, "sunflower", "huh")); @@ -1483,10 +1444,7 @@ public void internalLimit_recordViewsWithSegmentation() throws JSONException { endSegm.put("satellite", "hoho"); endSegm.put("avu", 25); mCountly.views().stopViewWithID(viewID, endSegm); - ClearFillSegmentationViewEnd(expectedSegm, "VI", null); - expectedSegm.putAll(TestUtils.map("av", 25, "sa", "hoho", "su", "huh")); - - ModuleEventsTests.validateEventInRQ(ModuleViews.VIEW_EVENT_KEY, expectedSegm, 1); + validateView("VI", 0.0, 1, 2, false, false, TestUtils.map("av", 25, "sa", "hoho", "su", "huh"), "idv1", ""); } /** @@ -1521,11 +1479,7 @@ public void internalLimit_recordViewsWithSegmentation_maxValueSize() throws JSON givenStartSegm.put("you", "would"); String viewID = mCountly.views().startView("VIEW", givenStartSegm); - Map expectedSegm = new HashMap<>(); - ClearFillSegmentationViewStart(expectedSegm, "VI", true); - expectedSegm.putAll(TestUtils.map("yo", "wo", "so", "ma", "av", "v1", "i_", "i_")); - - ModuleEventsTests.validateEventInRQ(ModuleViews.VIEW_EVENT_KEY, expectedSegm, 0); + validateView("VI", 0.0, 0, 1, true, true, TestUtils.map("yo", "wo", "so", "ma", "av", "v1", "i_", "i_"), "idv1", ""); mCountly.views().setGlobalViewSegmentation(TestUtils.map("go", 45, "gone", 567.78f)); @@ -1536,10 +1490,8 @@ public void internalLimit_recordViewsWithSegmentation_maxValueSize() throws JSON endSegm.put("happy_life", false); endSegm.put("nope", 123); mCountly.views().stopViewWithID(viewID, endSegm); - ClearFillSegmentationViewEnd(expectedSegm, "VI", null); - expectedSegm.putAll(TestUtils.map("av", 25, "no", 123, "sa", "ho", "ha", true)); - ModuleEventsTests.validateEventInRQ(ModuleViews.VIEW_EVENT_KEY, expectedSegm, 1); + validateView("VI", 0.0, 1, 2, false, false, TestUtils.map("av", 25, "no", 123, "sa", "ho", "ha", true), "idv1", ""); } /** @@ -1723,7 +1675,7 @@ public void startView_validateSupportedJSONArrays() throws JSONException { */ @Test public void startView_unsupportedDataTypesSegmentation() throws JSONException { - CountlyConfig countlyConfig = TestUtils.createBaseConfig(); + CountlyConfig countlyConfig = TestUtils.createScenarioEventIDConfig(TestUtils.incrementalViewIdGenerator(), TestUtils.incrementalEventIdGenerator()); countlyConfig.setEventQueueSizeToSend(1); Countly countly = new Countly().init(countlyConfig); @@ -1740,7 +1692,22 @@ public void startView_unsupportedDataTypesSegmentation() throws JSONException { Map expectedSegmentation = TestUtils.map(); ClearFillSegmentationViewStart(expectedSegmentation, "test", true); - ModuleEventsTests.validateEventInRQ(ModuleViews.VIEW_EVENT_KEY, expectedSegmentation, 0); + validateView("test", 0.0, 0, 1, true, false, expectedSegmentation, "idv1", ""); + } + + static void validateView(String viewName, Double viewDuration, int idx, int size, boolean start, boolean visit, Map customSegmentation, String id, String pvid) throws JSONException { + Map viewSegmentation = TestUtils.map("name", viewName, "segment", "Android"); + if (start) { + viewSegmentation.put("start", "1"); + } + if (visit) { + viewSegmentation.put("visit", "1"); + } + if (customSegmentation != null) { + viewSegmentation.putAll(customSegmentation); + } + + ModuleEventsTests.validateEventInRQ(TestUtils.commonDeviceId, ModuleViews.VIEW_EVENT_KEY, viewSegmentation, 1, 0.0, viewDuration, id, pvid, "_CLY_", "_CLY_", idx, size, 0, 1); } //todo extract orientation tests 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 d317e9d8b..0a655ff1b 100644 --- a/sdk/src/androidTest/java/ly/count/android/sdk/TestUtils.java +++ b/sdk/src/androidTest/java/ly/count/android/sdk/TestUtils.java @@ -13,6 +13,7 @@ import java.util.Map; import java.util.Random; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; import org.json.JSONException; import org.json.JSONObject; import org.junit.Assert; @@ -492,12 +493,25 @@ protected static CountlyStore getCountyStore() { * @return array of request params */ protected static @NonNull Map[] getCurrentRQ() { + return getCurrentRQ(""); + } + + /** + * Get current request queue from target folder + * + * @param filter Filter by given string + * @return array of request params + */ + protected static @NonNull Map[] getCurrentRQ(String filter) { //get all request files from target folder String[] requests = getCountyStore().getRequests(); //create array of request params Map[] resultMapArray = new ConcurrentHashMap[requests.length]; for (int i = 0; i < requests.length; i++) { + if (!requests[i].contains(filter)) { + continue; + } String[] params = requests[i].split("&"); @@ -618,4 +632,14 @@ protected static void validateRequest(String deviceId, Map expec protected static void assertRQSize(int size) { Assert.assertEquals(size, getCurrentRQ().length); } + + static SafeIDGenerator incrementalViewIdGenerator() { + AtomicInteger counter = new AtomicInteger(0); + return () -> "idv" + counter.incrementAndGet(); + } + + static SafeIDGenerator incrementalEventIdGenerator() { + AtomicInteger counter = new AtomicInteger(0); + return () -> "ide" + counter.incrementAndGet(); + } } diff --git a/sdk/src/androidTest/java/ly/count/android/sdk/scUP_UserProfileTests.java b/sdk/src/androidTest/java/ly/count/android/sdk/scUP_UserProfileTests.java index 6710ebb8c..141c01af8 100644 --- a/sdk/src/androidTest/java/ly/count/android/sdk/scUP_UserProfileTests.java +++ b/sdk/src/androidTest/java/ly/count/android/sdk/scUP_UserProfileTests.java @@ -335,7 +335,7 @@ public void UP_207_CNR_M() throws JSONException { TestUtils.validateRequest("merge_id", TestUtils.map("old_device_id", TestUtils.commonDeviceId), 4); - ModuleEventsTests.validateEventInRQ("merge_id", "C", 1, 0.0d, 0.0d, 5, 0, 1, 8); + ModuleEventsTests.validateEventInRQ("merge_id", "C", 5, 0, 1); validateUserDataRequest(6, 8, "4", "merge_id"); @@ -388,7 +388,7 @@ public void UP_208_CR_CG_M() throws JSONException { ModuleSessionsTests.validateSessionEndRequest(4, null, TestUtils.commonDeviceId); TestUtils.validateRequest("merge_id", TestUtils.map("old_device_id", TestUtils.commonDeviceId), 5); - ModuleEventsTests.validateEventInRQ("merge_id", "C", 1, 0.0d, 0.0d, 6, 0, 1, 9); + ModuleEventsTests.validateEventInRQ("merge_id", "C", 6, 0, 1); validateUserDataRequest(7, 9, "4", "merge_id"); 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 6615572dc..3c588ddb9 100644 --- a/sdk/src/main/java/ly/count/android/sdk/ModuleViews.java +++ b/sdk/src/main/java/ly/count/android/sdk/ModuleViews.java @@ -468,15 +468,11 @@ void updateOrientation(int newOrientation) { } void stopRunningViewsAndSend() { - L.d("[ModuleViews] stopRunningViewsAndSend, going to the background and pausing"); + L.d("[ModuleViews] stopRunningViewsAndSend, going to the background and stopping views"); for (Map.Entry entry : viewDataMap.entrySet()) { ViewData vd = entry.getValue(); vd.willStartAgain = true; - - if (vd.viewStartTimeSeconds > 0) { - //if the view is running - stopViewWithIDInternal(vd.viewID, null); - } + stopViewWithIDInternal(vd.viewID, null); } } @@ -486,7 +482,6 @@ void startAutoStoppedViews() { Iterator> iterator = viewDataMap.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry value = iterator.next(); - iterator.remove(); ViewData vd = value.getValue(); if (vd.willStartAgain) { //if the view is paused From c793650ba7e47c7bb812edc43e782acca61d45da Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 13 Aug 2024 16:29:29 +0300 Subject: [PATCH 03/11] feat: test it with bg/fg switch --- .../count/android/sdk/ModuleViewsTests.java | 63 +++++++++++++++++++ 1 file changed, 63 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 bd6bf7a5e..e9ce8d6d6 100644 --- a/sdk/src/androidTest/java/ly/count/android/sdk/ModuleViewsTests.java +++ b/sdk/src/androidTest/java/ly/count/android/sdk/ModuleViewsTests.java @@ -1695,6 +1695,69 @@ public void startView_unsupportedDataTypesSegmentation() throws JSONException { validateView("test", 0.0, 0, 1, true, false, expectedSegmentation, "idv1", ""); } + /** + * "startView" with bg/fg switch case + * - Validate that after an auto stopped view is started and app gone to background + * running view should stop and send a stop view request + * - After coming from the background to foreground the stopped view should start again + * - When we stop the view it should be removed from the cached views + * + * @throws InterruptedException if the thread is interrupted + * @throws JSONException if the JSON is not valid + */ + @Test + public void startView_restartAfterActivityComesFromForeground() throws InterruptedException, JSONException { + CountlyConfig countlyConfig = TestUtils.createScenarioEventIDConfig(TestUtils.incrementalViewIdGenerator(), TestUtils.incrementalEventIdGenerator()); + countlyConfig.setApplication(null); + countlyConfig.setContext(TestUtils.getContext()); + countlyConfig.setGlobalViewSegmentation(TestUtils.map("try", "this", "maybe", false)); + + countlyConfig.setEventQueueSizeToSend(1); + Countly countly = new Countly().init(countlyConfig); + + Map segmentation = TestUtils.map( + "a", "a", + "b", 55, + "c", new JSONArray(Arrays.asList("aa", "asd", "ad8")), + "d", true + ); + + Activity activity = Mockito.mock(Activity.class); + + TestUtils.assertRQSize(0); + + countly.onStart(activity); + countly.views().startView("test", segmentation); + + segmentation.put("try", "this"); + segmentation.put("maybe", false); + + 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, segmentation, "idv1", ""); + + Thread.sleep(1000); + + countly.onStop(); + ModuleSessionsTests.validateSessionEndRequest(3, 1, TestUtils.commonDeviceId); + validateView("test", 1.0, 4, 5, false, false, TestUtils.map("try", "this", "maybe", false), "idv1", ""); + + countly.onStart(activity); + + ModuleSessionsTests.validateSessionBeginRequest(5, TestUtils.commonDeviceId); + ModuleEventsTests.validateEventInRQ(TestUtils.commonDeviceId, ModuleViews.ORIENTATION_EVENT_KEY, null, 1, 0.0, 0.0, "ide2", "_CLY_", "idv1", "_CLY_", 6, 8, 0, 1); + validateView("test", 0.0, 7, 8, true, true, TestUtils.map("try", "this", "maybe", false), "idv2", "idv1"); + + Thread.sleep(1000); + + countly.views().stopViewWithName("test"); + validateView("test", 1.0, 8, 9, false, false, TestUtils.map("try", "this", "maybe", false), "idv2", "idv1"); + countly.onStop(); + + ModuleSessionsTests.validateSessionEndRequest(9, 1, TestUtils.commonDeviceId); + TestUtils.assertRQSize(10); + } + static void validateView(String viewName, Double viewDuration, int idx, int size, boolean start, boolean visit, Map customSegmentation, String id, String pvid) throws JSONException { Map viewSegmentation = TestUtils.map("name", viewName, "segment", "Android"); if (start) { From 7ed041676b87d14bc5e1f60f46e76a2d735e7e9e Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 14 Aug 2024 14:46:16 +0300 Subject: [PATCH 04/11] refactor: rename function --- sdk/src/main/java/ly/count/android/sdk/ModuleViews.java | 6 +++--- 1 file changed, 3 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 3c588ddb9..7b49d3fe9 100644 --- a/sdk/src/main/java/ly/count/android/sdk/ModuleViews.java +++ b/sdk/src/main/java/ly/count/android/sdk/ModuleViews.java @@ -476,8 +476,8 @@ void stopRunningViewsAndSend() { } } - void startAutoStoppedViews() { - L.d("[ModuleViews] startAutoStoppedViews, app is coming back to the foreground, starting views that were paused"); + void startStoppedViews() { + L.d("[ModuleViews] startStoppedViews, app is coming back to the foreground, starting views that were paused"); Iterator> iterator = viewDataMap.entrySet().iterator(); while (iterator.hasNext()) { @@ -549,7 +549,7 @@ void onActivityStarted(Activity activity, int updatedActivityCount) { if (updatedActivityCount == 1) { //if we go to the background, pause all running views - startAutoStoppedViews(); + startStoppedViews(); } } From 180300e43607cee84a675c4808703c715076e80f Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 14 Aug 2024 14:50:57 +0300 Subject: [PATCH 05/11] doc: add changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30c13b81f..463d99b2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## XX.XX.XX +* The views will be stopped and restarted now while going to the background or foreground instead of resuming and pausing. + ## 24.7.1 * ! Minor breaking change ! Unsupported types for user properties will now be omitted, they won't be converted to strings. From e34730b67a70129d29ffda1918f677da6eaf2d63 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 14 Aug 2024 15:07:00 +0300 Subject: [PATCH 06/11] doc: rename func --- .../main/java/ly/count/android/sdk/ModuleViews.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 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 7b49d3fe9..1b6a7b3d7 100644 --- a/sdk/src/main/java/ly/count/android/sdk/ModuleViews.java +++ b/sdk/src/main/java/ly/count/android/sdk/ModuleViews.java @@ -476,15 +476,15 @@ void stopRunningViewsAndSend() { } } - void startStoppedViews() { - L.d("[ModuleViews] startStoppedViews, app is coming back to the foreground, starting views that were paused"); + void startAutoStoppedViews() { + L.d("[ModuleViews] startAutoStoppedViews, app is coming back to the foreground, starting views that were stopped"); Iterator> iterator = viewDataMap.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry value = iterator.next(); ViewData vd = value.getValue(); if (vd.willStartAgain) { - //if the view is paused + //if the view is auto-stopped, start it again and remove from the cache iterator.remove(); startViewInternal(vd.viewName, vd.viewSegmentation, vd.isAutoStoppedView); } @@ -548,8 +548,8 @@ void onActivityStarted(Activity activity, int updatedActivityCount) { } if (updatedActivityCount == 1) { - //if we go to the background, pause all running views - startStoppedViews(); + //if we go to the background, stop all running views + startAutoStoppedViews(); } } From 91857358323e5daaa3e3592afcda8577cf36e512 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 14 Aug 2024 15:16:19 +0300 Subject: [PATCH 07/11] doc: rename func --- sdk/src/main/java/ly/count/android/sdk/ModuleViews.java | 6 +++--- 1 file changed, 3 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 1b6a7b3d7..761390f2e 100644 --- a/sdk/src/main/java/ly/count/android/sdk/ModuleViews.java +++ b/sdk/src/main/java/ly/count/android/sdk/ModuleViews.java @@ -476,8 +476,8 @@ void stopRunningViewsAndSend() { } } - void startAutoStoppedViews() { - L.d("[ModuleViews] startAutoStoppedViews, app is coming back to the foreground, starting views that were stopped"); + void startStoppedViews() { + L.d("[ModuleViews] startStoppedViews, app is coming back to the foreground, starting views that were stopped"); Iterator> iterator = viewDataMap.entrySet().iterator(); while (iterator.hasNext()) { @@ -549,7 +549,7 @@ void onActivityStarted(Activity activity, int updatedActivityCount) { if (updatedActivityCount == 1) { //if we go to the background, stop all running views - startAutoStoppedViews(); + startStoppedViews(); } } From 562a8816f734476367b2229a864b19c863b4c056 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 15 Aug 2024 09:48:23 +0300 Subject: [PATCH 08/11] fix: codacy --- .../java/ly/count/android/sdk/ModuleEventsTests.java | 4 +--- .../java/ly/count/android/sdk/ModuleViewsTests.java | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/sdk/src/androidTest/java/ly/count/android/sdk/ModuleEventsTests.java b/sdk/src/androidTest/java/ly/count/android/sdk/ModuleEventsTests.java index a58f64c6b..ebaa0cd5e 100644 --- a/sdk/src/androidTest/java/ly/count/android/sdk/ModuleEventsTests.java +++ b/sdk/src/androidTest/java/ly/count/android/sdk/ModuleEventsTests.java @@ -813,9 +813,7 @@ protected static void validateEventInRQ(String deviceId, String eventName, Map= 0 && dow < 7); Assert.assertTrue(hour >= 0 && hour < 24); Assert.assertTrue(timestamp >= 0); - - System.out.println("Event: " + event.toString()); - + validateId(id, event.optString("id", ""), "Event ID"); validateId(pvid, event.optString("pvid", ""), "Previous View ID"); validateId(cvid, event.optString("cvid", ""), "Current View ID"); 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 e9ce8d6d6..c36f817f9 100644 --- a/sdk/src/androidTest/java/ly/count/android/sdk/ModuleViewsTests.java +++ b/sdk/src/androidTest/java/ly/count/android/sdk/ModuleViewsTests.java @@ -1722,7 +1722,7 @@ public void startView_restartAfterActivityComesFromForeground() throws Interrupt "d", true ); - Activity activity = Mockito.mock(Activity.class); + Activity activity = mock(Activity.class); TestUtils.assertRQSize(0); From 7caa2653bdb68dadec04ebf9fe7af3fc422107e5 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Fri, 16 Aug 2024 11:32:10 +0300 Subject: [PATCH 09/11] fix: remove segm adding --- sdk/src/main/java/ly/count/android/sdk/ModuleViews.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 761390f2e..94889ff5b 100644 --- a/sdk/src/main/java/ly/count/android/sdk/ModuleViews.java +++ b/sdk/src/main/java/ly/count/android/sdk/ModuleViews.java @@ -275,6 +275,8 @@ void stopViewWithIDInternal(@Nullable String viewID, @Nullable Map customViewSegmentation, String viewRecordingSource) { @@ -486,7 +488,7 @@ void startStoppedViews() { if (vd.willStartAgain) { //if the view is auto-stopped, start it again and remove from the cache iterator.remove(); - startViewInternal(vd.viewName, vd.viewSegmentation, vd.isAutoStoppedView); + startViewInternal(vd.viewName, null, vd.isAutoStoppedView); } } } From 9af1ad8b6a21f7898573f3c339b49cecf22b05a3 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 21 Aug 2024 16:17:25 +0300 Subject: [PATCH 10/11] feat: add previous segmentation --- sdk/src/main/java/ly/count/android/sdk/ModuleViews.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 94889ff5b..c5176c3d6 100644 --- a/sdk/src/main/java/ly/count/android/sdk/ModuleViews.java +++ b/sdk/src/main/java/ly/count/android/sdk/ModuleViews.java @@ -488,7 +488,7 @@ void startStoppedViews() { if (vd.willStartAgain) { //if the view is auto-stopped, start it again and remove from the cache iterator.remove(); - startViewInternal(vd.viewName, null, vd.isAutoStoppedView); + startViewInternal(vd.viewName, vd.viewSegmentation, vd.isAutoStoppedView); } } } From 23264b0ea03b6b0d13c7d38f9e029f443c50b729 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 21 Aug 2024 16:31:38 +0300 Subject: [PATCH 11/11] feat: add tests --- .../count/android/sdk/ModuleViewsTests.java | 58 +++++++++++++++++++ .../ly/count/android/sdk/ModuleViews.java | 2 - 2 files changed, 58 insertions(+), 2 deletions(-) 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 c36f817f9..da950799e 100644 --- a/sdk/src/androidTest/java/ly/count/android/sdk/ModuleViewsTests.java +++ b/sdk/src/androidTest/java/ly/count/android/sdk/ModuleViewsTests.java @@ -1758,6 +1758,64 @@ public void startView_restartAfterActivityComesFromForeground() throws Interrupt TestUtils.assertRQSize(10); } + /** + * "startView" with bg/fg switch case + * - Validate that after an auto stopped view is started and app gone to background + * running view should stop and send a stop view request + * - After coming from the background to foreground the stopped view should start again, + * and it should contain the previous segmentation + * - When we stop the view it should be removed from the cached views + * + * @throws InterruptedException if the thread is interrupted + * @throws JSONException if the JSON is not valid + */ + @Test + public void startView_restartAfterActivityComesFromForeground_copyPreviousSegmentation() throws InterruptedException, JSONException { + CountlyConfig countlyConfig = TestUtils.createScenarioEventIDConfig(TestUtils.incrementalViewIdGenerator(), TestUtils.incrementalEventIdGenerator()); + countlyConfig.setApplication(null); + countlyConfig.setContext(TestUtils.getContext()); + countlyConfig.setGlobalViewSegmentation(TestUtils.map("try", "this", "maybe", false)); + + countlyConfig.setEventQueueSizeToSend(1); + Countly countly = new Countly().init(countlyConfig); + + Map segmentation = TestUtils.map( + "a", "a", + "b", 55, + "c", new JSONArray(Arrays.asList("aa", "asd", "ad8")), + "d", true + ); + + Activity activity = mock(Activity.class); + + TestUtils.assertRQSize(0); + + countly.onStart(activity); + countly.views().startView("test", segmentation); + + countly.views().addSegmentationToViewWithName("test", TestUtils.map("yama", "de", "kuda", "sai")); + + segmentation.put("try", "this"); + segmentation.put("maybe", false); + + 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, segmentation, "idv1", ""); + + Thread.sleep(1000); + + countly.onStop(); + ModuleSessionsTests.validateSessionEndRequest(3, 1, TestUtils.commonDeviceId); + validateView("test", 1.0, 4, 5, false, false, TestUtils.map("try", "this", "maybe", false, "yama", "de", "kuda", "sai"), "idv1", ""); + + countly.onStart(activity); + + ModuleSessionsTests.validateSessionBeginRequest(5, TestUtils.commonDeviceId); + ModuleEventsTests.validateEventInRQ(TestUtils.commonDeviceId, ModuleViews.ORIENTATION_EVENT_KEY, null, 1, 0.0, 0.0, "ide2", "_CLY_", "idv1", "_CLY_", 6, 8, 0, 1); + validateView("test", 0.0, 7, 8, true, true, TestUtils.map("try", "this", "maybe", false, "yama", "de", "kuda", "sai"), "idv2", "idv1"); + TestUtils.assertRQSize(8); + } + static void validateView(String viewName, Double viewDuration, int idx, int size, boolean start, boolean visit, Map customSegmentation, String id, String pvid) throws JSONException { Map viewSegmentation = TestUtils.map("name", viewName, "segment", "Android"); if (start) { 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 c5176c3d6..761390f2e 100644 --- a/sdk/src/main/java/ly/count/android/sdk/ModuleViews.java +++ b/sdk/src/main/java/ly/count/android/sdk/ModuleViews.java @@ -275,8 +275,6 @@ void stopViewWithIDInternal(@Nullable String viewID, @Nullable Map customViewSegmentation, String viewRecordingSource) {