Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
a749f11
add cmab support
jaeopt Oct 21, 2025
4ca1e42
fix cmab
jaeopt Oct 22, 2025
b840dc9
clean up
jaeopt Oct 23, 2025
4f077f3
clean up
jaeopt Oct 23, 2025
1ee5ba6
add cmab config
jaeopt Oct 23, 2025
35a3876
more tests
jaeopt Oct 23, 2025
162e766
add more tests
jaeopt Oct 23, 2025
c5e178c
fix with cmab builder
jaeopt Oct 28, 2025
13bc880
upgrade android to 35
jaeopt Oct 29, 2025
ca86f4e
upgrade targetSDK and gradle versions
jaeopt Oct 29, 2025
8d660b3
clean up github workflow
jaeopt Oct 29, 2025
6f6407a
fix gradle settings
jaeopt Oct 29, 2025
f612e79
clean up
jaeopt Oct 29, 2025
23536be
fix for API 21 tests
jaeopt Oct 29, 2025
fccb5cd
clean up
jaeopt Oct 29, 2025
dd47d8d
Merge branch 'jae/upgrade-android-api' into jae/FSSDK-11136
jaeopt Oct 29, 2025
f12e872
add cmab support
jaeopt Oct 21, 2025
9949c71
fix cmab
jaeopt Oct 22, 2025
f62d141
clean up
jaeopt Oct 23, 2025
4e4eb46
clean up
jaeopt Oct 23, 2025
07ed767
add cmab config
jaeopt Oct 23, 2025
11f89c8
more tests
jaeopt Oct 23, 2025
8cda9b2
add more tests
jaeopt Oct 23, 2025
50b0be9
fix with cmab builder
jaeopt Oct 28, 2025
f57eefa
local maven
jaeopt Oct 29, 2025
762c44b
fix cmabservice instantiation
jaeopt Oct 29, 2025
cb1d9ea
fix cmab interfaces
jaeopt Oct 30, 2025
969180c
fix for java-sdk changes
jaeopt Oct 30, 2025
4faabd2
testing
jaeopt Oct 30, 2025
70a3039
fix cmab tests
jaeopt Oct 31, 2025
2b2497f
fix cmab tests
jaeopt Oct 31, 2025
57541f7
maven local support
jaeopt Oct 31, 2025
7834c92
fix conflicts
jaeopt Oct 31, 2025
c7a237b
clean up
jaeopt Oct 31, 2025
2da0445
fix conflicts
jaeopt Oct 31, 2025
6a87840
add blocking async api
jaeopt Oct 31, 2025
826f8c5
clean up android user context
jaeopt Nov 3, 2025
a8d1ffa
clean up androd user contexts for testability
jaeopt Nov 3, 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 @@ -117,6 +117,7 @@ public void setup() throws Exception {
notificationCenter,
null,
odpManager,
null,
"test-vuid",
null,
null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2249,7 +2249,9 @@ public void testDecide() {
assertEquals(decision.getVariables().toMap(), variablesExpected.toMap());
assertEquals(decision.getRuleKey(), FEATURE_MULTI_VARIATE_EXPERIMENT_KEY);
assertEquals(decision.getFlagKey(), flagKey);
assertEquals(decision.getUserContext(), userContext);
OptimizelyUserContext decisionUserContext = decision.getUserContext();
assertEquals(decisionUserContext.getUserId(), userContext.getUserId());
assertEquals(decisionUserContext.getAttributes(), userContext.getAttributes());
assertTrue(decision.getReasons().isEmpty());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ public void initializeSyncWithEnvironment() {
EventHandler eventHandler = mock(DefaultEventHandler.class);
EventProcessor eventProcessor = mock(EventProcessor.class);
OptimizelyManager optimizelyManager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, 3600L, datafileHandler, null, 3600L,
eventHandler, eventProcessor, null, null, null, null, null, null, null);
eventHandler, eventProcessor, null, null, null, null, null, null, null, null);
/*
* Scenario#1: when datafile is not Empty
* Scenario#2: when datafile is Empty
Expand Down Expand Up @@ -222,7 +222,7 @@ public void initializeAsyncWithEnvironment() {
EventHandler eventHandler = mock(DefaultEventHandler.class);
EventProcessor eventProcessor = mock(EventProcessor.class);
final OptimizelyManager optimizelyManager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, 3600L, datafileHandler, null, 3600L,
eventHandler, eventProcessor, null, null, null, null, null, null, null);
eventHandler, eventProcessor, null, null, null, null, null, null, null, null);

/*
* Scenario#1: when datafile is not Empty
Expand Down Expand Up @@ -494,7 +494,7 @@ public void initializeSyncWithUpdateOnNewDatafileDisabled() {
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();

OptimizelyManager manager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
null, null, null, null, null, null, null, null, null);
null, null, null, null, null, null, null, null, null, null);

ArgumentCaptor<Context> contextCaptor = ArgumentCaptor.forClass(Context.class);
ArgumentCaptor<DatafileConfig> configCaptor = ArgumentCaptor.forClass(DatafileConfig.class);
Expand Down Expand Up @@ -533,7 +533,7 @@ public void initializeSyncWithUpdateOnNewDatafileEnabled() {
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();

OptimizelyManager manager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
null, null, null, null, null, null, null, null, null);
null, null, null, null, null, null, null, null, null, null);

ArgumentCaptor<Context> contextCaptor = ArgumentCaptor.forClass(Context.class);
ArgumentCaptor<DatafileConfig> configCaptor = ArgumentCaptor.forClass(DatafileConfig.class);
Expand Down Expand Up @@ -572,7 +572,7 @@ public void initializeSyncWithDownloadToCacheDisabled() {
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();

OptimizelyManager manager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
null, null, null, null, null, null, null, null, null);
null, null, null, null, null, null, null, null, null, null);

ArgumentCaptor<Context> contextCaptor = ArgumentCaptor.forClass(Context.class);
ArgumentCaptor<DatafileConfig> configCaptor = ArgumentCaptor.forClass(DatafileConfig.class);
Expand Down Expand Up @@ -611,7 +611,7 @@ public void initializeSyncWithUpdateOnNewDatafileDisabledWithPeriodicPollingEnab
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();

OptimizelyManager manager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
null, null, null, null, null, null, null, null, null);
null, null, null, null, null, null, null, null, null, null);

ArgumentCaptor<Context> contextCaptor = ArgumentCaptor.forClass(Context.class);
ArgumentCaptor<DatafileConfig> configCaptor = ArgumentCaptor.forClass(DatafileConfig.class);
Expand Down Expand Up @@ -651,7 +651,7 @@ public void initializeSyncWithUpdateOnNewDatafileEnabledWithPeriodicPollingEnabl
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();

OptimizelyManager manager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
null, null, null, null, null, null, null, null, null);
null, null, null, null, null, null, null, null, null, null);

ArgumentCaptor<Context> contextCaptor = ArgumentCaptor.forClass(Context.class);
ArgumentCaptor<DatafileConfig> configCaptor = ArgumentCaptor.forClass(DatafileConfig.class);
Expand Down Expand Up @@ -690,7 +690,7 @@ public void initializeSyncWithUpdateOnNewDatafileDisabledWithPeriodicPollingDisa
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();

OptimizelyManager manager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
null, null, null, null, null, null, null, null, null);
null, null, null, null, null, null, null, null, null, null);

ArgumentCaptor<Context> contextCaptor = ArgumentCaptor.forClass(Context.class);
ArgumentCaptor<DatafileConfig> configCaptor = ArgumentCaptor.forClass(DatafileConfig.class);
Expand All @@ -706,7 +706,7 @@ public void initializeSyncWithUpdateOnNewDatafileDisabledWithPeriodicPollingDisa

return datafileHandler;
}).when(manager.getDatafileHandler()).downloadDatafile(contextCaptor.capture(), configCaptor.capture(), listenerCaptor.capture());

OptimizelyClient client = manager.initialize(context, defaultDatafile, downloadToCache, updateConfigOnNewDatafile);

try {
Expand All @@ -730,7 +730,7 @@ public void initializeSyncWithUpdateOnNewDatafileEnabledWithPeriodicPollingDisab
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();

OptimizelyManager manager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
null, null, null, null, null, null, null, null, null);
null, null, null, null, null, null, null, null, null, null);

ArgumentCaptor<Context> contextCaptor = ArgumentCaptor.forClass(Context.class);
ArgumentCaptor<DatafileConfig> configCaptor = ArgumentCaptor.forClass(DatafileConfig.class);
Expand Down Expand Up @@ -769,7 +769,7 @@ public void initializeSyncWithResourceDatafileNoCache() {
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();

OptimizelyManager manager = spy(new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
null, null, null, null, null, null, null, null, null));
null, null, null, null, null, null, null, null, null, null));

datafileHandler.removeSavedDatafile(context, manager.getDatafileConfig());
OptimizelyClient client = manager.initialize(context, R.raw.datafile, downloadToCache, updateConfigOnNewDatafile);
Expand All @@ -786,7 +786,7 @@ public void initializeSyncWithResourceDatafileNoCacheWithDefaultParams() {
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();

OptimizelyManager manager = spy(new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
null, null, null, null, null, null, null, null, null));
null, null, null, null, null, null, null, null, null, null));

datafileHandler.removeSavedDatafile(context, manager.getDatafileConfig());
OptimizelyClient client = manager.initialize(context, R.raw.datafile);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -816,28 +816,33 @@ public OptimizelyConfig getOptimizelyConfig() {
* @return An OptimizelyUserContext associated with this OptimizelyClient.
*/
@Nullable
public OptimizelyUserContext createUserContext(@NonNull String userId,
@NonNull Map<String, Object> attributes) {
if (optimizely != null) {
return optimizely.createUserContext(userId, attributes);
} else {
public OptimizelyUserContextAndroid createUserContext(@NonNull String userId,
@NonNull Map<String, Object> attributes) {
if (optimizely == null) {
logger.warn("Optimizely is not initialized, could not create a user context");
return null;
}

if (userId == null) {
logger.warn("The userId parameter must be nonnull.");
return null;
}

return new OptimizelyUserContextAndroid(optimizely, userId, attributes);
}

@Nullable
public OptimizelyUserContext createUserContext(@NonNull String userId) {
public OptimizelyUserContextAndroid createUserContext(@NonNull String userId) {
return createUserContext(userId, Collections.emptyMap());
}

@Nullable
public OptimizelyUserContext createUserContext() {
public OptimizelyUserContextAndroid createUserContext() {
return createUserContext(Collections.emptyMap());
}

@Nullable
public OptimizelyUserContext createUserContext(@NonNull Map<String, Object> attributes) {
public OptimizelyUserContextAndroid createUserContext(@NonNull Map<String, Object> attributes) {
if (vuid == null) {
logger.warn("Optimizely vuid is not available. A userId is required to create a user context.");
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,15 @@
import com.optimizely.ab.android.event_handler.EventDispatcher;
import com.optimizely.ab.android.odp.DefaultODPApiManager;
import com.optimizely.ab.android.odp.VuidManager;
import com.optimizely.ab.android.sdk.cmab.DefaultCmabClient;
import com.optimizely.ab.android.shared.Client;
import com.optimizely.ab.android.shared.DatafileConfig;
import com.optimizely.ab.android.shared.OptlyStorage;
import com.optimizely.ab.android.user_profile.DefaultUserProfileService;
import com.optimizely.ab.bucketing.UserProfileService;
import com.optimizely.ab.cmab.client.CmabClient;
import com.optimizely.ab.cmab.service.CmabService;
import com.optimizely.ab.cmab.service.DefaultCmabService;
import com.optimizely.ab.config.ProjectConfig;
import com.optimizely.ab.config.parser.ConfigParseException;
import com.optimizely.ab.error.ErrorHandler;
Expand Down Expand Up @@ -90,6 +96,7 @@ public class OptimizelyManager {
@NonNull private UserProfileService userProfileService;
@Nullable private ODPManager odpManager;
@Nullable private final String vuid;
@Nullable private CmabService cmabService;

@Nullable private OptimizelyStartListener optimizelyStartListener;
private boolean returnInMainThreadFromAsyncInit = true;
Expand All @@ -112,6 +119,7 @@ public class OptimizelyManager {
@NonNull NotificationCenter notificationCenter,
@Nullable List<OptimizelyDecideOption> defaultDecideOptions,
@Nullable ODPManager odpManager,
@Nullable CmabService cmabService,
@Nullable String vuid,
@Nullable String clientEngineName,
@Nullable String clientVersion) {
Expand All @@ -137,6 +145,7 @@ public class OptimizelyManager {
this.userProfileService = userProfileService;
this.vuid = vuid;
this.odpManager = odpManager;
this.cmabService = cmabService;
this.notificationCenter = notificationCenter;
this.defaultDecideOptions = defaultDecideOptions;

Expand Down Expand Up @@ -646,6 +655,7 @@ private OptimizelyClient buildOptimizely(@NonNull Context context, @NonNull Stri
builder.withNotificationCenter(notificationCenter);
builder.withDefaultDecideOptions(defaultDecideOptions);
builder.withODPManager(odpManager);
builder.withCmabService(cmabService);
Optimizely optimizely = builder.build();

return new OptimizelyClient(optimizely, LoggerFactory.getLogger(OptimizelyClient.class), vuid);
Expand Down Expand Up @@ -781,15 +791,19 @@ public static class Builder {
@Nullable private List<OptimizelyDecideOption> defaultDecideOptions = null;
@Nullable private ODPEventManager odpEventManager;
@Nullable private ODPSegmentManager odpSegmentManager;
@Nullable private CmabClient cmabClient;

private int odpSegmentCacheSize = 100;
private int odpSegmentCacheTimeoutInSecs = 600;
private int odpSegmentCacheTimeoutInSecs = 10*60;
private int timeoutForODPSegmentFetchInSecs = 10;
private int timeoutForODPEventDispatchInSecs = 10;
private boolean odpEnabled = true;
private boolean vuidEnabled = false;
private String vuid = null;

private int cmabCacheSize = 100;
private int cmabCacheTimeoutInSecs = 30*60;

private String customSdkName = null;
private String customSdkVersion = null;

Expand Down Expand Up @@ -1058,6 +1072,33 @@ public Builder withClientInfo(@Nullable String clientEngineName, @Nullable Strin
this.customSdkVersion = clientVersion;
return this;
}

/**
* Override the default Cmab cache size (100).
* @param size the size
* @return this {@link Builder} instance
*/
public Builder withCmabCacheSize(int size) {
this.cmabCacheSize = size;
return this;
}

/**
* Override the default Cmab cache timeout (30 minutes).
* @param interval the interval
* @param timeUnit the time unit of the timeout argument
* @return this {@link Builder} instance
*/
public Builder withCmabCacheTimeout(int interval, TimeUnit timeUnit) {
this.cmabCacheTimeoutInSecs = (int) timeUnit.toSeconds(interval);
return this;
}

public Builder withCmabClient(CmabClient cmabClient) {
this.cmabClient = cmabClient;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For swift CmabClient is internal, here it is public, why this difference?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A good point. I do not see a solid reason to open this but it will be consistent with ODP and other module implementation. I guess swift is good as is too.

return this;
}

/**
* Get a new {@link Builder} instance to create {@link OptimizelyManager} with.
* @param context the application context used to create default service if not provided.
Expand Down Expand Up @@ -1160,6 +1201,15 @@ public OptimizelyManager build(Context context) {
.build();
}

DefaultCmabService.Builder cmabBuilder = DefaultCmabService.builder();
if (cmabClient == null) {
cmabClient = new DefaultCmabClient(context);
}
cmabBuilder.withClient(cmabClient);
cmabBuilder.withCmabCacheSize(cmabCacheSize);
cmabBuilder.withCmabCacheTimeoutInSecs(cmabCacheTimeoutInSecs);
CmabService cmabService = cmabBuilder.build();

return new OptimizelyManager(projectId, sdkKey,
datafileConfig,
logger,
Expand All @@ -1173,6 +1223,7 @@ public OptimizelyManager build(Context context) {
notificationCenter,
defaultDecideOptions,
odpManager,
cmabService,
vuid,
customSdkName,
customSdkVersion
Expand Down
Loading
Loading