Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FSSDK-9918] add returnInMainThread flag for async init #470

Merged
merged 6 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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 @@ -21,6 +21,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.util.Log;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
Expand Down Expand Up @@ -48,6 +49,8 @@
import org.mockito.stubbing.Answer;
import org.slf4j.Logger;

import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
Expand All @@ -58,6 +61,7 @@
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
import static org.junit.Assert.assertNotEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
Expand Down Expand Up @@ -359,7 +363,7 @@ public void injectOptimizely() {
UserProfileService userProfileService = mock(UserProfileService.class);
OptimizelyStartListener startListener = mock(OptimizelyStartListener.class);

optimizelyManager.setOptimizelyStartListener(startListener);
optimizelyManager.setOptimizelyStartListener(startListener, true);
optimizelyManager.injectOptimizely(context, userProfileService, minDatafile);
try {
executor.awaitTermination(5, TimeUnit.SECONDS);
Expand Down Expand Up @@ -750,6 +754,72 @@ public void initializeSyncWithResourceDatafileNoCacheWithDefaultParams() {
verify(manager).initialize(eq(context), eq(defaultDatafile), eq(true), eq(false));
}

@Test
public void initializeAsyncCallbackInBackgroundThread() throws InterruptedException {
OptimizelyManager optimizelyManager = OptimizelyManager.builder(testProjectId)
.build(InstrumentationRegistry.getInstrumentation().getTargetContext());

CountDownLatch latch = new CountDownLatch(1);

// by default, async init returns in main thread.
// this parameter should be set to false to overrule it.
boolean returnInMainThread = false;

optimizelyManager.initialize(
InstrumentationRegistry.getInstrumentation().getContext(),
null,
returnInMainThread,
(client) -> {
Log.d("Optly", "[TESTING] " + Thread.currentThread().getName());
try {
assertNotEquals(
"OptimizelyStartListener should be called in a background thread",
"main", Thread.currentThread().getName()
);
latch.countDown();
} catch (AssertionError e) {
// we need catch and silence this assertion error, otherwise it will be caught in OptimizeManager,
// and give a wrong error message. The failure will be detected with the latch timeout below.
}
}
);

boolean completed = latch.await(1, TimeUnit.SECONDS);
if (!completed) {
fail("OptimizelyStartListener thread checking failed");
}
}

@Test
public void initializeAsyncCallbackInMainThread() throws InterruptedException {
OptimizelyManager optimizelyManager = OptimizelyManager.builder(testProjectId)
.build(InstrumentationRegistry.getInstrumentation().getTargetContext());

CountDownLatch latch = new CountDownLatch(1);

optimizelyManager.initialize(
InstrumentationRegistry.getInstrumentation().getContext(),
null,
(client) -> {
Log.d("Optly", "[TESTING] " + Thread.currentThread().getName());
try {
assertEquals(
"OptimizelyStartListener should be called in a background thread",
"main", Thread.currentThread().getName()
);
latch.countDown();
} catch (AssertionError e) {
// we need catch and silence this assertion error, otherwise it will be caught in OptimizeManager,
// and give a wrong error message. The failure will be detected with the latch timeout below.
}
}
);

boolean completed = latch.await(1, TimeUnit.SECONDS);
if (!completed) {
fail("OptimizelyStartListener thread checking failed");
}
}

// Utils

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ public class OptimizelyManager {
@Nullable private final String vuid;

@Nullable private OptimizelyStartListener optimizelyStartListener;
private boolean returnInMainThreadFromAsyncInit = true;

@Nullable private final List<OptimizelyDecideOption> defaultDecideOptions;
private String sdkVersion = null;
Expand Down Expand Up @@ -175,8 +176,13 @@ OptimizelyStartListener getOptimizelyStartListener() {
return optimizelyStartListener;
}

void setOptimizelyStartListener(@Nullable OptimizelyStartListener optimizelyStartListener) {
void setOptimizelyStartListener(@Nullable OptimizelyStartListener optimizelyStartListener, boolean returnInMainThread) {
this.optimizelyStartListener = optimizelyStartListener;
this.returnInMainThreadFromAsyncInit = returnInMainThread;
}

void setOptimizelyStartListener(@Nullable OptimizelyStartListener optimizelyStartListener) {
setOptimizelyStartListener(optimizelyStartListener, true);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
setOptimizelyStartListener(optimizelyStartListener, true);
boolean returnInMainThread = true;
setOptimizelyStartListener(optimizelyStartListener, returnInMainThread);

}

private void notifyStartListener() {
Expand Down Expand Up @@ -398,11 +404,27 @@ public void initialize(@NonNull final Context context, @NonNull OptimizelyStartL
* @see #initialize(Context, Integer, OptimizelyStartListener)
*/
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void initialize(@NonNull final Context context, @RawRes final Integer datafileRes, @NonNull OptimizelyStartListener optimizelyStartListener) {
public void initialize(
@NonNull final Context context,
@RawRes final Integer datafileRes,
@NonNull OptimizelyStartListener optimizelyStartListener)
{
// return in main thread after async completed (backward compatible)
boolean returnInMainThread = true;
initialize(context, datafileRes, returnInMainThread, optimizelyStartListener);
}

@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void initialize(
@NonNull final Context context,
@RawRes final Integer datafileRes,
final boolean returnInMainThread,
@NonNull OptimizelyStartListener optimizelyStartListener)
{
if (!isAndroidVersionSupported()) {
return;
}
setOptimizelyStartListener(optimizelyStartListener);
setOptimizelyStartListener(optimizelyStartListener, returnInMainThread);
datafileHandler.downloadDatafile(context, datafileConfig, getDatafileLoadedListener(context,datafileRes));
}

Expand Down Expand Up @@ -553,7 +575,7 @@ public void onStartComplete(UserProfileService userProfileService) {
logger.info("No listener to send Optimizely to");
}
}
});
}, returnInMainThreadFromAsyncInit);
}
else {
if (optimizelyStartListener != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ static public void samplesAll(Context context) {
samplesForDoc_NotificatonListener(context);
samplesForDoc_OlderVersions(context);
samplesForDoc_ForcedDecision(context);
samplesForDoc_ODP(context);
samplesForDoc_ODP_async(context);
samplesForDoc_ODP_sync(context);
}

static public void samplesForDecide(Context context) {
Expand Down Expand Up @@ -859,7 +860,7 @@ static public void samplesForDoc_ForcedDecision(Context context) {
success = user.removeAllForcedDecisions();
}

static public void samplesForDoc_ODP(Context context) {
static public void samplesForDoc_ODP_async(Context context) {
OptimizelyManager optimizelyManager = OptimizelyManager.builder().withSDKKey("VivZyCGPHY369D4z8T9yG").build(context);
optimizelyManager.initialize(context, null, (OptimizelyClient client) -> {
OptimizelyUserContext userContext = client.createUserContext("user_123");
Expand All @@ -871,4 +872,19 @@ static public void samplesForDoc_ODP(Context context) {
});
}

static public void samplesForDoc_ODP_sync(Context context) {
OptimizelyManager optimizelyManager = OptimizelyManager.builder().withSDKKey("VivZyCGPHY369D4z8T9yG").build(context);

boolean returnInMainThread = false;

optimizelyManager.initialize(context, null, returnInMainThread, (OptimizelyClient client) -> {
OptimizelyUserContext userContext = client.createUserContext("user_123");
userContext.fetchQualifiedSegments();

Log.d("Optimizely", "[ODP] segments = " + userContext.getQualifiedSegments());
OptimizelyDecision optDecision = userContext.decide("odp-flag-1");
Log.d("Optimizely", "[ODP] decision = " + optDecision.toString());
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ package com.optimizely.ab.android.test_app
import android.content.Context
import android.content.IntentFilter
import android.net.wifi.WifiManager
import android.os.Parcel
import android.os.Parcelable
import android.util.Log
import com.optimizely.ab.OptimizelyDecisionContext
import com.optimizely.ab.OptimizelyForcedDecision
Expand All @@ -29,7 +27,6 @@ import com.optimizely.ab.android.event_handler.EventRescheduler
import com.optimizely.ab.android.sdk.OptimizelyClient
import com.optimizely.ab.android.sdk.OptimizelyManager
import com.optimizely.ab.bucketing.UserProfileService
import com.optimizely.ab.config.Variation
import com.optimizely.ab.config.parser.JsonParseException
import com.optimizely.ab.error.ErrorHandler
import com.optimizely.ab.error.RaiseExceptionErrorHandler
Expand All @@ -40,12 +37,8 @@ import com.optimizely.ab.notification.DecisionNotification
import com.optimizely.ab.notification.NotificationHandler
import com.optimizely.ab.notification.TrackNotification
import com.optimizely.ab.notification.UpdateConfigNotification
import com.optimizely.ab.optimizelyconfig.OptimizelyConfig
import com.optimizely.ab.optimizelydecision.OptimizelyDecideOption
import com.optimizely.ab.optimizelydecision.OptimizelyDecision
import com.optimizely.ab.optimizelyjson.OptimizelyJSON
import org.slf4j.LoggerFactory
import java.lang.Exception
import java.util.*
import java.util.concurrent.TimeUnit

Expand Down Expand Up @@ -76,7 +69,8 @@ object APISamplesInKotlin {
samplesForDoc_NotificatonListener(context)
samplesForDoc_OlderVersions(context)
samplesForDoc_ForcedDecision(context)
samplesForDoc_ODP(context)
samplesForDoc_ODP_async(context)
samplesForDoc_ODP_sync(context)
}

fun samplesForDecide(context: Context) {
Expand Down Expand Up @@ -829,7 +823,7 @@ object APISamplesInKotlin {
success = user.removeAllForcedDecisions()
}

fun samplesForDoc_ODP(context: Context?) {
fun samplesForDoc_ODP_async(context: Context?) {
val optimizelyManager =
OptimizelyManager.builder().withSDKKey("VivZyCGPHY369D4z8T9yG").build(context)
optimizelyManager.initialize(context!!, null) { client: OptimizelyClient ->
Expand All @@ -842,6 +836,22 @@ object APISamplesInKotlin {
}
}

fun samplesForDoc_ODP_sync(context: Context?) {
val optimizelyManager =
OptimizelyManager.builder().withSDKKey("VivZyCGPHY369D4z8T9yG").build(context)

val returnInMainThread = false;

optimizelyManager.initialize(context!!, null, returnInMainThread) { client: OptimizelyClient ->
val userContext = client.createUserContext("user_123")
userContext!!.fetchQualifiedSegments()

Log.d("Optimizely", "[ODP] segments = " + userContext.qualifiedSegments)
val optDecision = userContext.decide("odp-flag-1")
Log.d("Optimizely", "[ODP] decision = $optDecision")
}
}

}


Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.optimizely.ab.android.event_handler.EventRescheduler
import com.optimizely.ab.android.sdk.OptimizelyClient
import com.optimizely.ab.android.sdk.OptimizelyManager
import com.optimizely.ab.android.shared.CountingIdlingResourceManager
import com.optimizely.ab.android.test_app.Samples.APISamplesInJava
import com.optimizely.ab.notification.DecisionNotification
import com.optimizely.ab.notification.TrackNotification
import com.optimizely.ab.notification.UpdateConfigNotification
Expand Down Expand Up @@ -131,4 +132,4 @@ class SplashScreenActivity : AppCompatActivity() {
// The Idling Resource which will be null in production.
private val countingIdlingResourceManager: CountingIdlingResourceManager? = null
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
Expand All @@ -39,6 +40,8 @@
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;

/**
* Tests for {@link DefaultUserProfileService}
Expand All @@ -51,7 +54,6 @@ public class DefaultUserProfileServiceTest {
private UserProfileCache.DiskCache diskCache;
private ExecutorService executor;
private Logger logger;
private UserProfileCache.LegacyDiskCache legacyDiskCache;
private Map<String, Map<String, Object>> memoryCache;
private String projectId;
private UserProfileCache userProfileCache;
Expand All @@ -66,11 +68,10 @@ public void setup() {
logger = mock(Logger.class);
cache = new Cache(InstrumentationRegistry.getInstrumentation().getTargetContext(), logger);
executor =Executors.newSingleThreadExecutor();
legacyDiskCache = new UserProfileCache.LegacyDiskCache(cache, executor, logger, projectId);
memoryCache = new ConcurrentHashMap<>();
projectId = "123";
diskCache = new UserProfileCache.DiskCache(cache, executor, logger, projectId);
userProfileCache = new UserProfileCache(diskCache, logger, memoryCache, legacyDiskCache);
userProfileCache = new UserProfileCache(diskCache, logger, memoryCache);
userProfileService = new DefaultUserProfileService(userProfileCache, logger);

// Test data.
Expand Down Expand Up @@ -103,6 +104,20 @@ public void teardown() {
cache.delete(diskCache.getFileName());
}

@Test
public void startInBackground() throws InterruptedException {
DefaultUserProfileService ups = spy(DefaultUserProfileService.class);

CountDownLatch latch = new CountDownLatch(1);
ups.startInBackground((u) -> {
latch.countDown();
});

latch.await(3, TimeUnit.SECONDS);

verify(ups).start();
}

@Test
public void saveAndStartAndLookup() {
userProfileService.save(userProfileMap1);
Expand Down
Loading
Loading