Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
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 @@ -31,12 +31,13 @@ public class IterableApi {
static volatile IterableApi sharedInstance = new IterableApi();

private static final String TAG = "IterableApi";
private static final long CRITERIA_COOLDOWN_MS = 60000; // 5 second cooldown
private Context _applicationContext;
IterableConfig config;
private String _apiKey;
private String _email;
private String _userId;
private String _userIdAnon;
String _userIdAnon;
private String _authToken;
private boolean _debugMode;
private Bundle _payloadData;
Expand All @@ -55,6 +56,7 @@ public class IterableApi {
private IterableAuthManager authManager;
private HashMap<String, String> deviceAttributes = new HashMap<>();
private IterableKeychain keychain;
private long lastCriteriaFetch = 0;

void fetchRemoteConfiguration() {
apiClient.getRemoteConfiguration(new IterableHelper.IterableActionHandler() {
Expand Down Expand Up @@ -402,7 +404,7 @@ public void onSwitchToForeground() {
public void onSwitchToBackground() {}
};

private void onForeground() {
void onForeground() {
if (!_firstForegroundHandled) {
_firstForegroundHandled = true;
if (sharedInstance.config.autoPushRegistration && sharedInstance.isInitialized()) {
Expand Down Expand Up @@ -435,6 +437,21 @@ private void onForeground() {
editor.putBoolean(IterableConstants.SHARED_PREFS_DEVICE_NOTIFICATIONS_ENABLED, systemNotificationEnabled);
editor.apply();
}

long currentTime = System.currentTimeMillis();

// fetching anonymous user criteria on foregrounding
if (!sharedInstance.checkSDKInitialization()
&& sharedInstance._userIdAnon == null
&& sharedInstance.config.enableAnonActivation
&& sharedInstance.getVisitorUsageTracked()
&& sharedInstance.config.foregroundCriteriaFetch
&& currentTime - lastCriteriaFetch >= CRITERIA_COOLDOWN_MS) {

lastCriteriaFetch = currentTime;
anonymousUserManager.getCriteria();
IterableLogger.d(TAG, "Fetching anonymous user criteria - Foreground");
}
}

private boolean isInitialized() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ public class IterableConfig {
*/
final IterableAuthHandler authHandler;


/**
* Handler that can be used to retrieve the anonymous user id
*/
final IterableAnonUserHandler iterableAnonUserHandler;

/**
Expand Down Expand Up @@ -92,8 +94,20 @@ public class IterableConfig {

final boolean encryptionEnforced;

/**
* Enables anonymous user activation
*/
final boolean enableAnonActivation;

/**
* Toggles fetching of anonymous user criteria on foregrounding when set to true
* By default, the SDK will fetch anonymous user criteria on foregrounding.
*/
final boolean foregroundCriteriaFetch;

/**
* The number of anonymous events stored in local storage
*/
final int eventThresholdLimit;

/**
Expand Down Expand Up @@ -130,6 +144,7 @@ private IterableConfig(Builder builder) {
useInMemoryStorageForInApps = builder.useInMemoryStorageForInApps;
encryptionEnforced = builder.encryptionEnforced;
enableAnonActivation = builder.enableAnonActivation;
foregroundCriteriaFetch = builder.foregroundCriteriaFetch;
enableEmbeddedMessaging = builder.enableEmbeddedMessaging;
eventThresholdLimit = builder.eventThresholdLimit;
identityResolution = builder.identityResolution;
Expand All @@ -155,6 +170,7 @@ public static class Builder {
private IterableDecryptionFailureHandler decryptionFailureHandler;
private boolean encryptionEnforced = false;
private boolean enableAnonActivation = false;
private boolean foregroundCriteriaFetch = true;
private boolean enableEmbeddedMessaging = false;
private int eventThresholdLimit = 100;
private IterableIdentityResolution identityResolution = new IterableIdentityResolution();
Expand Down Expand Up @@ -310,7 +326,6 @@ public Builder setDataRegion(@NonNull IterableDataRegion dataRegion) {
* Set whether the SDK should store in-apps only in memory, or in file storage
* @param useInMemoryStorageForInApps `true` will have in-apps be only in memory
*/

@NonNull
public Builder setUseInMemoryStorageForInApps(boolean useInMemoryStorageForInApps) {
this.useInMemoryStorageForInApps = useInMemoryStorageForInApps;
Expand All @@ -327,6 +342,16 @@ public Builder setEnableAnonActivation(boolean enableAnonActivation) {
return this;
}

/**
* Set whether the SDK should disable criteria fetching on foregrounding. Set this to `false`
* if you want criteria to only be fetched on app launch.
* @param foregroundCriteriaFetch `true` will fetch criteria only on app launch.
*/
public Builder setForegroundCriteriaFetch(boolean foregroundCriteriaFetch) {
this.foregroundCriteriaFetch = foregroundCriteriaFetch;
return this;
}

public Builder setEventThresholdLimit(int eventThresholdLimit) {
this.eventThresholdLimit = eventThresholdLimit;
return this;
Expand All @@ -348,7 +373,6 @@ public Builder setEnableEmbeddedMessaging(boolean enableEmbeddedMessaging) {
* @param identityResolution
* @return
*/

public Builder setIdentityResolution(IterableIdentityResolution identityResolution) {
this.identityResolution = identityResolution;
return this;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.iterable.iterableapi;

import com.iterable.iterableapi.unit.PathBasedQueueDispatcher;
import com.iterable.iterableapi.util.DeviceInfoUtils;
import android.app.Activity;
import android.net.Uri;
Expand Down Expand Up @@ -55,13 +56,17 @@ public class IterableApiTest extends BaseTest {

public static final String PACKAGE_NAME = "com.iterable.iterableapi.test";
private MockWebServer server;

private PathBasedQueueDispatcher dispatcher;
private IterableApiClient originalApiClient;
private IterableApiClient mockApiClient;
private IterablePushRegistration.IterablePushRegistrationImpl originalPushRegistrationImpl;

@Before
public void setUp() {
server = new MockWebServer();
dispatcher = new PathBasedQueueDispatcher();
server.setDispatcher(dispatcher);
IterableApi.overrideURLEndpointPath(server.url("").toString());

reInitIterableApi();
Expand Down Expand Up @@ -89,6 +94,10 @@ private void reInitIterableApi() {
IterableApi.sharedInstance.apiClient = mockApiClient;
}

private void addResponse(String endPoint) {
dispatcher.enqueueResponse("/" + endPoint, new MockResponse().setResponseCode(200).setBody("{}"));
}

@Test
public void testSdkInitializedWithoutEmailOrUserId() throws Exception {
IterableApi.initialize(getContext(), "apiKey");
Expand Down Expand Up @@ -836,4 +845,124 @@ public void testFetchRemoteConfigurationCalledWhenInForeground() throws Exceptio
IterableActivityMonitor.instance = new IterableActivityMonitor();
}

@Test
public void testForegroundCriteriaFetchWhenConditionsMet() throws Exception {
// Clear any pending requests
while (server.takeRequest(1, TimeUnit.SECONDS) != null) {}

// Mock responses for expected endpoints
addResponse(IterableConstants.ENDPOINT_CRITERIA_LIST);

// Initialize with anon activation and foreground fetch enabled
IterableConfig config = new IterableConfig.Builder()
.setEnableAnonActivation(true)
.setForegroundCriteriaFetch(true)
.build();

IterableActivityMonitor.getInstance().unregisterLifecycleCallbacks(getContext());
IterableActivityMonitor.instance = new IterableActivityMonitor();

// Initialize API
IterableApi.initialize(getContext(), "apiKey", config);
IterableApi.getInstance().setVisitorUsageTracked(true);

// Simulate app coming to foreground
Robolectric.buildActivity(Activity.class).create().start().resume();
shadowOf(getMainLooper()).idle();

// Verify criteria fetch request was made
RecordedRequest criteriaRequest = server.takeRequest(1, TimeUnit.SECONDS);
assertNotNull("Criteria request should be made on foreground", criteriaRequest);
assertTrue("Request URL should contain getCriteria endpoint",
criteriaRequest.getPath().contains(IterableConstants.ENDPOINT_CRITERIA_LIST));

// Clean up
IterableActivityMonitor.getInstance().unregisterLifecycleCallbacks(getContext());
IterableActivityMonitor.instance = new IterableActivityMonitor();
}

@Test
public void testCriteriaFetchNotCalledWhenDisabled() throws Exception {
// Clear any pending requests
while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }

// Mock response for remote configuration only
addResponse(IterableConstants.ENDPOINT_GET_REMOTE_CONFIGURATION);
addResponse(IterableConstants.ENDPOINT_CRITERIA_LIST);

// Initialize with foreground fetch disabled
IterableConfig config = new IterableConfig.Builder()
.setEnableAnonActivation(true)
.setForegroundCriteriaFetch(false)
.build();

IterableActivityMonitor.getInstance().unregisterLifecycleCallbacks(getContext());
IterableActivityMonitor.instance = new IterableActivityMonitor();

// Initialize API
IterableApi.initialize(getContext(), "apiKey", config);

// Simulate app coming to foreground
Robolectric.buildActivity(Activity.class).create().start().resume();
shadowOf(getMainLooper()).idle();

// Take the remote configuration request
RecordedRequest remoteConfigRequest = server.takeRequest(1, TimeUnit.SECONDS);
assertNotNull(remoteConfigRequest);
assertTrue(remoteConfigRequest.getPath().contains(IterableConstants.ENDPOINT_GET_REMOTE_CONFIGURATION));

// Verify no criteria fetch request was made
RecordedRequest criteriaRequest = server.takeRequest(1, TimeUnit.SECONDS);
assertNull("No criteria request should be made when feature is disabled", criteriaRequest);

// Clean up
IterableActivityMonitor.getInstance().unregisterLifecycleCallbacks(getContext());
IterableActivityMonitor.instance = new IterableActivityMonitor();
}

@Test
public void testForegroundCriteriaFetchWithCooldown() throws Exception {
// Clear any pending requests
while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }

// Mock responses
addResponse(IterableConstants.ENDPOINT_CRITERIA_LIST);
addResponse(IterableConstants.ENDPOINT_GET_REMOTE_CONFIGURATION);

// Initialize with required config
IterableConfig config = new IterableConfig.Builder()
.setEnableAnonActivation(true)
.setForegroundCriteriaFetch(true)
.build();

IterableActivityMonitor.getInstance().unregisterLifecycleCallbacks(getContext());
IterableActivityMonitor.instance = new IterableActivityMonitor();

// Initialize API
IterableApi.initialize(getContext(), "apiKey", config);
IterableApi.getInstance().setVisitorUsageTracked(true);

// First foreground
Robolectric.buildActivity(Activity.class).create().start().resume();
shadowOf(getMainLooper()).idle();

// Verify first criteria fetch
RecordedRequest firstCriteriaRequest = server.takeRequest(1, TimeUnit.SECONDS);
assertNotNull("First criteria request should be made", firstCriteriaRequest);
assertTrue("First request URL should contain getCriteria endpoint",
firstCriteriaRequest.getPath().contains(IterableConstants.ENDPOINT_CRITERIA_LIST));

// Immediate second foreground
Robolectric.buildActivity(Activity.class).create().start().resume();
shadowOf(getMainLooper()).idle();

// Verify no criteria requests during cooldown period
RecordedRequest secondRequest = server.takeRequest(1, TimeUnit.SECONDS);
assertFalse("Second request should not be a criteria request",
secondRequest.getPath().contains(IterableConstants.ENDPOINT_CRITERIA_LIST));

// Clean up
IterableActivityMonitor.getInstance().unregisterLifecycleCallbacks(getContext());
IterableActivityMonitor.instance = new IterableActivityMonitor();
}
}
Loading