diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9baeb66b..e1c65319 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,7 +4,6 @@
   * "setLocale(String)"
 
 * Added the user profiles feature interface, and it is accessible through "Countly::instance()::userProfile()" call.
-
 * Added the location feature interface, and it is accessible through "Countly::instance()::location()" call.
 * Added init time configuration for the location parameters:
   * "setLocation(String countryCode, String city, String location, String ipAddress)"
@@ -12,6 +11,9 @@
 * Crash Reporting interface added and accessible through "Countly::instance()::crash()" call.
 * Added "disableUnhandledCrashReporting" function to the "Config" class to disable automatic uncaught crash reporting.
 * Added "setMaxBreadcrumbCount(int)" function to the "Config" class to change allowed max breadcrumb count.
+* Added the views feature interface, and it is accessible through "Countly::instance()::views()" call.
+* Added a configuration function to set global view segmentation to the "Config" class:
+  * "views.setGlobalViewSegmentation(Map<String, Object>)"
 
 * Fixed a bug where setting custom user properties would not work.
 * Fixed a bug where setting organization of the user would not work.
@@ -46,6 +48,12 @@
   * "setCustom(String, Object)" instead use "Countly::userProfile::setProperty" via "instance()" call
   * "set(String, Object)" instead use "Countly::userProfile::setProperty" via "instance()" call
   * "picture(byte[])" instead use "Countly::userProfile::setProperty" via "instance()" call
+* Deprecated "View::start(bool)" call, use "Countly::views::startView" instead via "instance()" call.
+* Deprecated "View::stop(bool)" call, use "Countly::views::stopViewWithName" or "Countly::views::stopViewWithID" instead via "instance()" call.
+* Deprecated "Usage::view(String)" call, use "Countly::views::startView" instead via "instance()" call.
+* Deprecated "Usage::view(String, bool)" call, use "Countly::views::startView" instead via "instance()" call.
+* Deprecated "Countly::view(String)" call, use "Countly::views::startView" instead via "instance()" call.
+* Deprecated "Countly::view(String, bool)" call, use "Countly::views::startView" instead via "instance()" call.
 
 ## 23.10.1
 
diff --git a/sdk-java/src/main/java/ly/count/sdk/java/Config.java b/sdk-java/src/main/java/ly/count/sdk/java/Config.java
index 93836f3f..321c42d7 100644
--- a/sdk-java/src/main/java/ly/count/sdk/java/Config.java
+++ b/sdk-java/src/main/java/ly/count/sdk/java/Config.java
@@ -10,6 +10,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import ly.count.sdk.java.internal.ConfigViews;
 import ly.count.sdk.java.internal.CoreFeature;
 import ly.count.sdk.java.internal.Log;
 import ly.count.sdk.java.internal.LogCallback;
@@ -1456,4 +1457,6 @@ public Config disableLocation() {
         locationEnabled = false;
         return this;
     }
+
+    public ConfigViews views = new ConfigViews(this);
 }
diff --git a/sdk-java/src/main/java/ly/count/sdk/java/Countly.java b/sdk-java/src/main/java/ly/count/sdk/java/Countly.java
index 0b91c093..c1dbbab4 100644
--- a/sdk-java/src/main/java/ly/count/sdk/java/Countly.java
+++ b/sdk-java/src/main/java/ly/count/sdk/java/Countly.java
@@ -14,6 +14,7 @@
 import ly.count.sdk.java.internal.ModuleLocation;
 import ly.count.sdk.java.internal.ModuleRemoteConfig;
 import ly.count.sdk.java.internal.ModuleUserProfile;
+import ly.count.sdk.java.internal.ModuleViews;
 import ly.count.sdk.java.internal.SDKCore;
 
 /**
@@ -475,6 +476,21 @@ public ModuleCrashes.Crashes crashes() {
         return sdk.crashes();
     }
 
+    /**
+     * <code>Views</code> interface to use views feature.
+     *
+     * @return {@link ModuleViews.Views} instance.
+     */
+    public ModuleViews.Views views() {
+        if (!isInitialized()) {
+            if (L != null) {
+                L.e("[Countly] SDK is not initialized yet.");
+            }
+            return null;
+        }
+        return sdk.views();
+    }
+
     /**
      * Get existing or create new timed event object, don't record it.
      *
diff --git a/sdk-java/src/main/java/ly/count/sdk/java/View.java b/sdk-java/src/main/java/ly/count/sdk/java/View.java
index bab08f04..d449bcd6 100644
--- a/sdk-java/src/main/java/ly/count/sdk/java/View.java
+++ b/sdk-java/src/main/java/ly/count/sdk/java/View.java
@@ -1,4 +1,5 @@
 package ly.count.sdk.java;
+import ly.count.sdk.java.internal.ModuleViews;
 
 import ly.count.sdk.java.internal.ModuleViews;
 
diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ConfigViews.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ConfigViews.java
new file mode 100644
index 00000000..6886181b
--- /dev/null
+++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ConfigViews.java
@@ -0,0 +1,23 @@
+package ly.count.sdk.java.internal;
+
+import java.util.Map;
+import ly.count.sdk.java.Config;
+
+public class ConfigViews {
+    private final Config config;
+
+    public ConfigViews(Config config) {
+        this.config = config;
+    }
+
+    protected Map<String, Object> globalViewSegmentation = null;
+
+    /**
+     * @param segmentation segmentation values that will be added for all recorded views (manual and automatic)
+     * @return Returns the same config object for convenient linking
+     */
+    public Config setGlobalViewSegmentation(Map<String, Object> segmentation) {
+        globalViewSegmentation = segmentation;
+        return config;
+    }
+}
diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/EventImpl.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/EventImpl.java
index 43c53e89..978fbc0e 100644
--- a/sdk-java/src/main/java/ly/count/sdk/java/internal/EventImpl.java
+++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/EventImpl.java
@@ -26,6 +26,11 @@ class EventImpl implements Event, JSONable {
     protected int hour;
     protected int dow;
 
+    protected String id;
+    protected String pvid;
+    protected String cvid;
+    protected String peid;
+
     final Log L;
 
     public interface EventRecorder {
@@ -38,7 +43,7 @@ public interface EventRecorder {
      */
     private boolean invalid = false;
 
-    EventImpl(@Nonnull String key, int count, Double sum, Double duration, @Nonnull Map<String, Object> segmentation, @Nonnull Log givenL) {
+    EventImpl(@Nonnull String key, int count, Double sum, Double duration, @Nonnull Map<String, Object> segmentation, @Nonnull Log givenL, String id, String pvid, String cvid, String peid) {
         L = givenL;
 
         this.recorder = null;
@@ -51,6 +56,10 @@ public interface EventRecorder {
         this.timestamp = instant.timestamp;
         this.hour = instant.hour;
         this.dow = instant.dow;
+        this.id = id;
+        this.pvid = pvid;
+        this.cvid = cvid;
+        this.peid = peid;
     }
 
     EventImpl(@Nonnull EventRecorder recorder, @Nonnull String key, @Nonnull Log givenL) {
@@ -239,6 +248,10 @@ public boolean equals(Object obj) {
     protected static final String TIMESTAMP_KEY = "timestamp";
     protected static final String DAY_OF_WEEK = "dow";
     protected static final String HOUR = "hour";
+    protected static final String ID_KEY = "id";
+    protected static final String PV_ID_KEY = "pvid";
+    protected static final String CV_ID_KEY = "cvid";
+    protected static final String PE_ID_KEY = "peid";
 
     /**
      * Serialize to JSON format according to Countly server requirements
@@ -266,6 +279,23 @@ public String toJSON(@Nonnull Log log) {
             if (duration != null) {
                 json.put(DUR_KEY, duration);
             }
+
+            //set the ID's only if they are not 'null'
+            if (id != null) {
+                json.put(ID_KEY, id);
+            }
+
+            if (pvid != null) {
+                json.put(PV_ID_KEY, pvid);
+            }
+
+            if (cvid != null) {
+                json.put(CV_ID_KEY, cvid);
+            }
+
+            if (peid != null) {
+                json.put(PE_ID_KEY, peid);
+            }
         } catch (JSONException e) {
             log.e("[EventImpl] Cannot serialize event to JSON " + e);
         }
@@ -304,6 +334,20 @@ public void recordEvent(Event event) {
             event.hour = json.optInt(HOUR);
             event.dow = json.optInt(DAY_OF_WEEK);
 
+            // the parsed ID's might not be set, or it might be set as null
+            if (!json.isNull(ID_KEY)) {
+                event.id = json.getString(ID_KEY);
+            }
+            if (!json.isNull(PV_ID_KEY)) {
+                event.pvid = json.getString(PV_ID_KEY);
+            }
+            if (!json.isNull(CV_ID_KEY)) {
+                event.cvid = json.getString(CV_ID_KEY);
+            }
+            if (!json.isNull(PE_ID_KEY)) {
+                event.peid = json.getString(PE_ID_KEY);
+            }
+
             if (!json.isNull(SEGMENTATION_KEY)) {
                 final JSONObject segm = json.getJSONObject(SEGMENTATION_KEY);
                 final HashMap<String, Object> segmentation = new HashMap<>(segm.length());
diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/InternalConfig.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/InternalConfig.java
index 2add2b0e..5c51185b 100644
--- a/sdk-java/src/main/java/ly/count/sdk/java/internal/InternalConfig.java
+++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/InternalConfig.java
@@ -31,7 +31,7 @@ public class InternalConfig extends Config {
     public StorageProvider storageProvider;
     protected IdGenerator viewIdGenerator;
     protected IdGenerator eventIdGenerator;
-
+    protected ViewIdProvider viewIdProvider;
     /**
      * Shouldn't be used!
      */
diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleEvents.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleEvents.java
index b4a6c771..4cae3bb5 100644
--- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleEvents.java
+++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleEvents.java
@@ -1,9 +1,7 @@
 package ly.count.sdk.java.internal;
 
-import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.stream.Collectors;
 import ly.count.sdk.java.Countly;
 import ly.count.sdk.java.Session;
 import ly.count.sdk.java.View;
@@ -12,6 +10,9 @@ public class ModuleEvents extends ModuleBase {
     protected EventQueue eventQueue = null;
     final Map<String, EventImpl> timedEvents = new ConcurrentHashMap<>();
     protected Events eventsInterface = null;
+    ViewIdProvider viewIdProvider = null;
+    IdGenerator idGenerator = null;
+    String previousEventId = null;
 
     @Override
     public void init(InternalConfig config) {
@@ -20,6 +21,14 @@ public void init(InternalConfig config) {
         eventQueue = new EventQueue(L, config.getEventsBufferSize());
         eventQueue.restoreFromDisk();
         eventsInterface = new Events();
+
+        idGenerator = config.eventIdGenerator;
+    }
+
+    @Override
+    public void initFinished(InternalConfig config) {
+        super.initFinished(config);
+        viewIdProvider = config.viewIdProvider;
     }
 
     @Override
@@ -85,37 +94,43 @@ private synchronized void addEventsToRequestQ(String deviceId) {
         ModuleRequests.pushAsync(internalConfig, request);
     }
 
-    protected void removeInvalidDataFromSegments(Map<String, Object> segments) {
+    protected void recordEventInternal(String key, int count, Double sum, Double dur, Map<String, Object> segmentation, String eventIdOverride) {
+        if (count <= 0) {
+            L.w("[ModuleEvents] recordEventInternal, Count can't be less than 1, ignoring this event.");
+            return;
+        }
 
-        if (segments == null || segments.isEmpty()) {
+        if (key == null || key.isEmpty()) {
+            L.w("[ModuleEvents] recordEventInternal, Key can't be null or empty, ignoring this event.");
             return;
         }
 
-        List<String> toRemove = segments.entrySet().stream()
-            .filter(entry -> !Utils.isValidDataType(entry.getValue()))
-            .map(Map.Entry::getKey)
-            .collect(Collectors.toList());
+        L.d("[ModuleEvents] recordEventInternal, Recording event with key: [" + key + "] and provided event ID of:[" + eventIdOverride + "] and segmentation with:[" + (segmentation == null ? "null" : segmentation.size()) + "] keys");
 
-        toRemove.forEach(key -> {
-            L.w("[ModuleEvents] RemoveSegmentInvalidDataTypes: In segmentation Data type '" + segments.get(key) + "' of item '" + key + "' isn't valid.");
-            segments.remove(key);
-        });
-    }
+        Utils.removeInvalidDataFromSegments(segmentation, L);
 
-    protected void recordEventInternal(String key, int count, Double sum, Double dur, Map<String, Object> segmentation) {
-        if (count <= 0) {
-            L.w("[ModuleEvents] recordEventInternal: Count can't be less than 1, ignoring this event.");
-            return;
+        String eventId, pvid = null, cvid = null;
+        if (Utils.isEmptyOrNull(eventIdOverride)) {
+            L.d("[ModuleEvents] recordEventInternal, Generating new event id because it was null or empty");
+            eventId = idGenerator.generateId();
+        } else {
+            eventId = eventIdOverride;
         }
 
-        if (key == null || key.isEmpty()) {
-            L.w("[ModuleEvents] recordEventInternal: Key can't be null or empty, ignoring this event.");
-            return;
+        if (key.equals(ModuleViews.KEY_VIEW_EVENT)) {
+            pvid = viewIdProvider.getPreviousViewId();
+        } else {
+            cvid = viewIdProvider.getCurrentViewId();
+        }
+
+        String previousEventIdToSend = this.previousEventId;
+        if (key.equals(FeedbackWidgetType.nps.eventKey) || key.equals(FeedbackWidgetType.survey.eventKey) || key.equals(ModuleViews.KEY_VIEW_EVENT) || key.equals(FeedbackWidgetType.rating.eventKey)) {
+            previousEventIdToSend = null;
+        } else {
+            this.previousEventId = eventId;
         }
 
-        removeInvalidDataFromSegments(segmentation);
-        EventImpl event = new EventImpl(key, count, sum, dur, segmentation, L);
-        addEventToQueue(event);
+        addEventToQueue(new EventImpl(key, count, sum, dur, segmentation, L, eventId, pvid, cvid, previousEventIdToSend));
     }
 
     private void addEventToQueue(EventImpl event) {
@@ -148,7 +163,7 @@ boolean startEventInternal(final String key) {
                 L.w("startEventInternal, eventRecorder, No timed event with the name [" + key + "] is started, nothing to end. Will ignore call.");
                 return;
             }
-            recordEventInternal(eventImpl.key, eventImpl.count, eventImpl.sum, eventImpl.duration, eventImpl.segmentation);
+            recordEventInternal(eventImpl.key, eventImpl.count, eventImpl.sum, eventImpl.duration, eventImpl.segmentation, eventImpl.id);
         }, key, L));
 
         return true;
@@ -179,7 +194,7 @@ boolean endEventInternal(final String key, final Map<String, Object> segmentatio
         long currentTimestamp = TimeUtils.timestampMs();
         double duration = (currentTimestamp - event.timestamp) / 1000.0;
 
-        recordEventInternal(key, count, sum, duration, segmentation);
+        recordEventInternal(key, count, sum, duration, segmentation, null);
         return true;
     }
 
@@ -207,7 +222,7 @@ public class Events {
          */
         public void recordEvent(String key, Map<String, Object> segmentation, int count, Double sum, Double dur) {
             L.i("[Events] recordEvent: key = " + key + ", count = " + count + ", sum = " + sum + ", segmentation = " + segmentation + ", dur = " + dur);
-            recordEventInternal(key, count, sum, dur, segmentation);
+            recordEventInternal(key, count, sum, dur, segmentation, null);
         }
 
         /**
diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleViews.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleViews.java
index 8f45c091..dd176c3e 100644
--- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleViews.java
+++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleViews.java
@@ -1,9 +1,568 @@
 package ly.count.sdk.java.internal;
 
-/**
- * Views support
- */
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import ly.count.sdk.java.Countly;
 
-public class ModuleViews extends ModuleBase {
+public class ModuleViews extends ModuleBase implements ViewIdProvider {
+    String currentViewID = null;
+    String previousViewID = null;
+    private boolean firstView = true;
+    static final String KEY_VIEW_EVENT = "[CLY]_view";
+    static final String KEY_NAME = "name";
+    static final String KEY_VISIT = "visit";
+    static final String KEY_VISIT_VALUE = "1";
+    static final String KEY_SEGMENT = "segment";
+    static final String KEY_START = "start";
+    static final String KEY_START_VALUE = "1";
+    Map<String, ViewData> viewDataMap = new LinkedHashMap<>(); // map viewIDs to its viewData
+    String[] reservedSegmentationKeysViews = new String[] { KEY_NAME, KEY_VISIT, KEY_START, KEY_SEGMENT };
+    //interface for SDK users
+    Views viewsInterface;
+    IdGenerator idGenerator;
+    Map<String, Object> globalViewSegmentation = new ConcurrentHashMap<>();
 
+    static class ViewData {
+        String viewID;
+        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.
+        Map<String, Object> viewSegmentation = new ConcurrentHashMap<>();
+    }
+
+    ModuleViews() {
+    }
+
+    @Override
+    public void init(InternalConfig config) {
+        super.init(config);
+        L.v("[ModuleViews] Initializing");
+        viewsInterface = new Views();
+
+        setGlobalViewSegmentationInternal(config.views.globalViewSegmentation);
+
+        idGenerator = config.viewIdGenerator;
+        config.viewIdProvider = this;
+    }
+
+    @Override
+    public void deviceIdChanged(String oldDeviceId, boolean withMerge) {
+        super.deviceIdChanged(oldDeviceId, withMerge);
+        L.d("[ModuleViews] deviceIdChanged: oldDeviceId = " + oldDeviceId + ", withMerge = " + withMerge);
+        if (!withMerge) {
+            stopAllViewsInternal(null);
+        }
+    }
+
+    @Override
+    public void stop(InternalConfig config, boolean clear) {
+        viewsInterface = null;
+        viewDataMap.clear();
+        if (globalViewSegmentation != null) {
+            globalViewSegmentation.clear();
+            globalViewSegmentation = null;
+        }
+    }
+
+    private void removeReservedKeysFromViewSegmentation(Map<String, Object> segmentation) {
+        if (segmentation == null) {
+            return;
+        }
+
+        for (String key : reservedSegmentationKeysViews) {
+            if (segmentation.containsKey(key)) {
+                segmentation.remove(key);
+                L.w("[ModuleViews] removeReservedKeysAndUnsupportedTypesFromViewSegmentation, You cannot use the key:[" + key + "] in your segmentation since it's reserved by the SDK");
+            }
+        }
+    }
+
+    /**
+     * Checks the provided Segmentation by the user. Sanitizes it
+     * and transfers the data into an internal Segmentation Object.
+     */
+    void setGlobalViewSegmentationInternal(@Nullable Map<String, Object> segmentation) {
+        L.d("[ModuleViews] setGlobalViewSegmentationInternal, with[" + (segmentation == null ? "null" : segmentation.size()) + "] entries");
+
+        globalViewSegmentation.clear();
+
+        if (segmentation != null && !segmentation.isEmpty()) {
+            removeReservedKeysFromViewSegmentation(segmentation);
+            Utils.removeInvalidDataFromSegments(segmentation, L);
+            globalViewSegmentation.putAll(segmentation);
+        }
+    }
+
+    public void updateGlobalViewSegmentationInternal(@Nonnull Map<String, Object> segmentation) {
+        removeReservedKeysFromViewSegmentation(segmentation);
+        Utils.removeInvalidDataFromSegments(segmentation, L);
+
+        globalViewSegmentation.putAll(segmentation);
+    }
+
+    private Map<String, Object> createViewEventSegmentation(@Nonnull ViewData vd, boolean firstView, boolean visit, Map<String, Object> customViewSegmentation) {
+        Map<String, Object> viewSegmentation = new ConcurrentHashMap<>();
+        viewSegmentation.putAll(globalViewSegmentation);
+        viewSegmentation.putAll(vd.viewSegmentation);
+
+        if (customViewSegmentation != null) {
+            viewSegmentation.putAll(customViewSegmentation);
+        }
+
+        viewSegmentation.put(KEY_NAME, vd.viewName);
+        if (visit) {
+            viewSegmentation.put(KEY_VISIT, KEY_VISIT_VALUE);
+        }
+        if (firstView) {
+            viewSegmentation.put(KEY_START, KEY_START_VALUE);
+        }
+        viewSegmentation.put(KEY_SEGMENT, internalConfig.getSdkPlatform());
+        return viewSegmentation;
+    }
+
+    private void autoCloseRequiredViews(boolean closeAllViews, Map<String, Object> customViewSegmentation) {
+        L.d("[ModuleViews] autoCloseRequiredViews");
+        List<String> viewsToRemove = new ArrayList<>();
+
+        for (Map.Entry<String, ViewData> entry : viewDataMap.entrySet()) {
+            ViewData vd = entry.getValue();
+            if (closeAllViews || vd.isAutoStoppedView) {
+                viewsToRemove.add(vd.viewID);
+            }
+        }
+
+        if (!viewsToRemove.isEmpty()) {
+            L.d("[ModuleViews] autoCloseRequiredViews, about to close [" + viewsToRemove.size() + "] views");
+        }
+
+        removeReservedKeysFromViewSegmentation(customViewSegmentation);
+        viewsToRemove.forEach(s -> stopViewWithIDInternal(s, customViewSegmentation));
+    }
+
+    /**
+     * Record a view manually, without automatic tracking
+     * or tracks a view that is not automatically tracked
+     * like a fragment, Message box or a transparent Activity
+     * with segmentation if provided. (This is the internal function)
+     *
+     * @param viewName String - name of the view
+     * @param customViewSegmentation Map<String, Object> - segmentation that will be added to the view, set 'null' if none should be added
+     * @return Returns link to Countly for call chaining
+     */
+    @Nullable String startViewInternal(@Nullable String viewName, @Nullable Map<String, Object> customViewSegmentation, boolean viewShouldBeAutomaticallyStopped) {
+
+        if (viewName == null || viewName.isEmpty()) {
+            L.e("[ModuleViews] startViewInternal, Trying to record view with null or empty view name, ignoring request");
+            return null;
+        }
+
+        removeReservedKeysFromViewSegmentation(customViewSegmentation);
+
+        int segmCount = 0;
+        if (customViewSegmentation != null) {
+            segmCount = customViewSegmentation.size();
+        }
+        L.d("[ModuleViews] Recording view with name: [" + viewName + "], previous view ID:[" + currentViewID + "] custom view segment count:[" + segmCount + "], first:[" + firstView + "], autoStop:[" + viewShouldBeAutomaticallyStopped + "]");
+
+        //stop views that should be automatically stopped
+        //no segmentation should be used in this case
+        autoCloseRequiredViews(false, null);
+
+        ViewData currentViewData = new ViewData();
+        currentViewData.viewID = idGenerator.generateId();
+        currentViewData.viewName = viewName;
+        currentViewData.viewStartTimeSeconds = TimeUtils.uniqueTimestampS();
+        currentViewData.isAutoStoppedView = viewShouldBeAutomaticallyStopped;
+
+        viewDataMap.put(currentViewData.viewID, currentViewData);
+        previousViewID = currentViewID;
+        currentViewID = currentViewData.viewID;
+
+        Map<String, Object> viewSegmentation = createViewEventSegmentation(currentViewData, firstView, true, customViewSegmentation);
+
+        if (firstView) {
+            L.d("[ModuleViews] Recording view as the first one in the session. [" + viewName + "]");
+            firstView = false;
+        }
+
+        recordView(currentViewID, 0.0, viewSegmentation);
+        return currentViewData.viewID;
+    }
+
+    protected void setFirstViewInternal(boolean firstView) {
+        this.firstView = firstView;
+    }
+
+    void stopViewWithNameInternal(@Nullable String viewName, @Nullable Map<String, Object> customViewSegmentation) {
+        String viewID = validateViewWithName(viewName, "stopViewWithNameInternal");
+        if (viewID == null) {
+            return;
+        }
+
+        stopViewWithIDInternal(viewID, customViewSegmentation);
+    }
+
+    void stopViewWithIDInternal(@Nullable String viewID, @Nullable Map<String, Object> customViewSegmentation) {
+        ViewData vd = validateViewID(viewID, "stopViewWithIDInternal");
+        if (vd == null) {
+            return;
+        }
+        removeReservedKeysFromViewSegmentation(customViewSegmentation);
+
+        L.d("[ModuleViews] View [" + vd.viewName + "], id:[" + vd.viewID + "] is getting closed, reporting duration: [" + (TimeUtils.uniqueTimestampS() - vd.viewStartTimeSeconds) + "] s, current timestamp: [" + TimeUtils.uniqueTimestampMs() + "]");
+        recordViewEndEvent(vd, customViewSegmentation, "stopViewWithIDInternal");
+
+        viewDataMap.remove(vd.viewID);
+    }
+
+    private void recordView(String id, Double duration, Map<String, Object> segmentation) {
+        ModuleEvents events = internalConfig.sdk.module(ModuleEvents.class);
+        if (events == null) {
+            L.e("[ModuleViews] recordView, events module is not initialized");
+            return;
+        }
+
+        events.recordEventInternal(KEY_VIEW_EVENT, 1, 0.0, duration, segmentation, id);
+    }
+
+    private void recordViewEndEvent(ViewData vd, @Nullable Map<String, Object> filteredCustomViewSegmentation, String viewRecordingSource) {
+        double lastElapsedDurationSeconds = 0.0;
+        //we do sanity check the time component and print error in case of problem
+        if (vd.viewStartTimeSeconds < 0) {
+            L.e("[ModuleViews] " + viewRecordingSource + ", view start time value is not normal: [" + vd.viewStartTimeSeconds + "], ignoring that duration");
+        } else if (vd.viewStartTimeSeconds == 0) {
+            L.i("[ModuleViews] " + viewRecordingSource + ", view is either paused or didn't run, ignoring start timestamp");
+        } else {
+            lastElapsedDurationSeconds = (double) (TimeUtils.uniqueTimestampS() - vd.viewStartTimeSeconds);
+        }
+
+        //only record view if the view name is not null
+        if (vd.viewName == null) {
+            L.e("[ModuleViews] " + viewRecordingSource + " , view has no internal name, ignoring it");
+            return;
+        }
+
+        Map<String, Object> segments = createViewEventSegmentation(vd, false, false, filteredCustomViewSegmentation);
+        recordView(vd.viewID, lastElapsedDurationSeconds, segments);
+    }
+
+    void pauseViewWithIDInternal(String viewID) {
+        ViewData vd = validateViewID(viewID, "pauseViewWithIDInternal");
+        if (vd == null) {
+            return;
+        }
+
+        L.d("[ModuleViews] pauseViewWithIDInternal, pausing view for ID:[" + viewID + "], name:[" + vd.viewName + "]");
+
+        if (vd.viewStartTimeSeconds == 0) {
+            L.w("[ModuleViews] pauseViewWithIDInternal, pausing a view that is already paused. ID:[" + viewID + "], name:[" + vd.viewName + "]");
+            return;
+        }
+
+        recordViewEndEvent(vd, null, "pauseViewWithIDInternal");
+
+        vd.viewStartTimeSeconds = 0;
+    }
+
+    void resumeViewWithIDInternal(String viewID) {
+        ViewData vd = validateViewID(viewID, "resumeViewWithIDInternal");
+        if (vd == null) {
+            return;
+        }
+
+        L.d("[ModuleViews] resumeViewWithIDInternal, resuming view for ID:[" + viewID + "], name:[" + vd.viewName + "]");
+
+        if (vd.viewStartTimeSeconds > 0) {
+            L.w("[ModuleViews] resumeViewWithIDInternal, resuming a view that is already running. ID:[" + viewID + "], name:[" + vd.viewName + "]");
+            return;
+        }
+
+        vd.viewStartTimeSeconds = TimeUtils.uniqueTimestampS();
+    }
+
+    void stopAllViewsInternal(Map<String, Object> viewSegmentation) {
+        L.d("[ModuleViews] stopAllViewsInternal");
+
+        autoCloseRequiredViews(true, viewSegmentation);
+    }
+
+    private ViewData validateViewID(String viewID, String function) {
+        if (viewID == null || viewID.isEmpty()) {
+            L.e("[ModuleViews] validateViewID, " + function + ", Trying to process view with null or empty view ID, ignoring request");
+            return null;
+        }
+
+        if (!viewDataMap.containsKey(viewID)) {
+            L.w("[ModuleViews] validateViewID, " + function + ",  there is no view with the provided view id");
+            return null;
+        }
+
+        ViewData vd = viewDataMap.get(viewID);
+        if (vd == null) {
+            L.e("[ModuleViews] validateViewID, " + function + ",  view id:[" + viewID + "] has a 'null' value. This should not be happening");
+        }
+
+        return vd;
+    }
+
+    private String validateViewWithName(String viewName, String function) {
+        if (viewName == null || viewName.isEmpty()) {
+            L.e("[ModuleViews] " + function + ", Trying to process the view with null or empty view name, ignoring request");
+            return null;
+        }
+
+        String viewID = null;
+
+        for (Map.Entry<String, ViewData> entry : viewDataMap.entrySet()) {
+            ViewData vd = entry.getValue();
+            if (vd != null && viewName.equals(vd.viewName)) {
+                viewID = entry.getKey();
+            }
+        }
+
+        if (viewID == null) {
+            L.e("[ModuleViews] " + function + ", No view entry found with the provided name :[" + viewName + "]");
+        }
+
+        return viewID;
+    }
+
+    void addSegmentationToViewWithNameInternal(@Nullable String viewName, @Nullable Map<String, Object> viewSegmentation) {
+        String viewID = validateViewWithName(viewName, "addSegmentationToViewWithNameInternal");
+        if (viewID == null) {
+            return;
+        }
+        addSegmentationToViewWithIDInternal(viewID, viewSegmentation);
+    }
+
+    private void addSegmentationToViewWithIDInternal(String viewID, Map<String, Object> viewSegmentation) {
+        ViewData vd = validateViewID(viewID, "addSegmentationToViewWithIdInternal");
+        if (vd == null) {
+            return;
+        }
+
+        if (viewSegmentation == null || viewSegmentation.isEmpty()) {
+            L.e("[ModuleViews] addSegmentationToViewWithIdInternal, Trying to add segmentation with null or empty view segmentation, ignoring request");
+            return;
+        }
+        removeReservedKeysFromViewSegmentation(viewSegmentation);
+        vd.viewSegmentation.putAll(viewSegmentation);
+    }
+
+    public @Nonnull String getCurrentViewId() {
+        return currentViewID == null ? "" : currentViewID;
+    }
+
+    public @Nonnull String getPreviousViewId() {
+        return previousViewID == null ? "" : previousViewID;
+    }
+
+    public class Views {
+
+        /**
+         * Record a view manually, without automatic tracking
+         * or tracks a view that is not automatically tracked
+         * like a fragment, Message box or a transparent Activity
+         *
+         * @param viewName String - name of the view
+         * @return Returns View ID
+         */
+        public String startAutoStoppedView(@Nonnull String viewName) {
+            synchronized (Countly.instance()) {
+                return startAutoStoppedView(viewName, null);
+            }
+        }
+
+        /**
+         * Record a view manually, without automatic tracking
+         * or tracks a view that is not automatically tracked
+         * like a fragment, Message box or a transparent Activity
+         * with segmentation. (This is the main function that is used)
+         *
+         * @param viewName String - name of the view
+         * @param viewSegmentation Map<String, Object> - segmentation that will be added to the view, set 'null' if none should be added
+         * @return String - view ID
+         */
+        public String startAutoStoppedView(@Nonnull String viewName, @Nullable Map<String, Object> viewSegmentation) {
+            synchronized (Countly.instance()) {
+                L.i("[Views] Calling startAutoStoppedView [" + viewName + "] sg[" + viewSegmentation + "]");
+                return startViewInternal(viewName, viewSegmentation, true);
+            }
+        }
+
+        /**
+         * Starts a view which would not close automatically (For multi view tracking)
+         *
+         * @param viewName - String
+         * @return String - View ID
+         */
+        public @Nullable String startView(@Nonnull String viewName) {
+            synchronized (Countly.instance()) {
+                L.i("[Views] Calling startView vn[" + viewName + "]");
+                return startViewInternal(viewName, null, false);
+            }
+        }
+
+        /**
+         * Starts a view which would not close automatically (For multi view tracking)
+         *
+         * @param viewName String - name of the view
+         * @param viewSegmentation Map<String, Object> - segmentation that will be added to the view, set 'null' if none should be added
+         * @return String - View ID
+         */
+        public @Nullable String startView(@Nonnull String viewName, @Nullable Map<String, Object> viewSegmentation) {
+            synchronized (Countly.instance()) {
+                L.i("[Views] Calling startView vn[" + viewName + "] sg[" + viewSegmentation + "]");
+                return startViewInternal(viewName, viewSegmentation, false);
+            }
+        }
+
+        /**
+         * Stops a view with the given name if it was open
+         *
+         * @param viewName String - view name
+         */
+        public void stopViewWithName(@Nonnull String viewName) {
+            synchronized (Countly.instance()) {
+                L.i("[Views] Calling stopViewWithName vn[" + viewName + "]");
+                stopViewWithNameInternal(viewName, null);
+            }
+        }
+
+        /**
+         * Stops a view with the given name if it was open
+         *
+         * @param viewName String - view name
+         * @param viewSegmentation Map<String, Object> - view segmentation
+         */
+        public void stopViewWithName(@Nonnull String viewName, @Nullable Map<String, Object> viewSegmentation) {
+            synchronized (Countly.instance()) {
+                L.i("[Views] Calling stopViewWithName vn[" + viewName + "] sg[" + viewSegmentation + "]");
+                stopViewWithNameInternal(viewName, viewSegmentation);
+            }
+        }
+
+        /**
+         * Stops a view with the given ID if it was open
+         *
+         * @param viewID String - view ID
+         */
+        public void stopViewWithID(@Nonnull String viewID) {
+            synchronized (Countly.instance()) {
+                L.i("[Views] Calling stopViewWithID vi[" + viewID + "]");
+                stopViewWithIDInternal(viewID, null);
+            }
+        }
+
+        /**
+         * Stops a view with the given ID if it was open
+         *
+         * @param viewID String - view ID
+         * @param viewSegmentation Map<String, Object> - view segmentation
+         */
+        public void stopViewWithID(@Nonnull String viewID, @Nullable Map<String, Object> viewSegmentation) {
+            synchronized (Countly.instance()) {
+                L.i("[Views] Calling stopViewWithID vi[" + viewID + "] sg[" + viewSegmentation + "]");
+                stopViewWithIDInternal(viewID, viewSegmentation);
+            }
+        }
+
+        /**
+         * Pauses a view with the given ID
+         *
+         * @param viewID String - view ID
+         */
+        public void pauseViewWithID(@Nonnull String viewID) {
+            synchronized (Countly.instance()) {
+                L.i("[Views] Calling pauseViewWithID vi[" + viewID + "]");
+                pauseViewWithIDInternal(viewID);
+            }
+        }
+
+        /**
+         * Resumes a view with the given ID
+         *
+         * @param viewID String - view ID
+         */
+        public void resumeViewWithID(@Nonnull String viewID) {
+            synchronized (Countly.instance()) {
+                L.i("[Views] Calling resumeViewWithID vi[" + viewID + "]");
+                resumeViewWithIDInternal(viewID);
+            }
+        }
+
+        /**
+         * Stops all views and records a segmentation if set
+         *
+         * @param viewSegmentation Map<String, Object> - view segmentation
+         */
+        public void stopAllViews(@Nullable Map<String, Object> viewSegmentation) {
+            synchronized (Countly.instance()) {
+                L.i("[Views] Calling stopAllViews sg[" + viewSegmentation + "]");
+                stopAllViewsInternal(viewSegmentation);
+            }
+        }
+
+        /**
+         * Adds segmentation to a view with the given name
+         *
+         * @param viewName String
+         * @param viewSegmentation Map<String, Object>
+         */
+        public void addSegmentationToViewWithName(@Nonnull String viewName, @Nullable Map<String, Object> viewSegmentation) {
+            synchronized (Countly.instance()) {
+                L.i("[Views] Calling addSegmentationToViewWithName vn[" + viewName + "] sg[" + viewSegmentation + "]");
+                addSegmentationToViewWithNameInternal(viewName, viewSegmentation);
+            }
+        }
+
+        /**
+         * Adds segmentation to a view with the given ID
+         *
+         * @param viewId String
+         * @param viewSegmentation Map<String, Object>
+         */
+        public void addSegmentationToViewWithID(@Nonnull String viewId, @Nullable Map<String, Object> viewSegmentation) {
+            synchronized (Countly.instance()) {
+                L.i("[Views] Calling addSegmentationToViewWithID vi[" + viewId + "] sg[" + viewSegmentation + "]");
+                addSegmentationToViewWithIDInternal(viewId, viewSegmentation);
+            }
+        }
+
+        /**
+         * Set a segmentation to be recorded with all views
+         *
+         * @param segmentation Map<String, Object> - global view segmentation
+         */
+        public void setGlobalViewSegmentation(@Nullable Map<String, Object> segmentation) {
+            synchronized (Countly.instance()) {
+                L.i("[Views] Calling setGlobalViewSegmentation sg[" + (segmentation == null ? null : segmentation.size()) + "]");
+
+                setGlobalViewSegmentationInternal(segmentation);
+            }
+        }
+
+        /**
+         * Updates the global segmentation for views
+         *
+         * @param segmentation Map<String, Object> - global view segmentation
+         */
+        public void updateGlobalViewSegmentation(@Nullable Map<String, Object> segmentation) {
+            synchronized (Countly.instance()) {
+                L.i("[Views] Calling updateGlobalViewSegmentation sg[" + (segmentation == null ? null : segmentation.size()) + "]");
+
+                if (segmentation == null) {
+                    L.w("[View] When updating segmentation values, they can't be 'null'.");
+                    return;
+                }
+
+                updateGlobalViewSegmentationInternal(segmentation);
+            }
+        }
+    }
 }
diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/SDKCore.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/SDKCore.java
index 0991a7cf..5bd32c09 100644
--- a/sdk-java/src/main/java/ly/count/sdk/java/internal/SDKCore.java
+++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/SDKCore.java
@@ -6,6 +6,7 @@
 import java.util.Map;
 import java.util.Queue;
 import java.util.TreeMap;
+import javax.annotation.Nonnull;
 import java.util.concurrent.ConcurrentHashMap;
 import javax.annotation.Nullable;
 import ly.count.sdk.java.Config;
@@ -443,6 +444,18 @@ public void init(final InternalConfig givenConfig) {
             config.eventIdGenerator = Utils::safeRandomVal;
         }
 
+        if (config.viewIdProvider == null) {
+            config.viewIdProvider = new ViewIdProvider() {
+                @Nonnull public String getCurrentViewId() {
+                    return "";
+                }
+
+                @Nonnull public String getPreviousViewId() {
+                    return "";
+                }
+            };
+        }
+
         // ModuleSessions is always enabled, even without consent
         int consents = config.getFeatures1() | CoreFeature.Sessions.getIndex();
         // build modules
@@ -741,6 +754,16 @@ public ModuleCrashes.Crashes crashes() {
         return module(ModuleCrashes.class).crashInterface;
     }
 
+    public ModuleViews.Views views() {
+        ModuleViews module = module(ModuleViews.class);
+        if (module == null) {
+            L.v("[SDKCore] views, Views feature has no consent, returning null");
+            return null;
+        }
+
+        return module.viewsInterface;
+    }
+
     public ModuleDeviceIdCore.DeviceId deviceId() {
         return module(ModuleDeviceIdCore.class).deviceIdInterface;
     }
diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/SessionImpl.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/SessionImpl.java
index a964a84c..a59dd93b 100644
--- a/sdk-java/src/main/java/ly/count/sdk/java/internal/SessionImpl.java
+++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/SessionImpl.java
@@ -311,7 +311,7 @@ public void recordEvent(Event event) {
 
         ModuleEvents eventsModule = (ModuleEvents) SDKCore.instance.module(CoreFeature.Events.getIndex());
         EventImpl eventImpl = (EventImpl) event;
-        eventsModule.recordEventInternal(eventImpl.key, eventImpl.count, eventImpl.sum, eventImpl.duration, eventImpl.segmentation);
+        eventsModule.recordEventInternal(eventImpl.key, eventImpl.count, eventImpl.sum, eventImpl.duration, eventImpl.segmentation, eventImpl.id);
     }
 
     @Override
@@ -368,6 +368,14 @@ public Session addLocation(double latitude, double longitude) {
         return this;
     }
 
+    /**
+     * Start view
+     *
+     * @param name String representing name of this View
+     * @param start whether this view is first in current application launch
+     * @return View instance
+     * @deprecated use {@link ModuleViews.Views#startView(String)} instead via {@link Countly#views()}
+     */
     public View view(String name, boolean start) {
         L.d("[SessionImpl] view: name = " + name + " start = " + start);
         if (!SDKCore.enabled(CoreFeature.Views)) {
@@ -385,6 +393,13 @@ public View view(String name, boolean start) {
         return currentView;
     }
 
+    /**
+     * Start view
+     *
+     * @param name String representing name of this View
+     * @return View instance
+     * @deprecated use {@link ModuleViews.Views#startView(String)} instead via {@link Countly#views()}
+     */
     public View view(String name) {
         return view(name, startView);
     }
diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/Utils.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/Utils.java
index e8c11b6b..20e784a6 100644
--- a/sdk-java/src/main/java/ly/count/sdk/java/internal/Utils.java
+++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/Utils.java
@@ -21,6 +21,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
 
 /**
  * Utility class
@@ -402,4 +403,27 @@ public static String safeRandomVal() {
         String b64Value = Utils.Base64.encode(value);
         return b64Value + timestamp;
     }
+
+    /**
+     * Removes invalid data types from segments
+     *
+     * @param segments to check
+     * @param L logger
+     */
+    public static void removeInvalidDataFromSegments(Map<String, Object> segments, Log L) {
+
+        if (segments == null || segments.isEmpty()) {
+            return;
+        }
+
+        List<String> toRemove = segments.entrySet().stream()
+            .filter(entry -> !Utils.isValidDataType(entry.getValue()))
+            .map(Map.Entry::getKey)
+            .collect(Collectors.toList());
+
+        toRemove.forEach(key -> {
+            L.w("[Utils] removeInvalidDataFromSegments, In segmentation Data type '" + segments.get(key) + "' of item '" + key + "' isn't valid.");
+            segments.remove(key);
+        });
+    }
 }
diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ViewIdProvider.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ViewIdProvider.java
new file mode 100644
index 00000000..a37fdd1f
--- /dev/null
+++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ViewIdProvider.java
@@ -0,0 +1,9 @@
+package ly.count.sdk.java.internal;
+
+import javax.annotation.Nonnull;
+
+public interface ViewIdProvider {
+    @Nonnull String getCurrentViewId();
+
+    @Nonnull String getPreviousViewId();
+}
diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ViewImpl.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ViewImpl.java
index 7b70fbeb..be165d3c 100644
--- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ViewImpl.java
+++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ViewImpl.java
@@ -1,5 +1,6 @@
 package ly.count.sdk.java.internal;
 
+import ly.count.sdk.java.Countly;
 import ly.count.sdk.java.Session;
 import ly.count.sdk.java.View;
 
@@ -9,18 +10,10 @@
 
 class ViewImpl implements View {
     private Log L = null;
-    static final String EVENT = "[CLY]_view";
-    static final String NAME = "name";
-    static final String VISIT = "visit";
-    static final String VISIT_VALUE = "1";
-    static final String SEGMENT = "segment";
-    static final String START = "start";
-    static final String START_VALUE = "1";
-
     final String name;
     final Session session;
-    EventImpl start;
-    boolean started, ended;
+    boolean start = false;
+    boolean stop = false;
 
     ViewImpl(Session session, String name, Log logger) {
         this.L = logger;
@@ -35,19 +28,13 @@ public void start(boolean firstView) {
             return;
         }
 
-        L.d("[ViewImpl] start: firstView = " + firstView);
-        if (started) {
+        if (start) {
+            L.w("[ViewImpl] start: View already started!");
             return;
         }
-        this.started = true;
-
-        start = (EventImpl) session.event(EVENT).addSegments(NAME, this.name, VISIT, VISIT_VALUE, SEGMENT, SDKCore.instance.config.getSdkPlatform());
-
-        if (firstView) {
-            start.addSegment(START, START_VALUE);
-        }
 
-        start.record();
+        start = true;
+        Countly.instance().views().startAutoStoppedView(name);
     }
 
     @Override
@@ -57,27 +44,16 @@ public void stop(boolean lastView) {
             return;
         }
 
-        if (start == null) {
-            L.e("[ViewImpl] stop: We are trying to end a view that has not been started.");
+        if (stop) {
+            L.w("[ViewImpl] stop: View already stopped!");
             return;
         }
-
-        L.d("[ViewImpl] stop: lastView = " + lastView);
-
-        if (ended) {
-            return;
+        stop = true;
+        Countly.instance().views().stopViewWithName(name);
+        ModuleViews viewsModule = (ModuleViews) SDKCore.instance.module(CoreFeature.Views.getIndex());
+        if (viewsModule != null) {
+            viewsModule.setFirstViewInternal(lastView);
         }
-        ended = true;
-        EventImpl event = (EventImpl) session.event(EVENT).addSegments(NAME, this.name, SEGMENT, SDKCore.instance.config.getSdkPlatform());
-
-        long startTs = start.getTimestamp();
-        long endTs = TimeUtils.timestampMs();
-
-        long viewDurationSeconds = (endTs - startTs) / 1000;
-
-        event.setDuration(viewDurationSeconds);
-
-        event.record();
     }
 
     @Override
@@ -85,9 +61,6 @@ public String toString() {
         return "ViewImpl{" +
             "name='" + name + '\'' +
             ", session=" + session +
-            ", start=" + start +
-            ", started=" + started +
-            ", ended=" + ended +
             '}';
     }
 }
diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/EventQueueTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/EventQueueTests.java
index cad1f0a6..dd33ba11 100644
--- a/sdk-java/src/test/java/ly/count/sdk/java/internal/EventQueueTests.java
+++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/EventQueueTests.java
@@ -177,12 +177,12 @@ public void restoreFromDisk() throws IOException {
         init(TestUtils.getConfigEvents(2));
 
         TestUtils.validateEQSize(0, eventQueue);
-        writeToEventQueue("{\"hour\":10,\"count\":1,\"dow\":4,\"key\":\"test-joinEvents-1\",\"timestamp\":1695887006647}:::{\"hour\":10,\"count\":1,\"dow\":4,\"key\":\"test-joinEvents-2\",\"timestamp\":1695887006657}", false);
+        writeToEventQueue("{\"id\":\"id\",\"cvid\":\"cvid\",\"pvid\":\"pvid\",\"peid\":\"peid\",\"hour\":10,\"count\":1,\"dow\":4,\"key\":\"test-joinEvents-1\",\"timestamp\":1695887006647}:::{\"hour\":10,\"count\":1,\"dow\":4,\"key\":\"test-joinEvents-2\",\"timestamp\":1695887006657}", false);
 
         eventQueue.restoreFromDisk();
         TestUtils.validateEQSize(2, eventQueue);
-        validateEvent(eventQueue.eventQueueMemoryCache.get(0), "test-joinEvents-1", null, 1, null, null);
-        validateEvent(eventQueue.eventQueueMemoryCache.get(1), "test-joinEvents-2", null, 1, null, null);
+        validateEvent(eventQueue.eventQueueMemoryCache.get(0), "test-joinEvents-1", null, 1, null, null, "id", "pvid", "cvid", "peid");
+        validateEvent(eventQueue.eventQueueMemoryCache.get(1), "test-joinEvents-2", null, 1, null, null, null, null, null, null);
     }
 
     /**
@@ -247,7 +247,7 @@ static void writeToEventQueue(String fileContent, boolean delete) throws IOExcep
     }
 
     private EventImpl createEvent(String key, Map<String, Object> segmentation, int count, Double sum, Double dur) {
-        return new EventImpl(key, count, sum, dur, segmentation, L);
+        return new EventImpl(key, count, sum, dur, segmentation, L, null, null, null, null);
     }
 
     public static void validateEventInQueue(String key, Map<String, Object> segmentation,
diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleDeviceIdTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleDeviceIdTests.java
index e8313eae..a0882287 100644
--- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleDeviceIdTests.java
+++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleDeviceIdTests.java
@@ -458,10 +458,10 @@ private void validateDeviceIdWithoutMergeChange(final int rqSize, String oldDevi
                 viewSegmentation.put("name", TestUtils.keysValues[1]);
                 viewSegmentation.put("start", "1");
                 viewSegmentation.put("visit", "1");
-                TestUtils.validateEvent(existingEvents.get(1), "[CLY]_view", viewSegmentation, 1, null, null); // view start event
+                TestUtils.validateEvent(existingEvents.get(1), "[CLY]_view", viewSegmentation, 1, 0.0, null, "_CLY_", "", null, null); // view start event
                 viewSegmentation.remove("start");
                 viewSegmentation.remove("visit");
-                TestUtils.validateEvent(existingEvents.get(3), "[CLY]_view", viewSegmentation, 1, null, 1.0); // view stop event
+                TestUtils.validateEvent(existingEvents.get(3), "[CLY]_view", viewSegmentation, 1, 0.0, 1.0, "_CLY_", "", null, null); // view stop event
                 remainingRequestIndex++;
             }
         } catch (NullPointerException ignored) {
@@ -591,8 +591,12 @@ private void setupEvent() {
     private List<EventImpl> validateEvents(int requestIndex, String deviceId, int timedEventIdx) {
         List<EventImpl> existingEvents = TestUtils.readEventsFromRequest(requestIndex, deviceId);
         if (!existingEvents.isEmpty()) {
-            TestUtils.validateEvent(existingEvents.get(0), TestUtils.keysValues[2], null, 1, null, null); // casual event
-            TestUtils.validateEvent(existingEvents.get(timedEventIdx), TestUtils.keysValues[0], null, 1, null, 1.0); // timed event
+            String expectedCVId = "";
+            if (existingEvents.size() > 2) {
+                expectedCVId = existingEvents.get(3).id;
+            }
+            TestUtils.validateEvent(existingEvents.get(0), TestUtils.keysValues[2], null, 1, null, null, "_CLY_", null, "", null); // casual event
+            TestUtils.validateEvent(existingEvents.get(timedEventIdx), TestUtils.keysValues[0], null, 1, null, 1.0, "_CLY_", null, expectedCVId, existingEvents.get(0).id); // timed event
         }
 
         return existingEvents;
diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleEventsTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleEventsTests.java
index 688ee6c7..17e556a8 100644
--- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleEventsTests.java
+++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleEventsTests.java
@@ -51,7 +51,7 @@ public void recordEvent() {
         Countly.instance().events().recordEvent(eKeys[0], segmentation, 1, 45.9, 32.0);
 
         //check if event was recorded correctly and size of event queue is equal to size of events in queue
-        TestUtils.validateEventInEQ(eKeys[0], segmentation, 1, 45.9, 32.0, 0, 1);
+        TestUtils.validateEventInEQ(eKeys[0], segmentation, 1, 45.9, 32.0, 0, 1, "_CLY_", null, "", null);
     }
 
     /**
@@ -75,8 +75,11 @@ public void recordEvent_queueSizeOver() {
         Assert.assertEquals(1, TestUtils.getCurrentRQ().length);
 
         List<EventImpl> eventsInRequest = TestUtils.readEventsFromRequest();
-        validateEvent(eventsInRequest.get(0), eKeys[0], null, 1, 45.9, 32.0);
-        validateEvent(eventsInRequest.get(1), eKeys[1], null, 1, 45.9, 32.0);
+
+        // check first event has no peid and has an SDK generated id by giving "_CLY_" flag to the function
+        validateEvent(eventsInRequest.get(0), eKeys[0], null, 1, 45.9, 32.0, "_CLY_", null, "", null);
+        // check second event has peid of first event and has an SDK generated id by giving "_CLY_" flag to the function
+        validateEvent(eventsInRequest.get(1), eKeys[1], null, 1, 45.9, 32.0, "_CLY_", null, "", eventsInRequest.get(0).id);
     }
 
     /**
@@ -98,7 +101,7 @@ public void recordEvent_queueSizeOverMemory() throws IOException {
         List<EventImpl> eventsInRequest = TestUtils.readEventsFromRequest();
         validateEvent(eventsInRequest.get(0), "test-joinEvents-1", null, 5, null, null);
         validateEvent(eventsInRequest.get(1), "test-joinEvents-2", null, 1, null, null);
-        validateEvent(eventsInRequest.get(2), eKeys[0], null, 1, 45.9, 32.0);
+        validateEvent(eventsInRequest.get(2), eKeys[0], null, 1, 45.9, 32.0, "_CLY_", null, "", null);
     }
 
     /**
@@ -168,7 +171,7 @@ public void recordEvent_invalidSegment() {
         //record event with key segmentation
         Countly.instance().events().recordEvent(eKeys[0], segmentation);
 
-        TestUtils.validateEventInEQ(eKeys[0], expectedSegmentation, 1, null, null, 0, 1);
+        TestUtils.validateEventInEQ(eKeys[0], expectedSegmentation, 1, null, null, 0, 1, "_CLY_", null, "", null);
     }
 
     /**
@@ -191,7 +194,7 @@ public void startEvent() {
         endEvent(eKeys[0], null, 1, null);
 
         Assert.assertEquals(0, moduleEvents.timedEvents.size());
-        TestUtils.validateEventInEQ(eKeys[0], null, 1, null, 0.0, 0, 1);
+        TestUtils.validateEventInEQ(eKeys[0], null, 1, null, 0.0, 0, 1, "_CLY_", null, "", null);
     }
 
     /**
@@ -248,7 +251,7 @@ public void startEvent_alreadyStarted() {
         endEvent(eKeys[0], null, 1, null);
 
         Assert.assertEquals(0, moduleEvents.timedEvents.size());
-        TestUtils.validateEventInEQ(eKeys[0], null, 1, null, 0.0, 0, 1);
+        TestUtils.validateEventInEQ(eKeys[0], null, 1, null, 0.0, 0, 1, "_CLY_", null, "", null);
     }
 
     /**
@@ -313,12 +316,13 @@ public void endEvent_withSegmentation() {
         Map<String, Object> segmentation = new ConcurrentHashMap<>();
         segmentation.put("hair_color", "red");
         segmentation.put("hair_length", "short");
-        segmentation.put("chauffeur", "g3chauffeur"); //
+        segmentation.put("chauffeur", "g3chauffeur");
 
         endEvent(eKeys[0], segmentation, 1, 5.0);
 
         Assert.assertEquals(0, moduleEvents.timedEvents.size());
-        TestUtils.validateEventInEQ(eKeys[0], segmentation, 1, 5.0, 0.0, 0, 1);
+        // check event has desired segmentation, no peid and a sdk generated id by giving "_CLY_" flag to the function
+        TestUtils.validateEventInEQ(eKeys[0], segmentation, 1, 5.0, 0.0, 0, 1, "_CLY_", null, "", null);
     }
 
     /**
@@ -414,7 +418,9 @@ public void cancelEvent() {
 
     @Test
     public void timedEventFlow() throws InterruptedException {
-        init(TestUtils.getConfigEvents(4));
+        InternalConfig config = new InternalConfig(TestUtils.getConfigEvents(4));
+        config.eventIdGenerator = TestUtils.idGenerator();
+        init(config);
         validateTimedEventSize(0, 0);
 
         startEvent(eKeys[0]); // start event to end it
@@ -428,12 +434,12 @@ public void timedEventFlow() throws InterruptedException {
         endEvent(eKeys[1], null, 3, 15.0);
 
         Assert.assertEquals(1, moduleEvents.timedEvents.size());
-        TestUtils.validateEventInEQ(eKeys[1], null, 3, 15.0, 1.0, 0, 1);
+        TestUtils.validateEventInEQ(eKeys[1], null, 3, 15.0, 1.0, 0, 1, TestUtils.keysValues[0], null, "", null);
 
         endEvent(eKeys[0], null, 2, 4.0);
 
         Assert.assertEquals(0, moduleEvents.timedEvents.size());
-        TestUtils.validateEventInEQ(eKeys[0], null, 2, 4.0, 2.0, 1, 2);
+        TestUtils.validateEventInEQ(eKeys[0], null, 2, 4.0, 2.0, 1, 2, TestUtils.keysValues[1], null, "", TestUtils.keysValues[0]);
     }
 
     private void validateTimedEventSize(int expectedQueueSize, int expectedTimedEventSize) {
diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java
index 21110ac2..815dbbdd 100644
--- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java
+++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java
@@ -591,10 +591,10 @@ private Map<String, Object> requiredWidgetSegmentation(String widgetId, Map<Stri
     }
 
     void feedbackValidateManualResultEQ(String eventKey, String widgetID, Map<String, Object> feedbackWidgetResult, int eqIndex) {
-        validateEvent(TestUtils.getCurrentEQ().get(eqIndex), eventKey, requiredWidgetSegmentation(widgetID, feedbackWidgetResult), 1, null, null);
+        validateEvent(TestUtils.getCurrentEQ().get(eqIndex), eventKey, requiredWidgetSegmentation(widgetID, feedbackWidgetResult), 1, null, null, "_CLY_", null, "", null);
     }
 
     void feedbackValidateManualResultRQ(String eventKey, String widgetID, Map<String, Object> feedbackWidgetResult, int eqIndex) {
-        validateEvent(TestUtils.readEventsFromRequest().get(eqIndex), eventKey, requiredWidgetSegmentation(widgetID, feedbackWidgetResult), 1, null, null);
+        validateEvent(TestUtils.readEventsFromRequest().get(eqIndex), eventKey, requiredWidgetSegmentation(widgetID, feedbackWidgetResult), 1, null, null, "_CLY_", null, "", null);
     }
 }
diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleViewsTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleViewsTests.java
new file mode 100644
index 00000000..3575f17e
--- /dev/null
+++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleViewsTests.java
@@ -0,0 +1,858 @@
+package ly.count.sdk.java.internal;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import ly.count.sdk.java.Config;
+import ly.count.sdk.java.Countly;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ModuleViewsTests {
+
+    @Before
+    public void beforeTest() {
+        TestUtils.createCleanTestState();
+    }
+
+    @After
+    public void afterTest() {
+        Countly.instance().halt();
+    }
+
+    // Non-existing views
+
+    /**
+     * "stopViewWithName" with non-existing view
+     * Validating that "stopViewWithName" with non-existing view doesn't generate any event
+     * No event should be generated
+     */
+    @Test
+    public void stopViewWithName_nonExisting() {
+        Countly.instance().init(TestUtils.getBaseConfig().enableFeatures(Config.Feature.Views, Config.Feature.Events));
+        TestUtils.validateEQSize(0);
+        Countly.instance().views().stopViewWithName(TestUtils.keysValues[0]);
+        TestUtils.validateEQSize(0);
+    }
+
+    /**
+     * "stopViewWithID" with non-existing view
+     * Validating that "stopViewWithID" with non-existing view doesn't generate any event
+     * No event should be generated
+     */
+    @Test
+    public void stopViewWithID_nonExisting() {
+        Countly.instance().init(TestUtils.getBaseConfig().enableFeatures(Config.Feature.Views, Config.Feature.Events));
+        TestUtils.validateEQSize(0);
+        Countly.instance().views().stopViewWithID(TestUtils.keysValues[0]);
+        TestUtils.validateEQSize(0);
+    }
+
+    /**
+     * "pauseViewWithID" with non-existing view
+     * Validating that "pauseViewWithID" with non-existing view doesn't generate any event
+     * No event should be generated
+     */
+    @Test
+    public void pauseViewWithID_nonExisting() {
+        Countly.instance().init(TestUtils.getBaseConfig().enableFeatures(Config.Feature.Views, Config.Feature.Events));
+        TestUtils.validateEQSize(0);
+        Countly.instance().views().pauseViewWithID(TestUtils.keysValues[0]);
+        TestUtils.validateEQSize(0);
+    }
+
+    /**
+     * "resumeViewWithID" with non-existing view
+     * Validating that "resumeViewWithID" with non-existing view doesn't generate any event
+     * No event should be generated
+     */
+    @Test
+    public void resumeViewWithID_nonExisting() {
+        Countly.instance().init(TestUtils.getBaseConfig().enableFeatures(Config.Feature.Views, Config.Feature.Events));
+        TestUtils.validateEQSize(0);
+        Countly.instance().views().resumeViewWithID(TestUtils.keysValues[0]);
+        TestUtils.validateEQSize(0);
+    }
+
+    /**
+     * "addSegmentationToView" with non-existing view
+     * Validating that "addSegmentationToView" with non-existing view doesn't generate any event
+     * No event should be generated
+     */
+    @Test
+    public void addSegmentationToView_nonExisting() {
+        Countly.instance().init(TestUtils.getBaseConfig().enableFeatures(Config.Feature.Views, Config.Feature.Events));
+        TestUtils.validateEQSize(0);
+        Countly.instance().views().addSegmentationToViewWithName(TestUtils.keysValues[0], TestUtils.map("a", 1, "b", 2));
+        TestUtils.validateEQSize(0);
+        Countly.instance().views().addSegmentationToViewWithID(TestUtils.keysValues[0], TestUtils.map("a", 1, "b", 2));
+        TestUtils.validateEQSize(0);
+    }
+
+    // Providing bad values
+
+    /**
+     * "startView" with null and empty names
+     * Both "startView" calls should be ignored and no event should be generated
+     * No event should be existed in the EQ
+     */
+    @Test
+    public void startView_nullAndEmpty() {
+        Countly.instance().init(TestUtils.getBaseConfig().enableFeatures(Config.Feature.Views, Config.Feature.Events));
+        badValueTrier(Countly.instance().views()::startView, Countly.instance().views()::startView);
+    }
+
+    /**
+     * "startAutoStoppedView" with null and empty names
+     * Both "startAutoStoppedView" calls should be ignored and no event should be generated
+     * No event should be existed in the EQ
+     */
+    @Test
+    public void startAutoStoppedView_nullAndEmpty() {
+        Countly.instance().init(TestUtils.getBaseConfig().enableFeatures(Config.Feature.Views, Config.Feature.Events));
+        badValueTrier(Countly.instance().views()::startAutoStoppedView, Countly.instance().views()::startAutoStoppedView);
+    }
+
+    /**
+     * "stopViewWithName" with null and empty names
+     * Both "stopViewWithName" calls should be ignored and no event should be generated
+     * No event should be existed in the EQ
+     */
+    @Test
+    public void stopViewWithName_nullAndEmpty() {
+        Countly.instance().init(TestUtils.getBaseConfig().enableFeatures(Config.Feature.Views, Config.Feature.Events));
+        badValueTrier(Countly.instance().views()::stopViewWithName, Countly.instance().views()::stopViewWithName);
+    }
+
+    /**
+     * "stopViewWithID" with null and empty names
+     * Both "stopViewWithName" calls should be ignored and no event should be generated
+     * No event should be existed in the EQ
+     */
+    @Test
+    public void stopViewWithID_nullAndEmpty() {
+        Countly.instance().init(TestUtils.getBaseConfig().enableFeatures(Config.Feature.Views, Config.Feature.Events));
+        badValueTrier(Countly.instance().views()::stopViewWithID, Countly.instance().views()::stopViewWithID);
+    }
+
+    /**
+     * "pauseViewWithID" with null and empty names
+     * Both "pauseViewWithID" calls should be ignored and no event should be generated
+     * No event should be existed in the EQ
+     */
+    @Test
+    public void pauseViewWithID_nullAndEmpty() {
+        Countly.instance().init(TestUtils.getBaseConfig().enableFeatures(Config.Feature.Views, Config.Feature.Events));
+        badValueTrier(Countly.instance().views()::pauseViewWithID, null);
+    }
+
+    /**
+     * "resumeViewWithID" with null and empty names
+     * Both "resumeViewWithID" calls should be ignored and no event should be generated
+     * No event should be existed in the EQ
+     */
+    @Test
+    public void resumeViewWithID_nullAndEmpty() {
+        Countly.instance().init(TestUtils.getBaseConfig().enableFeatures(Config.Feature.Views, Config.Feature.Events));
+        badValueTrier(Countly.instance().views()::resumeViewWithID, null);
+    }
+
+    /**
+     * "addSegmentationToViewWithID" with null and empty names
+     * Both "addSegmentationToViewWithID" calls should be ignored and no event should be generated
+     * No event should be existed in the EQ
+     */
+    @Test
+    public void addSegmentationToViewWithID_nullAndEmpty() {
+        Countly.instance().init(TestUtils.getBaseConfig().enableFeatures(Config.Feature.Views, Config.Feature.Events));
+        badValueTrier(null, Countly.instance().views()::addSegmentationToViewWithID);
+    }
+
+    /**
+     * "addSegmentationToViewWithName" with null and empty names
+     * Both "addSegmentationToViewWithName" calls should be ignored and no event should be generated
+     * No event should be existed in the EQ
+     */
+    @Test
+    public void addSegmentationToViewWithName_nullAndEmpty() {
+        Countly.instance().init(TestUtils.getBaseConfig().enableFeatures(Config.Feature.Views, Config.Feature.Events));
+        badValueTrier(null, Countly.instance().views()::addSegmentationToViewWithName);
+    }
+
+    private void badValueTrier(Consumer<String> idNameViewFunction, BiConsumer<String, Map<String, Object>> idNameSegmentViewFunction) {
+        TestUtils.validateEQSize(0);
+        if (idNameViewFunction != null) {
+            idNameViewFunction.accept(null);
+            TestUtils.validateEQSize(0);
+            idNameViewFunction.accept("");
+            TestUtils.validateEQSize(0);
+        }
+        if (idNameSegmentViewFunction != null) {
+            idNameSegmentViewFunction.accept(null, null);
+            TestUtils.validateEQSize(0);
+            idNameSegmentViewFunction.accept("", null);
+            TestUtils.validateEQSize(0);
+        }
+    }
+
+    // A simple flow
+
+    /**
+     * <pre>
+     * Make sure all the basic functions are working correctly and we are keeping time correctly
+     *
+     * 1- start view A
+     * 2- start view B
+     * 3- wait a moment
+     * 4- pause view A
+     * 5- wait a moment
+     * 6- resume view A
+     * 7- stop view with stopViewWithName/stopViewWithID/stopAllViews
+     * 8- Stop view B if needed
+     * 9- make sure the summary time is correct and two events are recorded
+     * </pre>
+     */
+    @Test
+    public void simpleFlow() throws InterruptedException {
+        Countly.instance().init(TestUtils.getConfigViews());
+        TestUtils.validateEQSize(0);
+
+        Map<String, Object> customSegmentationA = TestUtils.map("count", 56, "start", "1", "visit", "1", "name", TestUtils.keysValues[0], "segment", TestUtils.keysValues[1]);
+        String viewA = Countly.instance().views().startView("A", customSegmentationA);
+        TestUtils.validateEQSize(1);
+        Map<String, Object> customSegmentationB = TestUtils.map("gone", true, "lev", 78.91, "map", TestUtils.map("a", 1, "b", 2));
+        Countly.instance().views().startView("B", customSegmentationB);
+        Thread.sleep(1000);
+        Countly.instance().views().pauseViewWithID(viewA);
+        Thread.sleep(1000);
+        Countly.instance().views().resumeViewWithID(viewA);
+        Countly.instance().views().stopAllViews(null);
+        TestUtils.validateEQSize(5);
+
+        validateView("A", 0.0, 0, 5, true, true, TestUtils.map("count", 56), "idv1", "");
+        validateView("B", 0.0, 1, 5, false, true, TestUtils.map("gone", true, "lev", BigDecimal.valueOf(78.91)), "idv2", "idv1");
+        validateView("A", 1.0, 2, 5, false, false, null, "idv1", "idv1");
+        validateView("A", 0.0, 3, 5, false, false, null, "idv1", "idv1");
+        validateView("B", 2.0, 4, 5, false, false, null, "idv2", "idv1");
+    }
+
+    /**
+     * <pre>
+     * Validate the interaction of "startView" and "startAutoStoppedView". "startAutoStoppedView" should be automatically stopped when calling "startView", but not the other way around
+     *
+     * startView A
+     * startAutoStoppedView
+     * startView B
+     * make sure that at this point there are 4 events, 3 starting and 1 closing.
+     *
+     * stopViewWithName A
+     * stopViewWithID B
+     * </pre>
+     */
+    @Test
+    public void mixedTestFlow1() throws InterruptedException {
+        Countly.instance().init(TestUtils.getConfigViews());
+        TestUtils.validateEQSize(0);
+
+        Map<String, Object> customSegmentationA = TestUtils.map("money", 238746798234739L, "start", "1", "visit", "1", "name", TestUtils.keysValues[0], "segment", TestUtils.keysValues[1]);
+        Map<String, Object> customSegmentationB = TestUtils.map("gone_to", "Wall Sina", "map", TestUtils.map("titan", true, "level", 65));
+
+        Countly.instance().views().startView("A", customSegmentationA);
+        Countly.instance().views().startAutoStoppedView("AutoStopped", customSegmentationB);
+        Thread.sleep(1000);
+        String viewB = Countly.instance().views().startView("B");
+
+        TestUtils.validateEQSize(4);
+
+        validateView("A", 0.0, 0, 4, true, true, TestUtils.map("money", 238746798234739L), "idv1", ""); // starting
+        validateView("AutoStopped", 0.0, 1, 4, false, true, TestUtils.map("gone_to", "Wall Sina"), "idv2", "idv1"); // starting
+        validateView("AutoStopped", 1.0, 2, 4, false, false, null, "idv2", "idv1"); // closing
+        validateView("B", 0.0, 3, 4, false, true, null, "idv3", "idv2"); // starting
+
+        Countly.instance().views().stopViewWithName("A");
+        Countly.instance().views().stopViewWithID(viewB);
+        TestUtils.validateEQSize(6);
+
+        validateView("A", 1.0, 4, 6, false, false, null, "idv1", "idv2"); // closing
+        validateView("B", 0.0, 5, 6, false, false, null, "idv3", "idv2"); // closing
+    }
+
+    /**
+     * <pre>
+     * Make sure we can use view actions with the auto stopped ones
+     *
+     * startAutoStoppedView A
+     * wait a moment
+     * pause view
+     * wait a moment
+     * resume view
+     * stop view with stopViewWithName/stopViewWithID
+     * </pre>
+     */
+    @Test
+    public void useWithAutoStoppedOnes() throws InterruptedException {
+        Countly.instance().init(TestUtils.getConfigViews());
+        TestUtils.validateEQSize(0);
+
+        Map<String, Object> customSegmentationA = TestUtils.map("power_percent", 56.7f, "start", "1", "visit", "1", "name", TestUtils.keysValues[0], "segment", TestUtils.keysValues[1]);
+
+        String viewA = Countly.instance().views().startAutoStoppedView("A", customSegmentationA);
+        Thread.sleep(1000);
+        Countly.instance().views().pauseViewWithID(viewA);
+        Thread.sleep(1000);
+        Countly.instance().views().resumeViewWithID(viewA);
+        Countly.instance().views().stopViewWithName("A", null);
+
+        TestUtils.validateEQSize(3);
+
+        validateView("A", 0.0, 0, 3, true, true, TestUtils.map("power_percent", BigDecimal.valueOf(56.7)), "idv1", ""); // starting
+        validateView("A", 1.0, 1, 3, false, false, null, "idv1", ""); // starting
+        validateView("A", 0.0, 2, 3, false, false, null, "idv1", ""); // closing
+    }
+
+    /**
+     * <pre>
+     * Validate segmentation
+     * Just make sure the values are used
+     *
+     * startView A with segmentation
+     * make sure the correct things are added
+     *
+     * startView B with segmentation
+     * make sure the correct things are added
+     *
+     * Stop A with no segmentation
+     *
+     * Stop B with segmentation
+     *
+     * again make sure that segmentation is correctly applied
+     * </pre>
+     */
+    @Test
+    public void validateSegmentation1() {
+        Countly.instance().init(TestUtils.getConfigViews());
+        TestUtils.validateEQSize(0);
+
+        Map<String, Object> customSegmentationA = TestUtils.map("FigmaId", "YXNkOThhZnM=", "start", "1", "visit", "1", "name", TestUtils.keysValues[0], "segment", TestUtils.keysValues[1]);
+        Map<String, Object> customSegmentationB = TestUtils.map("FigmaId", "OWE4cZdkOWFz", "start", "1", "end", "1", "name", TestUtils.keysValues[2], "segment", TestUtils.keysValues[3]);
+
+        String viewA = Countly.instance().views().startView("A", customSegmentationA);
+        validateView("A", 0.0, 0, 1, true, true, TestUtils.map("FigmaId", "YXNkOThhZnM="), "idv1", ""); // starting
+
+        Countly.instance().views().startView("B", customSegmentationB);
+        validateView("B", 0.0, 1, 2, false, true, TestUtils.map("FigmaId", "OWE4cZdkOWFz", "end", "1"), "idv2", "idv1"); // starting
+
+        Countly.instance().views().stopViewWithID(viewA, null);
+        validateView("A", 0.0, 2, 3, false, false, null, "idv1", "idv1"); // closing
+
+        Countly.instance().views().stopViewWithName("B", TestUtils.map("ClickCount", 45));
+        validateView("B", 0.0, 3, 4, false, false, TestUtils.map("ClickCount", 45), "idv2", "idv1"); // closing
+    }
+
+    /**
+     * <pre>
+     * Validate segmentation 2
+     *
+     * - startView A
+     * - startView B
+     * - stopAllViews with segmentation
+     *
+     * make sure that the stop segmentation was added to all views
+     * </pre>
+     */
+    @Test
+    public void validateSegmentation2() {
+        Countly.instance().init(TestUtils.getConfigViews());
+        TestUtils.validateEQSize(0);
+
+        Countly.instance().views().startView("A");
+        Countly.instance().views().startView("B");
+        validateView("A", 0.0, 0, 2, true, true, null, "idv1", "");
+        validateView("B", 0.0, 1, 2, false, true, null, "idv2", "idv1");
+
+        Map<String, Object> allSegmentation = TestUtils.map("Copyright", "Countly", "AppExit", true, "DestroyToken", false, "ExitedAt", 1702975890000L);
+        Countly.instance().views().stopAllViews(allSegmentation);
+
+        validateView("A", 0.0, 2, 4, false, false, allSegmentation, "idv1", "idv1");
+        validateView("B", 0.0, 3, 4, false, false, allSegmentation, "idv2", "idv1");
+    }
+
+    /**
+     * <h3> Validate segmentation does not override internal keys </h2>
+     * <pre>
+     * Internal keys: "name", "start", "visit", "segment"
+     *
+     * - Start view and provide segmentation with internal keys
+     * - Stop view and provide segmentation with internal keys
+     * make sure that internal keys are not overridden at any point
+     * </pre>
+     */
+    @Test
+    public void validateSegmentation_internalKeys() {
+        Countly.instance().init(TestUtils.getConfigViews());
+        TestUtils.validateEQSize(0);
+
+        Map<String, Object> internalKeysSegmentation = TestUtils.map("start", "YES", "name", TestUtils.keysValues[0], "visit", "YES", "segment", TestUtils.keysValues[1]);
+
+        Countly.instance().views().startView("A", TestUtils.map(internalKeysSegmentation, "ultimate", "YES"));
+        Countly.instance().views().stopViewWithName("A", TestUtils.map(internalKeysSegmentation, "end", "Unfortunately", "time", 1234567890L));
+        validateView("A", 0.0, 0, 2, true, true, TestUtils.map("ultimate", "YES"), "idv1", "");
+        validateView("A", 0.0, 1, 2, false, false, TestUtils.map("end", "Unfortunately", "time", 1234567890), "idv1", "");
+    }
+
+    /**
+     * <pre>
+     * Try add segmentation to view functions with internal keys
+     *
+     * - start view A with segmentation - validate that event is created
+     * - Add segmentation to view with name A - with internal keys + valid param
+     * - pause view A - validate that segmentation is not empty and only valid segmentation is added and internal keys are not overridden
+     * - Add segmentation to view A with ID - with internal keys + valid param
+     * - stop view with ID A - validate that segmentation is not empty and only valid segmentation is added and internal keys are not overridden
+     *
+     * </pre>
+     */
+    @Test
+    public void addSegmentationToView_internalKeys() {
+        Countly.instance().init(TestUtils.getConfigViews());
+        TestUtils.validateEQSize(0);
+
+        Map<String, Object> internalKeysSegmentation = TestUtils.map("start", "YES", "name", TestUtils.keysValues[0], "visit", "YES", "segment", TestUtils.keysValues[1]);
+
+        String viewIDA = Countly.instance().views().startView("A");
+        validateView("A", 0.0, 0, 1, true, true, null, "idv1", "");
+        Countly.instance().views().addSegmentationToViewWithName("A", TestUtils.map(internalKeysSegmentation, "aniki", "HAVE"));
+        Countly.instance().views().pauseViewWithID(viewIDA);
+        validateView("A", 0.0, 1, 2, false, false, TestUtils.map("aniki", "HAVE"), "idv1", "");
+
+        Countly.instance().views().addSegmentationToViewWithID(viewIDA, TestUtils.map(internalKeysSegmentation, "oni-chan", "HAVE"));
+        Countly.instance().views().stopViewWithID(viewIDA);
+        validateView("A", 0.0, 2, 3, false, false, TestUtils.map("aniki", "HAVE", "oni-chan", "HAVE"), "idv1", "");
+    }
+
+    /**
+     * <pre>
+     * Try add segmentation to view functions with null and empty values
+     *
+     * - start view A with segmentation + some internal keys - validate event is created and only valid segmentation is added
+     * - Add segmentation to view with name A - with a param
+     * - Add segmentation to view with name A - null
+     * - pause view A - validate that segmentation is not empty and first call added the segmentation
+     * - Add segmentation to view with ID A - with a param
+     * - Add segmentation to view with ID A - empty
+     * - stop view A - validate that segmentation is not empty and added segmentations are exists and not overridden by null and empty values
+     *
+     * </pre>
+     */
+    @Test
+    public void addSegmentationToView_nullEmpty() {
+        Countly.instance().init(TestUtils.getConfigViews());
+        TestUtils.validateEQSize(0);
+
+        Map<String, Object> viewSegmentation = TestUtils.map("name", "A", "segment", TestUtils.getOS(), "arr", new ArrayList<>(), "done", true);
+
+        String viewIDA = Countly.instance().views().startView("A", viewSegmentation);
+        validateView("A", 0.0, 0, 1, true, true, TestUtils.map("done", true), "idv1", "");
+        Countly.instance().views().addSegmentationToViewWithName("A", TestUtils.map("a", 1));
+        Countly.instance().views().addSegmentationToViewWithName("A", null);
+        Countly.instance().views().pauseViewWithID(viewIDA);
+        validateView("A", 0.0, 1, 2, false, false, TestUtils.map("a", 1), "idv1", "");
+
+        Countly.instance().views().addSegmentationToViewWithID(viewIDA, TestUtils.map("b", 2));
+        Countly.instance().views().addSegmentationToViewWithID(viewIDA, TestUtils.map());
+        Countly.instance().views().stopViewWithID(viewIDA);
+        validateView("A", 0.0, 2, 3, false, false, TestUtils.map("a", 1, "b", 2), "idv1", "");
+    }
+
+    /**
+     * <pre>
+     * Add segmentation to view with init given global segmentation
+     *
+     * - start view A with none segmentation
+     * - Add segmentation to view A
+     * - pause view A - validate that segmentation is added
+     * - stop view A - validate that segmentation is also added to stop view event
+     *
+     * </pre>
+     */
+    @Test
+    public void addSegmentationToView() {
+        Countly.instance().init(TestUtils.getConfigViews(TestUtils.map("glob", "al")));
+        TestUtils.validateEQSize(0);
+        String viewIDA = Countly.instance().views().startView("A");
+        validateView("A", 0.0, 0, 1, true, true, TestUtils.map("glob", "al"), "idv1", "");
+        Countly.instance().views().addSegmentationToViewWithName("A", TestUtils.map("a", 1, "b", 2));
+        Countly.instance().views().pauseViewWithID(viewIDA);
+        validateView("A", 0.0, 1, 2, false, false, TestUtils.map("a", 1, "b", 2, "glob", "al"), "idv1", "");
+        Countly.instance().views().stopViewWithID(viewIDA);
+        validateView("A", 0.0, 2, 3, false, false, TestUtils.map("a", 1, "b", 2, "glob", "al"), "idv1", "");
+    }
+
+    /**
+     * <pre>
+     * Resume already running view
+     *
+     * - start view A
+     * - wait a moment
+     * - pause view A
+     * - wait a moment
+     * - pause view A again
+     * - wait a moment
+     * - stop view A
+     *
+     * Total time should be 1 seconds because it was paused already
+     *
+     * </pre>
+     *
+     * @throws InterruptedException to wait
+     */
+    @Test
+    public void pauseViewWithId_pausePaused() throws InterruptedException {
+        Countly.instance().init(TestUtils.getConfigViews());
+        TestUtils.validateEQSize(0);
+        String viewIDA = Countly.instance().views().startView("A");
+
+        Thread.sleep(1000);
+        Countly.instance().views().pauseViewWithID(viewIDA);
+        Thread.sleep(1000);
+        Countly.instance().views().pauseViewWithID(viewIDA);
+        Thread.sleep(1000);
+
+        Countly.instance().views().stopViewWithID(viewIDA);
+        validateView("A", 0.0, 0, 3, true, true, null, "idv1", "");
+        validateView("A", 1.0, 1, 3, false, false, null, "idv1", "");
+        validateView("A", 0.0, 2, 3, false, false, null, "idv1", "");
+    }
+
+    /**
+     * <pre>
+     * Resume already running view
+     *
+     * - start view A
+     * - wait a moment
+     * - resume view A
+     * - wait a moment
+     * - stop view A
+     *
+     * Total time should be 2 seconds because it was not paused
+     *
+     * </pre>
+     *
+     * @throws InterruptedException to wait
+     */
+    @Test
+    public void resumeViewWithId_resumeRunning() throws InterruptedException {
+        Countly.instance().init(TestUtils.getConfigViews());
+        TestUtils.validateEQSize(0);
+        String viewIDA = Countly.instance().views().startView("A");
+
+        Thread.sleep(1000);
+        Countly.instance().views().resumeViewWithID(viewIDA);
+        Thread.sleep(1000);
+
+        Countly.instance().views().stopViewWithID(viewIDA);
+        validateView("A", 0.0, 0, 2, true, true, null, "idv1", "");
+        validateView("A", 2.0, 1, 2, false, false, null, "idv1", "");
+    }
+
+    /**
+     * <pre>
+     * A mixed flow of sessions and views
+     *
+     * - start session
+     * - start view A - firstView true- event is created
+     * - wait a moment
+     * - end session - this call ends existing views so it stops A
+     * - start view B - firstView true - event is created
+     * - start session
+     * - start view C - firstView false - event is created
+     *
+     * There should be 5 events
+     * </pre>
+     *
+     * @throws InterruptedException for wait
+     */
+    @Test
+    public void mixedFlow_sessions() throws InterruptedException {
+        Countly.instance().init(TestUtils.getConfigViews().enableFeatures(Config.Feature.Sessions));
+        TestUtils.validateEQSize(0);
+        Countly.session().begin();
+
+        Countly.instance().view("A");
+
+        Thread.sleep(1000);
+        Countly.session().end(); // A will auto stop
+
+        Countly.instance().view("B");
+        Countly.session().begin();
+
+        Countly.instance().views().startView("C");
+
+        validateView("A", 0.0, 0, 5, true, true, null, "idv1", "");
+        validateView("A", 1.0, 1, 5, false, false, null, "idv1", "");
+        validateView("B", 0.0, 2, 5, true, true, null, "idv2", "idv1");
+        validateView("B", 0.0, 3, 5, false, false, null, "idv2", "idv1");
+        validateView("C", 0.0, 4, 5, false, true, null, "idv3", "idv2");
+    }
+
+    /**
+     * "setGlobalSegmentation" flow
+     * - set global segmentation to different one that has all accepted data types and an invalid data type in init
+     * - start view A
+     * - sleep for 1 sec
+     * - pause view A
+     * - start view B that has new segmentation
+     * - sleep for 1 sec
+     * - stop all views with segmentation
+     * ------
+     * Validate that all events are created and segmentation is correct, and init given segmentation should exist in all events
+     */
+    @Test
+    public void setGlobalSegmentation_initGiven() throws InterruptedException {
+        Countly.instance().init(TestUtils.getConfigViews(TestUtils.map("glob", "al", "int", Integer.MAX_VALUE, "float", BigDecimal.valueOf(Float.MAX_VALUE), "bool", true, "arr", new ArrayList<>(), "double", Double.MAX_VALUE, "long", Long.MAX_VALUE)));
+        Map<String, Object> clearedSegmentation = TestUtils.map("glob", "al", "int", Integer.MAX_VALUE, "float", BigDecimal.valueOf(Float.MAX_VALUE), "bool", true, "double", BigDecimal.valueOf(Double.MAX_VALUE), "long", Long.MAX_VALUE);
+        TestUtils.validateEQSize(0);
+        Countly.instance().views().startView("A", TestUtils.map("a", 1, "b", 2));
+        Thread.sleep(1000);
+        Countly.instance().views().pauseViewWithID("idv1");
+        Countly.instance().views().startView("B", TestUtils.map("c", 3, "d", 4));
+        Thread.sleep(1000);
+        Countly.instance().views().stopAllViews(TestUtils.map("e", 5, "f", 6));
+
+        validateView("A", 0.0, 0, 5, true, true, TestUtils.map(clearedSegmentation, "a", 1, "b", 2), "idv1", "");
+        validateView("A", 1.0, 1, 5, false, false, clearedSegmentation, "idv1", "");
+        validateView("B", 0.0, 2, 5, false, true, TestUtils.map(clearedSegmentation, "c", 3, "d", 4), "idv2", "idv1");
+        validateView("A", 0.0, 3, 5, false, false, TestUtils.map(clearedSegmentation, "e", 5, "f", 6), "idv1", "idv1");
+        validateView("B", 1.0, 4, 5, false, false, TestUtils.map(clearedSegmentation, "e", 5, "f", 6), "idv2", "idv1");
+    }
+
+    /**
+     * "setGlobalSegmentation" flow
+     * - init countly with global segmentation
+     * - start view A that overrides one of the global segmentation
+     * - set global segmentation to different one that has all accepted data types and an invalid data type
+     * - sleep for 1 sec
+     * - pause view A
+     * - start view B that has new segmentation
+     * - sleep for 1 sec
+     * - stop all views with segmentation
+     * ------
+     * Validate that all events are created and segmentation is correct, and things should be overridden correctly
+     */
+    @Test
+    public void setGlobalSegmentation() throws InterruptedException {
+        Countly.instance().init(TestUtils.getConfigViews(TestUtils.map("ab", 5, "a", 5)));
+        Map<String, Object> clearedSegmentation = TestUtils.map("glob", "al", "int", Integer.MAX_VALUE, "float", BigDecimal.valueOf(Float.MAX_VALUE), "bool", true, "double", BigDecimal.valueOf(Double.MAX_VALUE), "long", Long.MAX_VALUE);
+        TestUtils.validateEQSize(0);
+        Countly.instance().views().startView("A", TestUtils.map("a", 1, "b", 2));
+        Countly.instance().views().setGlobalViewSegmentation(TestUtils.map("glob", "al", "int", Integer.MAX_VALUE, "float", BigDecimal.valueOf(Float.MAX_VALUE), "bool", true, "arr", new ArrayList<>(), "double", Double.MAX_VALUE, "long", Long.MAX_VALUE));
+        Thread.sleep(1000);
+        Countly.instance().views().pauseViewWithID("idv1");
+        Countly.instance().views().startView("B", TestUtils.map("c", 3, "d", 4));
+        Thread.sleep(1000);
+        Countly.instance().views().stopAllViews(TestUtils.map("e", 5, "f", 6));
+
+        validateView("A", 0.0, 0, 5, true, true, TestUtils.map("ab", 5, "a", 1, "b", 2), "idv1", "");
+        validateView("A", 1.0, 1, 5, false, false, clearedSegmentation, "idv1", "");
+        validateView("B", 0.0, 2, 5, false, true, TestUtils.map(clearedSegmentation, "c", 3, "d", 4), "idv2", "idv1");
+        validateView("A", 0.0, 3, 5, false, false, TestUtils.map(clearedSegmentation, "e", 5, "f", 6), "idv1", "idv1");
+        validateView("B", 1.0, 4, 5, false, false, TestUtils.map(clearedSegmentation, "e", 5, "f", 6), "idv2", "idv1");
+    }
+
+    /**
+     * "updateGlobalViewSegmentation" flow
+     * - init countly with global segmentation
+     * - start view A that overrides one of the global segmentation
+     * - set global segmentation to different one that has all accepted data types and an invalid data type
+     * - sleep for 1 sec
+     * - pause view A
+     * - update global segmentation with new values and override one of the old values
+     * - start view B that has new segmentation
+     * - sleep for 1 sec
+     * - stop all views with segmentation
+     * ------
+     * Validate that all events are created and segmentation is correct, and things should be overridden correctly
+     */
+    @Test
+    public void updateGlobalSegmentation() throws InterruptedException {
+        Countly.instance().init(TestUtils.getConfigViews(TestUtils.map("ab", 5, "a", 5)));
+        Map<String, Object> clearedSegmentation = TestUtils.map("glob", "al", "int", Integer.MAX_VALUE, "float", BigDecimal.valueOf(Float.MAX_VALUE), "bool", true, "double", BigDecimal.valueOf(Double.MAX_VALUE), "long", Long.MAX_VALUE);
+        TestUtils.validateEQSize(0);
+        Countly.instance().views().startView("A", TestUtils.map("a", 1, "b", 2));
+        Countly.instance().views().setGlobalViewSegmentation(TestUtils.map("glob", "al", "int", Integer.MAX_VALUE, "float", BigDecimal.valueOf(Float.MAX_VALUE), "bool", true, "arr", new ArrayList<>(), "double", Double.MAX_VALUE, "long", Long.MAX_VALUE));
+        Thread.sleep(1000);
+        Countly.instance().views().pauseViewWithID("idv1");
+        Countly.instance().views().updateGlobalViewSegmentation(TestUtils.map("int", Integer.MIN_VALUE, "all", "glob"));
+        Countly.instance().views().startView("B", TestUtils.map("c", 3, "d", 4));
+        Thread.sleep(1000);
+        Countly.instance().views().stopAllViews(TestUtils.map("e", 5, "f", 6));
+
+        validateView("A", 0.0, 0, 5, true, true, TestUtils.map("ab", 5, "a", 1, "b", 2), "idv1", "");
+        validateView("A", 1.0, 1, 5, false, false, clearedSegmentation, "idv1", "");
+        clearedSegmentation.put("int", Integer.MIN_VALUE);
+        clearedSegmentation.put("all", "glob");
+        validateView("B", 0.0, 2, 5, false, true, TestUtils.map(clearedSegmentation, "c", 3, "d", 4), "idv2", "idv1");
+        validateView("A", 0.0, 3, 5, false, false, TestUtils.map(clearedSegmentation, "e", 5, "f", 6), "idv1", "idv1");
+        validateView("B", 1.0, 4, 5, false, false, TestUtils.map(clearedSegmentation, "e", 5, "f", 6), "idv2", "idv1");
+    }
+
+    /**
+     * "setGlobalViewSegmentation" init empty null
+     * should not override global segmentation with empty map given
+     * Global segmentation should stay as it is
+     */
+    @Test
+    public void setGlobalSegmentation_initGiven_empty() {
+        Countly.instance().init(TestUtils.getConfigViews(TestUtils.map()));
+        TestUtils.validateEQSize(0);
+        Countly.instance().views().startView("A", TestUtils.map("a", 1, "b", 2));
+
+        validateView("A", 0.0, 0, 1, true, true, TestUtils.map("a", 1, "b", 2), "idv1", "");
+    }
+
+    /**
+     * "setGlobalViewSegmentation" init given null
+     * should not override global segmentation with null map given
+     * Global segmentation should stay as it is
+     */
+    @Test
+    public void setGlobalSegmentation_initGiven_null() {
+        Countly.instance().init(TestUtils.getConfigViews(null));
+        TestUtils.validateEQSize(0);
+        Countly.instance().views().startView("A", TestUtils.map("a", 1, "b", 2));
+
+        validateView("A", 0.0, 0, 1, true, true, TestUtils.map("a", 1, "b", 2), "idv1", "");
+    }
+
+    /**
+     * "setGlobalViewSegmentation"
+     * should not override global segmentation with empty map given
+     * Global segmentation should stay as it is
+     */
+    @Test
+    public void setGlobalSegmentation_empty() {
+        Countly.instance().init(TestUtils.getConfigViews());
+        TestUtils.validateEQSize(0);
+
+        Countly.instance().views().setGlobalViewSegmentation(TestUtils.map());
+        Countly.instance().views().startView("A", TestUtils.map("a", 1, "b", 2));
+
+        validateView("A", 0.0, 0, 1, true, true, TestUtils.map("a", 1, "b", 2), "idv1", "");
+    }
+
+    /**
+     * "setGlobalViewSegmentation"
+     * should not override global segmentation with null map given
+     * Global segmentation should stay as it is
+     */
+    @Test
+    public void setGlobalSegmentation_null() {
+        Countly.instance().init(TestUtils.getConfigViews());
+        TestUtils.validateEQSize(0);
+
+        Countly.instance().views().setGlobalViewSegmentation(null);
+        Countly.instance().views().startView("A", TestUtils.map("a", 1, "b", 2));
+
+        validateView("A", 0.0, 0, 1, true, true, TestUtils.map("a", 1, "b", 2), "idv1", "");
+    }
+
+    /**
+     * "updateGlobalSegmentation"
+     * should not override global segmentation with empty map given
+     * Global segmentation should stay as it is
+     */
+    @Test
+    public void updateGlobalSegmentation_empty() {
+        Countly.instance().init(TestUtils.getConfigViews(TestUtils.map("glob", "all")));
+        TestUtils.validateEQSize(0);
+
+        Countly.instance().views().updateGlobalViewSegmentation(TestUtils.map());
+        Countly.instance().views().startView("A", TestUtils.map("a", 1, "b", 2));
+
+        validateView("A", 0.0, 0, 1, true, true, TestUtils.map("glob", "all", "a", 1, "b", 2), "idv1", "");
+    }
+
+    /**
+     * "updateGlobalSegmentation"
+     * should not override global segmentation with null given
+     * Global segmentation should stay as it is
+     */
+    @Test
+    public void updateGlobalSegmentation_null() {
+        Countly.instance().init(TestUtils.getConfigViews(TestUtils.map("glob", "all")));
+        TestUtils.validateEQSize(0);
+
+        Countly.instance().views().updateGlobalViewSegmentation(null);
+        Countly.instance().views().startView("A", TestUtils.map("a", 1, "b", 2));
+
+        validateView("A", 0.0, 0, 1, true, true, TestUtils.map("glob", "all", "a", 1, "b", 2), "idv1", "");
+    }
+
+    /**
+     * We make sure that "setGlobalSegmentation" and "updateGlobalSegmentation" updates/sets global segmentation
+     * ------
+     * initialize countly with init given global segmentation that includes all accepted data types and couple of incorrect ones (like arr=list())
+     * start view A with segmentation that overrides one of the global segmentations
+     * validate start view event is recorded for A with correct segmentation
+     * setGlobalViewSegmentation with all accepted data types and couple of not accepted data types
+     * sleep for 1 second
+     * pause view A
+     * validate pause view event, and setGlobalViewSegmentation values are exists
+     * call updateGlobalViewSegmentation that overrides some of the globalSegmentation and also add couple of incorrect data types
+     * start view B with segment that overrides couple of not overridden global segmentation values
+     * validate start view event for B is recorded and global segmentation values are overwridden and incorrect data types removed
+     * sleep for 1 second
+     * stopAllViews with segmentation that has incorrect data types, 1 global segm override and new segm values
+     * validate 2 stop view event is recorded in order of A,B and correct segm values are existed
+     * ------
+     *
+     * @throws InterruptedException for wait
+     */
+    @Test
+    public void updateGlobalSegmentation_flow() throws InterruptedException {
+        Countly.instance().init(TestUtils.getConfigViews(TestUtils.map("glob", "al", "int", Integer.MAX_VALUE, "float", BigDecimal.valueOf(Float.MAX_VALUE), "bool", true, "double", BigDecimal.valueOf(Double.MAX_VALUE), "long", Long.MAX_VALUE, "arr", new ArrayList<>(), "map", TestUtils.map())));
+
+        Map<String, Object> clearedSegmentation = TestUtils.map("glob", "al", "int", Integer.MAX_VALUE, "float", BigDecimal.valueOf(Float.MAX_VALUE), "bool", true, "double", BigDecimal.valueOf(Double.MAX_VALUE), "long", Long.MAX_VALUE);
+        TestUtils.validateEQSize(0);
+
+        Countly.instance().views().startView("A", TestUtils.map("glob", "no"));
+        validateView("A", 0.0, 0, 1, true, true, TestUtils.map(clearedSegmentation, "glob", "no"), "idv1", "");
+
+        Countly.instance().views().setGlobalViewSegmentation(TestUtils.map("glob", "al", "int", Integer.MAX_VALUE, "float", BigDecimal.valueOf(Float.MAX_VALUE), "bool", true, "arr", new ArrayList<>(), "double", Double.MAX_VALUE, "long", Long.MAX_VALUE));
+        Thread.sleep(1000);
+
+        Countly.instance().views().pauseViewWithID("idv1");
+        validateView("A", 1.0, 1, 2, false, false, clearedSegmentation, "idv1", "");
+
+        Countly.instance().views().updateGlobalViewSegmentation(TestUtils.map("int", Integer.MIN_VALUE, "arr", new ArrayList<>(), "all", "glob"));
+
+        Countly.instance().views().startView("B", TestUtils.map("float", BigDecimal.valueOf(Float.MIN_VALUE), "in", "case"));
+        validateView("B", 0.0, 2, 3, false, true, TestUtils.map(clearedSegmentation, "all", "glob", "int", Integer.MIN_VALUE, "float", BigDecimal.valueOf(Float.MIN_VALUE), "in", "case"), "idv2", "idv1");
+
+        Thread.sleep(1000);
+        Countly.instance().views().stopAllViews(TestUtils.map("bool", false));
+        validateView("A", 0.0, 3, 5, false, false, TestUtils.map(clearedSegmentation, "all", "glob", "int", Integer.MIN_VALUE, "bool", false), "idv1", "idv1");
+        validateView("B", 1.0, 4, 5, false, false, TestUtils.map(clearedSegmentation, "all", "glob", "int", Integer.MIN_VALUE, "bool", false), "idv2", "idv1");
+    }
+
+    static void validateView(String viewName, Double viewDuration, int idx, int size, boolean start, boolean visit, Map<String, Object> customSegmentation, String id, String pvid) {
+        Map<String, Object> viewSegmentation = TestUtils.map("name", viewName, "segment", TestUtils.getOS());
+        if (start) {
+            viewSegmentation.put("start", "1");
+        }
+        if (visit) {
+            viewSegmentation.put("visit", "1");
+        }
+        if (customSegmentation != null) {
+            viewSegmentation.putAll(customSegmentation);
+        }
+
+        TestUtils.validateEventInEQ(ModuleViews.KEY_VIEW_EVENT, viewSegmentation, 1, 0.0, viewDuration, idx, size, id, pvid, null, null);
+    }
+}
diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/SessionImplTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/SessionImplTests.java
index ba5eb841..2f0655ef 100644
--- a/sdk-java/src/test/java/ly/count/sdk/java/internal/SessionImplTests.java
+++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/SessionImplTests.java
@@ -1,6 +1,5 @@
 package ly.count.sdk.java.internal;
 
-import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import java.util.function.BiFunction;
@@ -605,7 +604,8 @@ public void view() {
         Countly.instance().init(TestUtils.getConfigSessions(Config.Feature.Views, Config.Feature.Events).setEventQueueSizeToSend(4));
         SessionImpl session = (SessionImpl) Countly.session();
         TestUtils.validateEQSize(0);
-        validateViewInEQ((ViewImpl) session.view("view"), 0, 1);
+        session.view("view");
+        ModuleViewsTests.validateView("view", 0.0, 0, 1, true, true, null, "_CLY_", "");
     }
 
     /**
@@ -615,25 +615,18 @@ public void view() {
      */
     @Test
     public void view_stopStartedAndNext() {
-        Countly.instance().init(TestUtils.getConfigSessions(Config.Feature.Views, Config.Feature.Events).setEventQueueSizeToSend(4));
+        InternalConfig config = new InternalConfig(TestUtils.getConfigSessions(Config.Feature.Views, Config.Feature.Events).setEventQueueSizeToSend(4));
+        config.viewIdGenerator = TestUtils.idGenerator();
+        Countly.instance().init(config);
+
         SessionImpl session = (SessionImpl) Countly.session();
         TestUtils.validateEQSize(0);
         session.view("start");
         TestUtils.validateEQSize(1);
-        validateViewInEQ((ViewImpl) session.view("next"), 2, 3);
-    }
-
-    private void validateViewInEQ(ViewImpl view, int eqIdx, int eqSize) {
-        List<EventImpl> eventList = TestUtils.getCurrentEQ();
-        assertEquals(eqSize, eventList.size());
-        EventImpl event = eventList.get(eqIdx);
-        assertEquals(event.sum, view.start.sum);
-        assertEquals(event.count, view.start.count);
-        assertEquals(event.key, view.start.key);
-        assertEquals(event.segmentation, view.start.segmentation);
-        assertEquals(event.hour, view.start.hour);
-        assertEquals(event.dow, view.start.dow);
-        assertEquals(event.duration, view.start.duration);
+        session.view("next");
+        ModuleViewsTests.validateView("start", 0.0, 0, 3, true, true, null, TestUtils.keysValues[0], "");
+        ModuleViewsTests.validateView("start", 0.0, 1, 3, false, false, null, TestUtils.keysValues[0], "");
+        ModuleViewsTests.validateView("next", 0.0, 2, 3, false, true, null, TestUtils.keysValues[1], TestUtils.keysValues[0]);
     }
 
     private void validateNotEquals(int idOffset, BiFunction<SessionImpl, SessionImpl, Consumer<Long>> setter) {
diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java
index 8697a7c3..94f148e7 100644
--- a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java
+++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java
@@ -12,6 +12,7 @@
 import java.util.Map;
 import java.util.Scanner;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Stream;
@@ -308,28 +309,56 @@ public static Map<String, String> parseQueryParams(String data) {
         return paramMap;
     }
 
-    static void validateEvent(EventImpl gonnaValidate, String key, Map<String, Object> segmentation, int count, Double sum, Double duration) {
+    static void validateEvent(EventImpl gonnaValidate, String key, Map<String, Object> segmentation, int count, Double sum, Double duration, String id, String pvid, String cvid, String peid) {
         Assert.assertEquals(key, gonnaValidate.key);
-        Assert.assertEquals(segmentation, gonnaValidate.segmentation);
+
+        if (segmentation != null) {
+            Assert.assertEquals("Event segmentation size are not equal", segmentation.size(), gonnaValidate.segmentation.size());
+            for (Map.Entry<String, Object> entry : segmentation.entrySet()) {
+                Assert.assertEquals(entry.getValue(), gonnaValidate.segmentation.get(entry.getKey()));
+            }
+        }
+
         Assert.assertEquals(count, gonnaValidate.count);
         Assert.assertEquals(sum, gonnaValidate.sum);
 
         if (duration != null) {
             double delta = 0.5;
-            Assert.assertTrue(Math.abs(duration - gonnaValidate.duration) < delta);
+            Assert.assertTrue(duration + " expected duration, got " + gonnaValidate.duration, Math.abs(duration - gonnaValidate.duration) < delta);
         }
 
         Assert.assertTrue(gonnaValidate.dow >= 0 && gonnaValidate.dow < 7);
         Assert.assertTrue(gonnaValidate.hour >= 0 && gonnaValidate.hour < 24);
         Assert.assertTrue(gonnaValidate.timestamp >= 0);
+        validateId(id, gonnaValidate.id, "Event ID");
+        validateId(pvid, gonnaValidate.pvid, "Previous View ID");
+        validateId(cvid, gonnaValidate.cvid, "Current View ID");
+        validateId(peid, gonnaValidate.peid, "Previous Event ID");
     }
 
-    static void validateEventInEQ(String key, Map<String, Object> segmentation, int count, Double sum, Double duration, int index, int size) {
+    // if id null
+    private static void validateId(String id, String gonnaValidate, String name) {
+        if (id != null && id.equals("_CLY_")) {
+            validateSafeRandomVal(gonnaValidate);
+        } else {
+            Assert.assertEquals(name + " is not validated", id, gonnaValidate);
+        }
+    }
+
+    static void validateEvent(EventImpl gonnaValidate, String key, Map<String, Object> segmentation, int count, Double sum, Double duration) {
+        validateEvent(gonnaValidate, key, segmentation, count, sum, duration, null, null, null, null);
+    }
+
+    static void validateEventInEQ(String key, Map<String, Object> segmentation, int count, Double sum, Double duration, int index, int size, String id, String pvid, String cvid, String peid) {
         List<EventImpl> events = getCurrentEQ();
-        validateEvent(events.get(index), key, segmentation, count, sum, duration);
+        validateEvent(events.get(index), key, segmentation, count, sum, duration, id, pvid, cvid, peid);
         validateEQSize(size);
     }
 
+    static void validateEventInEQ(String key, Map<String, Object> segmentation, int count, Double sum, Double duration, int index, int size) {
+        validateEventInEQ(key, segmentation, count, sum, duration, index, size, null, null, null, null);
+    }
+
     static List<EventImpl> readEventsFromRequest() {
         return readEventsFromRequest(0, TestUtils.DEVICE_ID);
     }
@@ -615,4 +644,27 @@ static void validateSafeRandomVal(String val) {
             Assert.fail("No match for " + val);
         }
     }
+
+    static IdGenerator idGenerator() {
+        AtomicInteger counter = new AtomicInteger(0);
+        return () -> TestUtils.keysValues[counter.getAndIncrement() % TestUtils.keysValues.length];
+    }
+
+    static IdGenerator incrementalViewIdGenerator() {
+        AtomicInteger counter = new AtomicInteger(0);
+        return () -> "idv" + counter.incrementAndGet();
+    }
+
+    static InternalConfig getConfigViews() {
+        InternalConfig config = new InternalConfig(TestUtils.getBaseConfig().enableFeatures(Config.Feature.Views, Config.Feature.Events));
+        config.viewIdGenerator = TestUtils.incrementalViewIdGenerator();
+        return config;
+    }
+
+    static InternalConfig getConfigViews(Map<String, Object> segmentation) {
+        InternalConfig config = new InternalConfig(TestUtils.getBaseConfig().enableFeatures(Config.Feature.Views, Config.Feature.Events));
+        config.viewIdGenerator = TestUtils.incrementalViewIdGenerator();
+        config.views.setGlobalViewSegmentation(segmentation);
+        return config;
+    }
 }
diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/TimedEventsTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/TimedEventsTests.java
index 12df8e06..2b0a9c28 100644
--- a/sdk-java/src/test/java/ly/count/sdk/java/internal/TimedEventsTests.java
+++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/TimedEventsTests.java
@@ -70,6 +70,6 @@ public void recordEventRegularFlow_base(boolean regularRecord) throws Interrupte
             tEvent.endAndRecord();
         }
 
-        TestUtils.validateEventInEQ("key", targetSegm, 5, 133.0, targetDuration, 0, 1);
+        TestUtils.validateEventInEQ("key", targetSegm, 5, 133.0, targetDuration, 0, 1, "_CLY_", null, "", null);
     }
 }
diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ViewImplTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ViewImplTests.java
new file mode 100644
index 00000000..4dff4cba
--- /dev/null
+++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ViewImplTests.java
@@ -0,0 +1,215 @@
+package ly.count.sdk.java.internal;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import ly.count.sdk.java.Config;
+import ly.count.sdk.java.Countly;
+import ly.count.sdk.java.View;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+
+@RunWith(JUnit4.class)
+public class ViewImplTests {
+
+    @Before
+    public void beforeTest() {
+        TestUtils.createCleanTestState();
+    }
+
+    @After
+    public void afterTest() {
+        Countly.instance().halt();
+    }
+
+    /**
+     * "constructor" with null session
+     * Validating default values
+     * Values should be set to default
+     */
+    @Test
+    public void constructor_defaults() {
+        ViewImpl view = new ViewImpl(null, TestUtils.keysValues[0], Mockito.mock(Log.class));
+        Assert.assertNull(view.session);
+        Assert.assertEquals(TestUtils.keysValues[0], view.name);
+        Assert.assertEquals("start", ModuleViews.KEY_START);
+        Assert.assertEquals("1", ModuleViews.KEY_START_VALUE);
+        Assert.assertEquals("visit", ModuleViews.KEY_VISIT);
+        Assert.assertEquals("1", ModuleViews.KEY_VISIT_VALUE);
+        Assert.assertEquals("segment", ModuleViews.KEY_SEGMENT);
+        Assert.assertEquals("[CLY]_view", ModuleViews.KEY_VIEW_EVENT);
+        Assert.assertEquals("name", ModuleViews.KEY_NAME);
+        Assert.assertFalse(view.toString().isEmpty());
+    }
+
+    /**
+     * "start" with defaults
+     * Validating default values and that view is recorded
+     * Values should be set to default and view should be recorded
+     */
+    @Test
+    public void start() {
+        InternalConfig config = new InternalConfig(TestUtils.getBaseConfig().setFeatures(Config.Feature.Views, Config.Feature.Events));
+        config.viewIdGenerator = TestUtils.idGenerator();
+        Countly.instance().init(config);
+
+        TestUtils.validateEQSize(0);
+        Countly.instance().view(TestUtils.keysValues[0]); // this calls start automatically
+        TestUtils.validateEQSize(1);
+        Map<String, Object> segmentations = new ConcurrentHashMap<>();
+        segmentations.put("name", TestUtils.keysValues[0]);
+        segmentations.put("visit", "1");
+        segmentations.put("segment", TestUtils.getOS());
+        segmentations.put("start", "1");
+        TestUtils.validateEventInEQ("[CLY]_view", segmentations, 1, 0.0, null, 0, 1, TestUtils.keysValues[0], "", null, null);
+    }
+
+    /**
+     * "start" with backendModeEnabled
+     * Validating that view is not recorded
+     * A view should not be recorded
+     */
+    @Test
+    public void start_backendModeEnabled() {
+        Countly.instance().init(TestUtils.getBaseConfig().setFeatures(Config.Feature.Views, Config.Feature.Events).enableBackendMode());
+        TestUtils.validateEQSize(0);
+        Countly.instance().view(TestUtils.keysValues[0]); // this calls start automatically
+        TestUtils.validateEQSize(0);
+    }
+
+    /**
+     * "start" with null and empty name
+     * Validating that views are not recorded
+     * Views should not be recorded
+     */
+    @Test
+    public void start_nullAndEmptyName() {
+        Countly.instance().init(TestUtils.getBaseConfig().setFeatures(Config.Feature.Views, Config.Feature.Events).enableBackendMode());
+        TestUtils.validateEQSize(0);
+        Countly.instance().view(null); // this calls start automatically
+        TestUtils.validateEQSize(0);
+        Countly.instance().view(""); // this calls start automatically
+        TestUtils.validateEQSize(0);
+    }
+
+    /**
+     * "start" with no consent to Events
+     * Validating that view is not recorded
+     * A view should not be recorded
+     */
+    @Test
+    public void start_noEventsConsent() {
+        Countly.instance().init(TestUtils.getBaseConfig().setFeatures(Config.Feature.Views));
+        TestUtils.validateEQSize(0);
+        Countly.instance().view(TestUtils.keysValues[0]); // this calls start automatically
+        TestUtils.validateEQSize(0);
+    }
+
+    /**
+     * "stop" with defaults
+     * Validating that start view and stop view are recorded
+     * Start and stop views should be recorded
+     *
+     * @throws InterruptedException for the duration of the view
+     */
+    @Test
+    public void stop() throws InterruptedException {
+        InternalConfig config = new InternalConfig(TestUtils.getBaseConfig().setFeatures(Config.Feature.Views, Config.Feature.Events));
+        config.viewIdGenerator = TestUtils.idGenerator();
+        Countly.instance().init(config);
+
+        TestUtils.validateEQSize(0);
+        View view = Countly.instance().view(TestUtils.keysValues[0]); // this calls start automatically
+        TestUtils.validateEQSize(1);
+        Map<String, Object> segmentations = new ConcurrentHashMap<>();
+        segmentations.put("name", TestUtils.keysValues[0]);
+        segmentations.put("visit", "1");
+        segmentations.put("segment", TestUtils.getOS());
+        segmentations.put("start", "1");
+        TestUtils.validateEventInEQ("[CLY]_view", segmentations, 1, 0.0, null, 0, 1, TestUtils.keysValues[0], "", null, null);
+
+        segmentations.remove("start");
+        segmentations.remove("visit");
+        Thread.sleep(1000);
+        view.stop(false);
+        TestUtils.validateEventInEQ("[CLY]_view", segmentations, 1, 0.0, 1.0, 1, 2, TestUtils.keysValues[0], "", null, null);
+    }
+
+    /**
+     * "stop" with defaults via calling stop on Countly
+     * Validating that start view and stop view are recorded but there should be 4 events recorded
+     * Start and stop views should be recorded and expected numbers of events should be in the queue
+     *
+     * @throws InterruptedException for the duration of the view
+     */
+    @Test
+    public void stop_sdkCall() throws InterruptedException {
+        InternalConfig config = new InternalConfig(TestUtils.getBaseConfig().setFeatures(Config.Feature.Views, Config.Feature.Events));
+        config.viewIdGenerator = TestUtils.idGenerator();
+        Countly.instance().init(config);
+
+        TestUtils.validateEQSize(0);
+        Countly.instance().view(TestUtils.keysValues[0]); // this calls start automatically
+        TestUtils.validateEQSize(1);
+        Map<String, Object> segmentations = new ConcurrentHashMap<>();
+        segmentations.put("name", TestUtils.keysValues[0]);
+        segmentations.put("visit", "1");
+        segmentations.put("segment", TestUtils.getOS());
+        segmentations.put("start", "1");
+        TestUtils.validateEventInEQ("[CLY]_view", segmentations, 1, 0.0, null, 0, 1, TestUtils.keysValues[0], "", null, null);
+
+        segmentations.remove("start");
+        segmentations.remove("visit");
+        Thread.sleep(1000);
+        Countly.instance().view(TestUtils.keysValues[0]).stop(false); // this call stop previous view and creates new one and stops it
+        TestUtils.validateEventInEQ("[CLY]_view", segmentations, 1, 0.0, null, 3, 4, TestUtils.keysValues[1], TestUtils.keysValues[0], null, null);
+    }
+
+    /**
+     * "stop" with backend mode enabled
+     * Validating that nothing should be recorded
+     * Event queue should be empty
+     */
+    @Test
+    public void stop_backendModeEnabled() {
+        Countly.instance().init(TestUtils.getBaseConfig().setFeatures(Config.Feature.Views, Config.Feature.Events).enableBackendMode());
+        TestUtils.validateEQSize(0);
+        Countly.instance().view(TestUtils.keysValues[0]); // this calls start automatically
+        TestUtils.validateEQSize(0);
+        Countly.instance().view(TestUtils.keysValues[0]).stop(false); // this call stop previous view and creates new one and stops it
+        TestUtils.validateEQSize(0);
+    }
+
+    /**
+     * "stop" with no consent to events
+     * Validating that nothing should be recorded
+     * Event queue should be empty
+     */
+    @Test
+    public void stop_noConsentForEvents() {
+        Countly.instance().init(TestUtils.getBaseConfig().setFeatures(Config.Feature.Views));
+        TestUtils.validateEQSize(0);
+        Countly.instance().view(TestUtils.keysValues[0]); // this calls start automatically
+        TestUtils.validateEQSize(0);
+        Countly.instance().view(TestUtils.keysValues[0]).stop(false); // this call stop previous view and creates new one and stops it
+        TestUtils.validateEQSize(0);
+    }
+
+    /**
+     * "stop" a not started view
+     * Validating that stop call does not generate any events
+     * Event queue should be empty
+     */
+    @Test
+    public void stop_notStartedView() {
+        Countly.instance().init(TestUtils.getBaseConfig().setFeatures(Config.Feature.Views, Config.Feature.Events));
+        ViewImpl view = new ViewImpl(Countly.session(), TestUtils.keysValues[0], Mockito.mock(Log.class));
+        TestUtils.validateEQSize(0);
+        view.stop(false);
+        TestUtils.validateEQSize(0);
+    }
+}
diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/sc_MV_ManualViewTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/sc_MV_ManualViewTests.java
new file mode 100644
index 00000000..b94a9ead
--- /dev/null
+++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/sc_MV_ManualViewTests.java
@@ -0,0 +1,535 @@
+package ly.count.sdk.java.internal;
+
+import ly.count.sdk.java.Countly;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for manual view tracking
+ * Notes:
+ * - legacy call recordView is view() call in the Java SDK
+ */
+@RunWith(JUnit4.class)
+public class sc_MV_ManualViewTests {
+    @Before
+    public void beforeTest() {
+        TestUtils.createCleanTestState();
+    }
+
+    @After
+    public void afterTest() {
+        Countly.instance().halt();
+    }
+
+    //(1XX) Value sanitation, wrong usage, simple tests
+
+    /**
+     * recordView(x2), startAutoStoppedView(x2), startView(x2), pauseViewWithID, resumeViewWithID, stopViewWithName(x2), stopViewWithID(x2),
+     * addSegmentationToViewWithID, addSegmentationToViewWithName, setGlobalViewSegmentation, updateGlobalViewSegmentation
+     * ----
+     * called with "null" values. versions with and without segmentation. nothing should crash, no events should be recorded
+     * ----
+     * Note: legacy call is called with "true" version of it additionally
+     */
+    @Test
+    public void MV_100_badValues_null() {
+        Countly.instance().init(TestUtils.getConfigViews());
+        TestUtils.validateEQSize(0);
+        Assert.assertEquals(0, TestUtils.getCurrentRQ().length);
+
+        // recordView(x2) + true version
+        Countly.instance().view(null);
+        Countly.instance().view(null, false);
+        Countly.instance().view(null, true);
+
+        // startAutoStoppedView(x2)
+        Countly.instance().views().startAutoStoppedView(null);
+        Countly.instance().views().startAutoStoppedView(null, TestUtils.map());
+
+        // startView(x2)
+        Countly.instance().views().startView(null);
+        Countly.instance().views().startView(null, TestUtils.map());
+
+        // pauseViewWithID, resumeViewWithID
+        Countly.instance().views().pauseViewWithID(null);
+        Countly.instance().views().resumeViewWithID(null);
+
+        // stopViewWithName(x2), stopViewWithID(x2)
+        Countly.instance().views().stopViewWithName(null);
+        Countly.instance().views().stopViewWithName(null, TestUtils.map());
+        Countly.instance().views().stopViewWithID(null);
+        Countly.instance().views().stopViewWithID(null, TestUtils.map());
+
+        // addSegmentationToViewWithID, addSegmentationToViewWithName
+        Countly.instance().views().addSegmentationToViewWithID(null, TestUtils.map());
+        Countly.instance().views().addSegmentationToViewWithName(null, TestUtils.map());
+
+        // setGlobalViewSegmentation, updateGlobalViewSegmentation
+        Countly.instance().views().setGlobalViewSegmentation(null);
+        Countly.instance().views().updateGlobalViewSegmentation(null);
+
+        TestUtils.validateEQSize(0);
+        Assert.assertEquals(0, TestUtils.getCurrentRQ().length);
+    }
+
+    /**
+     * recordView(x2), startAutoStoppedView(x2), startView(x2), pauseViewWithID, resumeViewWithID, stopViewWithName(x2),
+     * stopViewWithID(x2), addSegmentationToViewWithID, addSegmentationToViewWithName
+     * ----
+     * called with empty string values
+     * versions with and without segmentation
+     * nothing should crash, no events should be recorded
+     * ----
+     * Note: legacy call is called with "true" version of it additionally
+     */
+    @Test
+    public void MV_101_badValues_emptyString() {
+        Countly.instance().init(TestUtils.getConfigViews());
+        TestUtils.validateEQSize(0);
+        Assert.assertEquals(0, TestUtils.getCurrentRQ().length);
+
+        // recordView(x2) + true version
+        Countly.instance().view("");
+        Countly.instance().view("", false);
+        Countly.instance().view("", true);
+
+        // startAutoStoppedView(x2)
+        Countly.instance().views().startAutoStoppedView("");
+        Countly.instance().views().startAutoStoppedView("", TestUtils.map());
+
+        // startView(x2)
+        Countly.instance().views().startView("");
+        Countly.instance().views().startView("", TestUtils.map());
+
+        // pauseViewWithID, resumeViewWithID
+        Countly.instance().views().pauseViewWithID("");
+        Countly.instance().views().resumeViewWithID("");
+
+        // stopViewWithName(x2), stopViewWithID(x2)
+        Countly.instance().views().stopViewWithName("");
+        Countly.instance().views().stopViewWithName("", TestUtils.map());
+        Countly.instance().views().stopViewWithID("");
+        Countly.instance().views().stopViewWithID("", TestUtils.map());
+
+        // addSegmentationToViewWithID, addSegmentationToViewWithName
+        Countly.instance().views().addSegmentationToViewWithID("", TestUtils.map());
+        Countly.instance().views().addSegmentationToViewWithName("", TestUtils.map());
+
+        TestUtils.validateEQSize(0);
+        Assert.assertEquals(0, TestUtils.getCurrentRQ().length);
+    }
+
+    /**
+     * pauseViewWithID, resumeViewWithID, stopViewWithName(x2),
+     * stopViewWithID(x2), addSegmentationToViewWithID, addSegmentationToViewWithName
+     * ----
+     * called with empty string values
+     * versions with and without segmentation
+     * nothing should crash, no events should be recorded
+     */
+    @Test
+    public void MV_102_badValues_nonExistingViews() {
+        Countly.instance().init(TestUtils.getConfigViews());
+        TestUtils.validateEQSize(0);
+        Assert.assertEquals(0, TestUtils.getCurrentRQ().length);
+
+        Countly.instance().views().pauseViewWithID("idv1");
+        Countly.instance().views().resumeViewWithID(TestUtils.keysValues[1]);
+        Countly.instance().views().stopViewWithName(TestUtils.keysValues[2]);
+        Countly.instance().views().stopViewWithName(TestUtils.keysValues[3], TestUtils.map());
+        Countly.instance().views().stopViewWithID(TestUtils.keysValues[4]);
+        Countly.instance().views().stopViewWithID(TestUtils.keysValues[5], TestUtils.map());
+        Countly.instance().views().addSegmentationToViewWithID("idv1", TestUtils.map());
+        Countly.instance().views().addSegmentationToViewWithName(TestUtils.keysValues[1], TestUtils.map());
+
+        TestUtils.validateEQSize(0);
+        Assert.assertEquals(0, TestUtils.getCurrentRQ().length);
+    }
+
+    //(2XX) Usage flows
+
+    /**
+     * Make sure auto closing views behave correctly
+     * Steps:
+     * ----------
+     * recordView view A (sE_A id=idv1 pvid="" segm={visit="1" start="1"})
+     * wait 1 sec
+     * recordView view B (eE_A d=1 id=idv1 pvid="", segm={}) (sE_B id=idv2 pvid=idv1 segm={visit="1"})
+     * wait 1 sec
+     * start view C (eE_B d=1 id=idv2 pvid=idv1, segm={}) (sE_C id=idv3 pvid=idv2 segm={visit="1"})
+     * wait 1 sec
+     * startAutoStoppedView D (sE_D id=idv4 pvid=idv3 segm={visit="1"})
+     * wait 1 sec
+     * startAutoStoppedView E (eE_D d=1 id=idv4 pvid=idv3, segm={}) (sE_E id=idv5 pvid=idv4 segm={visit="1"})
+     * wait 1 sec
+     * start view F (eE_E d=1 id=idv5 pvid=idv4, segm={}) (sE_F id=idv6 pvid=idv5 segm={visit="1"})
+     * wait 1 sec
+     * recordView view G (sE_G id=idv7 pvid=idv6 segm={visit="1"})
+     * wait 1 sec
+     * startAutoStoppedView H (sE_H id=idv8 pvid=idv7 segm={visit="1"})
+     * wait 1 sec
+     * recordView view I (eE_H d=1 id=idv8 pvid=idv7, segm={}) (sE_I id=idv8 pvid=idv8 segm={visit="1"})
+     */
+    @Test
+    public void MV_200A_autostartView_autoClose_legacy() throws InterruptedException {
+        Countly.instance().init(TestUtils.getConfigViews().setEventQueueSizeToSend(20));
+        TestUtils.validateEQSize(0);
+
+        Countly.instance().view("A");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("A", 0.0, 0, 1, true, true, null, "idv1", "");
+
+        Countly.instance().view("B");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("A", 1.0, 1, 3, false, false, null, "idv1", "");
+        ModuleViewsTests.validateView("B", 0.0, 2, 3, false, true, null, "idv2", "idv1");
+
+        Countly.instance().views().startView("C", TestUtils.map("a", 1));
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("B", 1.0, 3, 5, false, false, null, "idv2", "idv1");
+        ModuleViewsTests.validateView("C", 0.0, 4, 5, false, true, TestUtils.map("a", 1), "idv3", "idv2");
+
+        Countly.instance().views().startAutoStoppedView("D");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("D", 0.0, 5, 6, false, true, null, "idv4", "idv3");
+
+        Countly.instance().views().startAutoStoppedView("E");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("D", 1.0, 6, 8, false, false, null, "idv4", "idv3");
+        ModuleViewsTests.validateView("E", 0.0, 7, 8, false, true, null, "idv5", "idv4");
+
+        Countly.instance().views().startView("F");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("E", 1.0, 8, 10, false, false, null, "idv5", "idv4");
+        ModuleViewsTests.validateView("F", 0.0, 9, 10, false, true, null, "idv6", "idv5");
+
+        Countly.instance().view("G");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("G", 0.0, 10, 11, false, true, null, "idv7", "idv6");
+
+        Countly.instance().views().startAutoStoppedView("H");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("G", 1.0, 11, 13, false, false, null, "idv7", "idv6");
+        ModuleViewsTests.validateView("H", 0.0, 12, 13, false, true, null, "idv8", "idv7");
+
+        Countly.instance().view("I");
+        ModuleViewsTests.validateView("H", 1.0, 13, 15, false, false, null, "idv8", "idv7");
+        ModuleViewsTests.validateView("I", 0.0, 14, 15, false, true, null, "idv9", "idv8");
+
+        Countly.instance().views().stopAllViews(null);
+        ModuleViewsTests.validateView("C", 6.0, 15, 18, false, false, null, "idv3", "idv8");
+        ModuleViewsTests.validateView("F", 3.0, 16, 18, false, false, null, "idv6", "idv8");
+        ModuleViewsTests.validateView("I", 0.0, 17, 18, false, false, null, "idv9", "idv8");
+    }
+
+    /**
+     * without the deprecated "recordViewCall" After every action, the EQ should be validated so make sure that the correct event is recorded
+     * ----------
+     * startAutoStoppedView view A (sE_A id=idv1 pvid="" segm={visit="1" start="1"})
+     * wait 1 sec
+     * startAutoStoppedView view B (eE_A d=1 id=idv1 pvid="", segm={}) (sE_B id=idv2 pvid=idv1 segm={visit="1"})
+     * wait 1 sec
+     * start view C (eE_B d=1 id=idv2 pvid=idv1, segm={}) (sE_C id=idv3 pvid=idv2 segm={visit="1"})
+     * stopAllViews (eE_X d=0 id=idv3 pvid=idv2, segm={})
+     */
+    @Test
+    public void MV_200B_autoStoppedView_autoClose() throws InterruptedException {
+        Countly.instance().init(TestUtils.getConfigViews().setEventQueueSizeToSend(20));
+        TestUtils.validateEQSize(0);
+
+        Countly.instance().views().startAutoStoppedView("A");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("A", 0.0, 0, 1, true, true, null, "idv1", "");
+
+        Countly.instance().views().startAutoStoppedView("B");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("A", 1.0, 1, 3, false, false, null, "idv1", "");
+        ModuleViewsTests.validateView("B", 0.0, 2, 3, false, true, null, "idv2", "idv1");
+
+        Countly.instance().views().startView("C", TestUtils.map("a", 1));
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("B", 1.0, 3, 5, false, false, null, "idv2", "idv1");
+        ModuleViewsTests.validateView("C", 0.0, 4, 5, false, true, TestUtils.map("a", 1), "idv3", "idv2");
+
+        Countly.instance().views().stopAllViews(null);
+        ModuleViewsTests.validateView("C", 1.0, 5, 6, false, false, null, "idv3", "idv2");
+    }
+
+    /**
+     * Steps:
+     * ----------
+     * start view A
+     * startAutoStoppedView B
+     * wait 1 sec
+     * pause view B
+     * wait 1 sec
+     * resume view B
+     * wait 1 sec
+     * RecordView C
+     * wait 1 sec
+     * pause view C
+     * wait 1 sec
+     * resume view C
+     * stopAllViews
+     * should record 8 events
+     */
+    @Test
+    public void MV_201A_autoStopped_pausedResumed_Legacy() throws InterruptedException {
+        Countly.instance().init(TestUtils.getConfigViews().setEventQueueSizeToSend(20));
+        TestUtils.validateEQSize(0);
+
+        Countly.instance().views().startView("A");
+        ModuleViewsTests.validateView("A", 0.0, 0, 1, true, true, null, "idv1", "");
+
+        Countly.instance().views().startAutoStoppedView("B");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("B", 0.0, 1, 2, false, true, null, "idv2", "idv1");
+
+        Countly.instance().views().pauseViewWithID("idv2");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("B", 1.0, 2, 3, false, false, null, "idv2", "idv1");
+
+        Countly.instance().views().resumeViewWithID("idv2");
+        Thread.sleep(1000);
+
+        Countly.instance().view("C");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("B", 1.0, 3, 5, false, false, null, "idv2", "idv1");
+        ModuleViewsTests.validateView("C", 0.0, 4, 5, false, true, null, "idv3", "idv2");
+
+        Countly.instance().views().pauseViewWithID("idv3");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("C", 1.0, 5, 6, false, false, null, "idv3", "idv2");
+
+        Countly.instance().views().resumeViewWithID("idv3");
+
+        Countly.instance().views().stopAllViews(null);
+        ModuleViewsTests.validateView("A", 5.0, 6, 8, false, false, null, "idv1", "idv2");
+        ModuleViewsTests.validateView("C", 0.0, 7, 8, false, false, null, "idv3", "idv2");
+    }
+
+    /**
+     * Steps:
+     * ----------
+     * start view A
+     * start startAutoStoppedView B
+     * wait 1 sec
+     * pause view B
+     * wait 1 sec
+     * resume view B
+     * stopAllViews
+     * should record 5 events
+     */
+    @Test
+    public void MV_201B_autoStopped_pausedResumed() throws InterruptedException {
+        Countly.instance().init(TestUtils.getConfigViews().setEventQueueSizeToSend(20));
+        TestUtils.validateEQSize(0);
+
+        Countly.instance().views().startView("A");
+        ModuleViewsTests.validateView("A", 0.0, 0, 1, true, true, null, "idv1", "");
+
+        Countly.instance().views().startAutoStoppedView("B");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("B", 0.0, 1, 2, false, true, null, "idv2", "idv1");
+
+        Countly.instance().views().pauseViewWithID("idv2");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("B", 1.0, 2, 3, false, false, null, "idv2", "idv1");
+
+        Countly.instance().views().resumeViewWithID("idv2");
+        Thread.sleep(1000);
+
+        Countly.instance().views().stopAllViews(null);
+        ModuleViewsTests.validateView("A", 3.0, 3, 5, false, false, null, "idv1", "idv1");
+        ModuleViewsTests.validateView("B", 1.0, 4, 5, false, false, null, "idv2", "idv1");
+    }
+
+    /**
+     * Steps:
+     * ----------
+     * startAutoStoppedView A
+     * wait 1 sec
+     * stop by name
+     * startAutoStoppedView B
+     * wait 1 sec
+     * stop by ID
+     * startAutoStoppedView C
+     * wait 1 sec
+     * stopAllViews
+     * record view D
+     * wait 1 sec
+     * stop by name
+     * record view E
+     * wait 1 sec
+     * stop by ID
+     * record view F
+     * wait 1 sec
+     * stopAllViews
+     * should record 12 events
+     */
+    @Test
+    public void MV_202A_autoStopped_stopped_legacy() throws InterruptedException {
+        Countly.instance().init(TestUtils.getConfigViews().setEventQueueSizeToSend(20));
+        TestUtils.validateEQSize(0);
+
+        Countly.instance().views().startAutoStoppedView("A");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("A", 0.0, 0, 1, true, true, null, "idv1", "");
+
+        Countly.instance().views().stopViewWithName("A");
+        ModuleViewsTests.validateView("A", 1.0, 1, 2, false, false, null, "idv1", "");
+
+        Countly.instance().views().startAutoStoppedView("B");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("B", 0.0, 2, 3, false, true, null, "idv2", "idv1");
+
+        Countly.instance().views().stopViewWithID("idv2");
+        ModuleViewsTests.validateView("B", 1.0, 3, 4, false, false, null, "idv2", "idv1");
+
+        Countly.instance().views().startAutoStoppedView("C");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("C", 0.0, 4, 5, false, true, null, "idv3", "idv2");
+
+        Countly.instance().views().stopAllViews(null);
+        ModuleViewsTests.validateView("C", 1.0, 5, 6, false, false, null, "idv3", "idv2");
+
+        Countly.instance().view("D");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("D", 0.0, 6, 7, false, true, null, "idv4", "idv3");
+
+        Countly.instance().views().stopViewWithName("D");
+        ModuleViewsTests.validateView("D", 1.0, 7, 8, false, false, null, "idv4", "idv3");
+
+        Countly.instance().view("E");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("E", 0.0, 8, 9, false, true, null, "idv5", "idv4");
+
+        Countly.instance().views().stopViewWithID("idv5");
+        ModuleViewsTests.validateView("E", 1.0, 9, 10, false, false, null, "idv5", "idv4");
+
+        Countly.instance().view("F");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("F", 0.0, 10, 11, false, true, null, "idv6", "idv5");
+
+        Countly.instance().views().stopAllViews(null);
+        ModuleViewsTests.validateView("F", 1.0, 11, 12, false, false, null, "idv6", "idv5");
+    }
+
+    /**
+     * Steps:
+     * ----------
+     * startAutoStoppedView A
+     * wait 1 sec
+     * stop by name
+     * startAutoStoppedView B
+     * wait 1 sec
+     * stop by ID
+     * startAutoStoppedView C
+     * wait 1 sec
+     * stopAllViews
+     * should record 6 events
+     */
+    @Test
+    public void MV_202B_autoStopped_stopped() throws InterruptedException {
+        Countly.instance().init(TestUtils.getConfigViews().setEventQueueSizeToSend(20));
+        TestUtils.validateEQSize(0);
+
+        Countly.instance().views().startAutoStoppedView("A");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("A", 0.0, 0, 1, true, true, null, "idv1", "");
+
+        Countly.instance().views().stopViewWithName("A");
+        ModuleViewsTests.validateView("A", 1.0, 1, 2, false, false, null, "idv1", "");
+
+        Countly.instance().views().startAutoStoppedView("B");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("B", 0.0, 2, 3, false, true, null, "idv2", "idv1");
+
+        Countly.instance().views().stopViewWithID("idv2");
+        ModuleViewsTests.validateView("B", 1.0, 3, 4, false, false, null, "idv2", "idv1");
+
+        Countly.instance().views().startAutoStoppedView("C");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("C", 0.0, 4, 5, false, true, null, "idv3", "idv2");
+
+        Countly.instance().views().stopAllViews(null);
+        ModuleViewsTests.validateView("C", 1.0, 5, 6, false, false, null, "idv3", "idv2");
+    }
+
+    /**
+     * Steps:
+     * ----------
+     * start view A
+     * wait 1 sec
+     * pause view A
+     * wait 1 sec
+     * resume view A
+     * wait 1 sec
+     * stopAllViews
+     * 3 events
+     */
+    @Test
+    public void MV_203_startView_PausedResumed() throws InterruptedException {
+        Countly.instance().init(TestUtils.getConfigViews().setEventQueueSizeToSend(20));
+        TestUtils.validateEQSize(0);
+
+        Countly.instance().views().startView("A");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("A", 0.0, 0, 1, true, true, null, "idv1", "");
+
+        Countly.instance().views().pauseViewWithID("idv1");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("A", 1.0, 1, 2, false, false, null, "idv1", "");
+
+        Countly.instance().views().resumeViewWithID("idv1");
+        Thread.sleep(1000);
+
+        Countly.instance().views().stopAllViews(null);
+        ModuleViewsTests.validateView("A", 1.0, 2, 3, false, false, null, "idv1", "");
+    }
+
+    /**
+     * Steps:
+     * ----------
+     * start view A
+     * wait 1 sec
+     * stop by name
+     * start view B
+     * wait 1 sec
+     * stop by ID
+     * start view c
+     * wait 1 sec
+     * stopAllViews
+     * should record 6 events
+     */
+    @Test
+    public void MV_203_startView_stopped() throws InterruptedException {
+        Countly.instance().init(TestUtils.getConfigViews().setEventQueueSizeToSend(20));
+        TestUtils.validateEQSize(0);
+
+        Countly.instance().views().startView("A");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("A", 0.0, 0, 1, true, true, null, "idv1", "");
+
+        Countly.instance().views().stopViewWithName("A");
+        ModuleViewsTests.validateView("A", 1.0, 1, 2, false, false, null, "idv1", "");
+
+        Countly.instance().views().startView("B");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("B", 0.0, 2, 3, false, true, null, "idv2", "idv1");
+
+        Countly.instance().views().stopViewWithID("idv2");
+        ModuleViewsTests.validateView("B", 1.0, 3, 4, false, false, null, "idv2", "idv1");
+
+        Countly.instance().views().startView("C");
+        Thread.sleep(1000);
+        ModuleViewsTests.validateView("C", 0.0, 4, 5, false, true, null, "idv3", "idv2");
+
+        Countly.instance().views().stopAllViews(null);
+        ModuleViewsTests.validateView("C", 1.0, 5, 6, false, false, null, "idv3", "idv2");
+    }
+}
diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ScenarioUtilsTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/sc_UA_UtilsTests.java
similarity index 61%
rename from sdk-java/src/test/java/ly/count/sdk/java/internal/ScenarioUtilsTests.java
rename to sdk-java/src/test/java/ly/count/sdk/java/internal/sc_UA_UtilsTests.java
index f857f4a9..b776dacb 100644
--- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ScenarioUtilsTests.java
+++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/sc_UA_UtilsTests.java
@@ -6,24 +6,22 @@
 import org.junit.runners.JUnit4;
 
 @RunWith(JUnit4.class)
-public class ScenarioUtilsTests {
+public class sc_UA_UtilsTests {
 
     /**
-     * "safeRandomVal"
-     * ### 001_validatingIDGenerator
-     * <p>
+     * <pre>
      * testing the ID generator function that is used for events and views
-     * <p>
+     *
      * Generate 2 values
-     * <p>
-     * they should be different.
-     * They should be 21 chars long.
-     * They should contain on base64 characters. first 8 one is base64 string and last 13 one is timestamp
+     *
+     * they should be different. They should be 21 chars long. They should contain only base64 characters.
+     * first 8 one is base64 string and last 13 one is timestamp
      *
      * @throws NumberFormatException for parsing part 2
+     * </pre>
      */
     @Test
-    public void _001_validatingIDGenerator() throws NumberFormatException {
+    public void UA_001_validatingIDGenerator() throws NumberFormatException {
         String val1 = Utils.safeRandomVal();
         String val2 = Utils.safeRandomVal();