Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
b6796fc
Version 5.3.2-rc1
gthea Oct 29, 2025
075cb85
WIP fused
gthea Oct 31, 2025
492ee25
WIP standalone / parent module
gthea Oct 31, 2025
3778c1e
Fix
gthea Oct 31, 2025
9038ecc
Consumer rules
gthea Nov 3, 2025
fb4eaeb
Restore
gthea Nov 4, 2025
2de6066
Remove unnecessary code
gthea Nov 4, 2025
4cf4c11
Publish working
gthea Nov 5, 2025
f2481d4
Signing task
gthea Nov 6, 2025
015ef86
Change version; expose SplitLogLevel
gthea Nov 6, 2025
eb4e6fb
Rename internal log level
gthea Nov 6, 2025
dceacb4
Merge branch 'development' into fused
gthea Nov 10, 2025
0fddefc
Restore log level
gthea Nov 10, 2025
4cac101
Update Logger tests
gthea Nov 10, 2025
1e0c0a1
Rename internal modules
gthea Nov 10, 2025
b6eb501
Clean up build.gradle
gthea Nov 10, 2025
4b13de6
Clean up
gthea Nov 10, 2025
2f9315a
Remove .module
gthea Nov 10, 2025
ed961c9
lint fix
gthea Nov 11, 2025
e5801de
Remove project/; refactor Gradle scripts
gthea Nov 12, 2025
d2bf298
Remove group and version in submodules
gthea Nov 12, 2025
33f8395
Scope Kotlin version override
gthea Nov 12, 2025
f0d24fb
Diffuse script enhancements
gthea Nov 14, 2025
89d9c94
Pipeline fix
gthea Nov 14, 2025
9d3831f
Update diffuse script
gthea Nov 14, 2025
9685552
Pull remote branches
gthea Nov 14, 2025
4c95ae3
New script
gthea Nov 14, 2025
3cc2e8e
events module
gthea Nov 14, 2025
e0af4b4
WIP
gthea Nov 14, 2025
cd6ac82
Renaming
gthea Nov 14, 2025
c06fc13
SplitEventsConfig
gthea Nov 18, 2025
99fc8c8
WIP
gthea Nov 20, 2025
3f3a4bb
Merge branch 'FME-11237_baseline' into events-poc
gthea Nov 20, 2025
0361384
Events module and interfaces
gthea Nov 20, 2025
68458c8
Jacoco fixes
gthea Nov 20, 2025
c4788d6
Remove missing dir from Sonar config
gthea Nov 20, 2025
a777140
Remove class
gthea Nov 20, 2025
4b8d1e2
Fix submodule coverage
gthea Nov 25, 2025
8e754ea
Fix coverage collection
gthea Nov 25, 2025
5e8e94f
Simplify sonarqube workflow
gthea Nov 25, 2025
c95de14
Add unregister method
gthea Nov 25, 2025
5da029d
Add libraries property in sonar
gthea Nov 25, 2025
d81f18c
EventsManagerConfig builder
gthea Nov 25, 2025
a605c0e
EventsManagerCore
gthea Nov 25, 2025
d377345
Update Gradle
gthea Nov 25, 2025
8e5050f
Clarification comment
gthea Nov 25, 2025
7c41008
Enhanced sonarqube workflow
gthea Nov 26, 2025
0111c34
Fix issues
gthea Nov 26, 2025
90e2bae
API and events-domain modules
gthea Nov 26, 2025
52b9452
API and Events domain modules
gthea Nov 26, 2025
1c44c5c
EventMetadata implementation
gthea Nov 26, 2025
c7561dd
Modify events-domain README
gthea Nov 26, 2025
0585ebf
Remove stacktrace
gthea Nov 26, 2025
8faefa0
Merge branch 'FME-11237_baseline' into FME-11235
gthea Nov 26, 2025
88cdc6e
Update readme
gthea Nov 26, 2025
c3a4616
Merge branch 'FME-11235' into FME-11235_2
gthea Nov 26, 2025
f9975b2
Cleanup
gthea Nov 26, 2025
0715d65
Clean up
gthea Nov 26, 2025
e1a7566
Cleanup
gthea Nov 26, 2025
41b0921
Use jetbrains annotations for core events module
gthea Nov 26, 2025
172f354
Update jetbrains annotations version
gthea Nov 27, 2025
d61a5c3
Core events delivery functionality
gthea Nov 27, 2025
853b504
Fix javadoc
gthea Nov 27, 2025
0345060
Fix SQ issues
gthea Nov 27, 2025
22d8796
EventsManager provider
gthea Nov 27, 2025
6bbbaab
BG and main thread event delivery
gthea Nov 27, 2025
2c82d0a
Logging
gthea Nov 27, 2025
272e80e
More strict log message check
gthea Nov 27, 2025
67354a2
Remove unnecessary classes
gthea Nov 27, 2025
92236a2
EventsManagersTest
gthea Nov 27, 2025
a5c282b
Move some events files to api and events-domain
gthea Nov 27, 2025
0d6bd0b
Move events classes to events-domain and api modules
gthea Nov 27, 2025
ceeb858
WIP SplitEventsManager
gthea Nov 28, 2025
55d59b4
Fix import
gthea Nov 28, 2025
09424af
Fix unlimited events edge case
gthea Nov 28, 2025
a22aae4
Clean up
gthea Nov 28, 2025
be0c187
Clean up 2
gthea Nov 28, 2025
810e99a
Clean up 3
gthea Nov 28, 2025
3eeebe1
EventsManagerCore extension
gthea Nov 29, 2025
27f5530
Migrate events-domain logic to use new Core functionality
gthea Nov 29, 2025
574ab9d
EventsManagerCore edge case
gthea Nov 30, 2025
23c26f6
Remove stream usage
gthea Nov 30, 2025
6d0d44d
Refactor EventsManagerCore for readability
gthea Nov 30, 2025
6f828e3
Clean up
gthea Dec 2, 2025
de7f159
Fix SQ issues
gthea Dec 2, 2025
b72eb2d
Merge branch 'FME-11237_baseline' into FME-11238-split-events_3
gthea Dec 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@

import static java.util.Objects.requireNonNull;

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;

import java.util.concurrent.Executor;

import io.harness.events.EventHandler;
import io.harness.events.EventsManager;
import io.harness.events.EventsManagers;
import io.split.android.client.SplitClient;
import io.split.android.client.api.EventMetadata;
import io.split.android.client.events.executors.SplitEventExecutorResources;
import io.split.android.client.events.executors.SplitEventExecutorResourcesImpl;
Expand All @@ -28,10 +26,6 @@ public class SplitEventsManager implements ISplitEventsManager, ListenableEvents
private final DualExecutorRegistration<SplitEvent, SplitInternalEvent, EventMetadata> mDualExecutorRegistration;
private SplitEventExecutorResources mResources;

// Track sync completion for SDK_READY_FROM_CACHE triggering. TODO: This is a temporary adaptation before extending EventsManager requireAny.
private volatile boolean mSplitsSyncComplete = false;
private volatile boolean mSegmentsSyncComplete = false;

/**
* Creates a new SplitEventsManager.
*
Expand Down Expand Up @@ -152,27 +146,20 @@ public void destroy() {
/**
* Notifies the synthetic composite events based on the actual internal event.
* These synthetic events simplify the SDK_READY condition evaluation.
* The prerequisite configuration ensures SDK_READY_FROM_CACHE always fires before SDK_READY.
* <p>
* Also handles the special case where SDK_READY_FROM_CACHE should fire
* before SDK_READY when both sync events have completed.
* TODO: Remove this method once EventsManagerConfig is updated.
*/
private void notifySyntheticEventsIfNeeded(SplitInternalEvent internalEvent) {
switch (internalEvent) {
case SPLITS_UPDATED:
case SPLITS_FETCHED:
mSplitsSyncComplete = true;
// Check if SDK_READY_FROM_CACHE should fire BEFORE notifying the sync complete
// to ensure correct ordering (SDK_READY_FROM_CACHE before SDK_READY)
triggerReadyFromCacheIfNeeded();
mEventsManager.notifyInternalEvent(SplitInternalEvent.SPLITS_SYNC_COMPLETE, null);
break;

case MY_SEGMENTS_UPDATED:
case MY_SEGMENTS_FETCHED:
case MY_LARGE_SEGMENTS_UPDATED:
mSegmentsSyncComplete = true;
// Check if SDK_READY_FROM_CACHE should fire BEFORE notifying the sync complete
triggerReadyFromCacheIfNeeded();
mEventsManager.notifyInternalEvent(SplitInternalEvent.SEGMENTS_SYNC_COMPLETE, null);
break;

Expand All @@ -182,36 +169,6 @@ private void notifySyntheticEventsIfNeeded(SplitInternalEvent internalEvent) {
}
}

/**
* Triggers SDK_READY_FROM_CACHE if SDK_READY is about to fire (both sync events received)
* but SDK_READY_FROM_CACHE hasn't fired yet.
* <p>
* TODO: This is a temporary adaptation before extending EventsManager requireAny.
*/
private void triggerReadyFromCacheIfNeeded() {
// Only trigger if both sync events have completed
if (!mSplitsSyncComplete || !mSegmentsSyncComplete) {
return;
}

// If SDK_READY_FROM_CACHE already triggered, nothing to do
if (mEventsManager.eventAlreadyTriggered(SplitEvent.SDK_READY_FROM_CACHE)) {
return;
}

// If SDK_READY already triggered, nothing to do (too late)
if (mEventsManager.eventAlreadyTriggered(SplitEvent.SDK_READY)) {
return;
}

// Both sync events received and SDK_READY_FROM_CACHE not yet fired
// Trigger it by firing all required cache events
mEventsManager.notifyInternalEvent(SplitInternalEvent.SPLITS_LOADED_FROM_STORAGE, null);
mEventsManager.notifyInternalEvent(SplitInternalEvent.MY_SEGMENTS_LOADED_FROM_STORAGE, null);
mEventsManager.notifyInternalEvent(SplitInternalEvent.ATTRIBUTES_LOADED_FROM_STORAGE, null);
mEventsManager.notifyInternalEvent(SplitInternalEvent.ENCRYPTION_MIGRATION_DONE, null);
}

private void startTimeoutThread(final int blockUntilReady) {
Thread timeoutThread = new Thread(new Runnable() {
@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package io.split.android.client.events;

import java.util.HashSet;
import java.util.Set;

import io.harness.events.EventsManagerConfig;

/**
Expand All @@ -19,25 +22,36 @@ private SplitEventsManagerConfigFactory() {
* <p>
* Event rules:
* <ul>
* <li>SDK_READY: requires both splits and segments sync to complete</li>
* <li>SDK_READY_FROM_CACHE: requires all cache loading events</li>
* <li>SDK_READY: requires both splits and segments sync to complete, and SDK_READY_FROM_CACHE must fire first</li>
* <li>SDK_READY_FROM_CACHE: fires when EITHER all cache loading events complete OR all sync events complete</li>
* <li>SDK_READY_TIMED_OUT: fires when timeout is reached (suppressed if SDK_READY fired first)</li>
* <li>SDK_UPDATE: fires on any data update after SDK_READY</li>
* </ul>
*
* @return the configured EventsManagerConfig
*/
static EventsManagerConfig<SplitEvent, SplitInternalEvent> create() {
// SDK_READY_FROM_CACHE fires when either:
// 1. Cache path: All cache loading events complete (AND), OR
// 2. Sync path: All sync events complete (AND)
Set<SplitInternalEvent> cacheGroup = new HashSet<>();
cacheGroup.add(SplitInternalEvent.SPLITS_LOADED_FROM_STORAGE);
cacheGroup.add(SplitInternalEvent.MY_SEGMENTS_LOADED_FROM_STORAGE);
cacheGroup.add(SplitInternalEvent.ATTRIBUTES_LOADED_FROM_STORAGE);
cacheGroup.add(SplitInternalEvent.ENCRYPTION_MIGRATION_DONE);

Set<SplitInternalEvent> syncGroup = new HashSet<>();
syncGroup.add(SplitInternalEvent.SPLITS_SYNC_COMPLETE);
syncGroup.add(SplitInternalEvent.SEGMENTS_SYNC_COMPLETE);

return EventsManagerConfig.<SplitEvent, SplitInternalEvent>builder()
.requireAll(SplitEvent.SDK_READY,
SplitInternalEvent.SPLITS_SYNC_COMPLETE,
SplitInternalEvent.SEGMENTS_SYNC_COMPLETE)

.requireAll(SplitEvent.SDK_READY_FROM_CACHE,
SplitInternalEvent.SPLITS_LOADED_FROM_STORAGE,
SplitInternalEvent.MY_SEGMENTS_LOADED_FROM_STORAGE,
SplitInternalEvent.ATTRIBUTES_LOADED_FROM_STORAGE,
SplitInternalEvent.ENCRYPTION_MIGRATION_DONE)
// SDK_READY_FROM_CACHE: OR of ANDs
// Fires when (cache group all done) OR (sync group all done)
.requireAny(SplitEvent.SDK_READY_FROM_CACHE, cacheGroup, syncGroup)

.requireAny(SplitEvent.SDK_READY_TIMED_OUT,
SplitInternalEvent.SDK_READY_TIMEOUT_REACHED)
Expand All @@ -49,6 +63,8 @@ static EventsManagerConfig<SplitEvent, SplitInternalEvent> create() {
SplitInternalEvent.RULE_BASED_SEGMENTS_UPDATED,
SplitInternalEvent.SPLIT_KILLED_NOTIFICATION)

// SDK_READY requires SDK_READY_FROM_CACHE to fire first
.prerequisite(SplitEvent.SDK_READY, SplitEvent.SDK_READY_FROM_CACHE)
.prerequisite(SplitEvent.SDK_UPDATE, SplitEvent.SDK_READY)

.suppressedBy(SplitEvent.SDK_READY_TIMED_OUT, SplitEvent.SDK_READY)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,14 @@ public enum SplitInternalEvent {

/**
* Synthetic event: fired when splits sync completes (either SPLITS_FETCHED or SPLITS_UPDATED).
* Used internally to simplify SDK_READY condition evaluation.
* <p>
* TODO: This is a temporary adaptation before extending EventsManager requireAny.
* Used internally to simplify SDK_READY and SDK_READY_FROM_CACHE condition evaluation.
*/
SPLITS_SYNC_COMPLETE,

/**
* Synthetic event: fired when segments sync completes (any of MY_SEGMENTS_FETCHED,
* MY_SEGMENTS_UPDATED, or MY_LARGE_SEGMENTS_UPDATED).
* Used internally to simplify SDK_READY condition evaluation.
* <p>
* TODO: This is a temporary adaptation before extending EventsManager requireAny.
* Used internally to simplify SDK_READY and SDK_READY_FROM_CACHE condition evaluation.
*/
SEGMENTS_SYNC_COMPLETE,
}
59 changes: 45 additions & 14 deletions events/src/main/java/io/harness/events/EventsManagerConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
public final class EventsManagerConfig<E, I> {
// External events that require ALL listed internals (AND)
private final Map<E, Set<I>> mRequireAll;
// External events triggered by ANY of the listed internals (OR)
private final Map<E, Set<I>> mRequireAny;
// External events triggered by ANY of the listed internal groups (OR of ANDs)
private final Map<E, Set<Set<I>>> mRequireAny;
// External-event guards: prerequisites that must have fired before External can emit
private final Map<E, Set<E>> mPrerequisites;
// External-event guards: if any of these have fired, suppress E
Expand All @@ -31,16 +31,16 @@ public final class EventsManagerConfig<E, I> {
* Creates a new EventsManagerConfig.
*
* @param requireAll External events that require ALL listed internals (AND)
* @param requireAny External events triggered by ANY of the listed internals (OR)
* @param requireAny External events triggered by ANY of the listed internal groups (OR of ANDs)
* @param prerequisites External-event guards: prerequisites that must have fired before External can emit
* @param suppressedBy External-event guards: if any of these have fired, suppress E
* @param executionLimits Execution policy: max executions per external event (-1 = unlimited)
*/
private EventsManagerConfig(Map<E, Set<I>> requireAll,
Map<E, Set<I>> requireAny,
Map<E, Set<E>> prerequisites,
Map<E, Set<E>> suppressedBy,
Map<E, Integer> executionLimits) {
Map<E, Set<Set<I>>> requireAny,
Map<E, Set<E>> prerequisites,
Map<E, Set<E>> suppressedBy,
Map<E, Integer> executionLimits) {
mRequireAll = requireAll == null
? Collections.emptyMap()
: Collections.unmodifiableMap(new HashMap<>(requireAll));
Expand Down Expand Up @@ -72,7 +72,7 @@ public Map<E, Set<I>> getRequireAll() {
}

@NotNull
public Map<E, Set<I>> getRequireAny() {
public Map<E, Set<Set<I>>> getRequireAny() {
return mRequireAny;
}

Expand Down Expand Up @@ -110,7 +110,7 @@ public static <E, I> Builder<E, I> builder() {
*/
public static final class Builder<E, I> {
private final Map<E, Set<I>> mRequireAll = new HashMap<>();
private final Map<E, Set<I>> mRequireAny = new HashMap<>();
private final Map<E, Set<Set<I>>> mRequireAny = new HashMap<>();
private final Map<E, Set<E>> mPrerequisites = new HashMap<>();
private final Map<E, Set<E>> mSuppressedBy = new HashMap<>();
private final Map<E, Integer> mExecutionLimits = new HashMap<>();
Expand All @@ -121,8 +121,8 @@ private Builder() {
/**
* Adds a requirement that ALL specified internal events must occur for the external event to fire.
*
* @param externalEvent the external event
* @param internalEvents the internal events that must ALL occur
* @param externalEvent the external event
* @param internalEvents the internal events that must ALL occur
* @return this builder
*/
@SafeVarargs
Expand All @@ -133,14 +133,45 @@ public final Builder<E, I> requireAll(E externalEvent, I... internalEvents) {

/**
* Adds a requirement that ANY of the specified internal events will trigger the external event.
* Each internal event is treated as a group of one (singleton).
*
* @param externalEvent the external event
* @param internalEvents the internal events, any of which will trigger the external event
* @param externalEvent the external event
* @param internalEvents the internal events, any of which will trigger the external event
* @return this builder
*/
@SafeVarargs
public final Builder<E, I> requireAny(E externalEvent, I... internalEvents) {
mRequireAny.put(externalEvent, new HashSet<>(Arrays.asList(internalEvents)));
// Convert each individual event to a singleton Set (group of one)
Set<Set<I>> groups = new HashSet<>();
for (I internalEvent : internalEvents) {
groups.add(Collections.singleton(internalEvent));
}
mRequireAny.put(externalEvent, groups);
return this;
}

/**
* Adds a requirement that ANY of the specified internal event groups will trigger the external event.
* Each group is an AND: all events in the group must occur.
* The external event fires when ANY group is fully satisfied (OR of ANDs).
* <p>
* Example:
* <pre>
* .requireAny(DISH_SERVED,
* Set.of(BOUGHT_INGREDIENTS, COOKED_MEAL), // Fresh cooking path
* Set.of(ORDERED_DELIVERY, DELIVERY_ARRIVED)) // Delivery path
* // Fires when: (fresh cooking done) OR (delivery arrived)
* </pre>
*
* @param externalEvent the external event
* @param internalEventGroups the groups of internal events; all events in a group must occur (AND),
* and any group being satisfied triggers the external event (OR)
* @return this builder
*/
@SafeVarargs
public final Builder<E, I> requireAny(E externalEvent, Set<I>... internalEventGroups) {
Set<Set<I>> groups = new HashSet<>(Arrays.asList(internalEventGroups));
mRequireAny.put(externalEvent, groups);
return this;
}

Expand Down
Loading
Loading