diff --git a/android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/ODPIntegrationUpdateConfigTest.java b/android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/ODPIntegrationUpdateConfigTest.java index 4685b5aa..04cd5236 100644 --- a/android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/ODPIntegrationUpdateConfigTest.java +++ b/android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/ODPIntegrationUpdateConfigTest.java @@ -117,7 +117,9 @@ public void setup() throws Exception { notificationCenter, null, odpManager, - "test-vuid"); + "test-vuid", + null, + null); } @Test diff --git a/android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/OptimizelyClientEngineTest.java b/android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/OptimizelyClientEngineTest.java index 8dc58f94..b539fe7a 100644 --- a/android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/OptimizelyClientEngineTest.java +++ b/android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/OptimizelyClientEngineTest.java @@ -35,20 +35,20 @@ @RunWith(AndroidJUnit4.class) public class OptimizelyClientEngineTest { @Test - public void testGetClientEngineFromContextAndroidTV() { + public void testGetClientEngineNameFromContextAndroidTV() { Context context = mock(Context.class); UiModeManager uiModeManager = mock(UiModeManager.class); when(context.getSystemService(Context.UI_MODE_SERVICE)).thenReturn(uiModeManager); when(uiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_TELEVISION); - assertEquals(EventBatch.ClientEngine.ANDROID_TV_SDK, OptimizelyClientEngine.getClientEngineFromContext(context)); + assertEquals("android-tv-sdk", OptimizelyClientEngine.getClientEngineNameFromContext(context)); } @Test - public void testGetClientEngineFromContextAndroid() { + public void testGetClientEngineNameFromContextAndroid() { Context context = mock(Context.class); UiModeManager uiModeManager = mock(UiModeManager.class); when(context.getSystemService(Context.UI_MODE_SERVICE)).thenReturn(uiModeManager); when(uiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_NORMAL); - assertEquals(EventBatch.ClientEngine.ANDROID_SDK, OptimizelyClientEngine.getClientEngineFromContext(context)); + assertEquals("android-sdk", OptimizelyClientEngine.getClientEngineNameFromContext(context)); } } diff --git a/android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/OptimizelyManagerEventHandlerTest.java b/android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/OptimizelyManagerEventHandlerTest.java index 9bfae9c9..a6a4f372 100644 --- a/android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/OptimizelyManagerEventHandlerTest.java +++ b/android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/OptimizelyManagerEventHandlerTest.java @@ -65,4 +65,25 @@ public void eventClientNameAndVersion() throws Exception { assertEquals(argument.getValue().getEventBatch().getClientVersion(), BuildConfig.CLIENT_VERSION); } + @Test + public void eventClientWithCustomNameAndVersion() throws Exception { + EventHandler mockEventHandler = mock(EventHandler.class); + + Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + OptimizelyManager optimizelyManager = OptimizelyManager.builder() + .withSDKKey("any-sdk-key") + .withEventDispatchInterval(0, TimeUnit.SECONDS) + .withEventHandler(mockEventHandler) + .withClientInfo("test-sdk", "test-version") + .build(context); + + OptimizelyClient optimizelyClient = optimizelyManager.initialize(context, minDatafileWithEvent); + optimizelyClient.track("test_event", "tester"); + + ArgumentCaptor argument = ArgumentCaptor.forClass(LogEvent.class); + verify(mockEventHandler, timeout(5000)).dispatchEvent(argument.capture()); + assertEquals(argument.getValue().getEventBatch().getClientName(), "test-sdk"); + assertEquals(argument.getValue().getEventBatch().getClientVersion(), "test-version"); + } + } diff --git a/android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/OptimizelyManagerTest.java b/android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/OptimizelyManagerTest.java index 1687761c..9b1fb08f 100644 --- a/android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/OptimizelyManagerTest.java +++ b/android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/OptimizelyManagerTest.java @@ -167,7 +167,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); + eventHandler, eventProcessor, null, null, null, null, null, null, null); /* * Scenario#1: when datafile is not Empty * Scenario#2: when datafile is Empty @@ -226,7 +226,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); + eventHandler, eventProcessor, null, null, null, null, null, null, null); /* * Scenario#1: when datafile is not Empty @@ -498,7 +498,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); doAnswer( new Answer() { @@ -531,7 +531,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); doAnswer( new Answer() { @@ -564,7 +564,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); doAnswer( new Answer() { @@ -597,7 +597,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); doAnswer( (Answer) invocation -> { @@ -629,7 +629,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); doAnswer( new Answer() { @@ -662,7 +662,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); doAnswer( new Answer() { @@ -696,7 +696,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); doAnswer( new Answer() { @@ -729,7 +729,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)); datafileHandler.removeSavedDatafile(context, manager.getDatafileConfig()); OptimizelyClient client = manager.initialize(context, R.raw.datafile, downloadToCache, updateConfigOnNewDatafile); @@ -746,7 +746,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)); datafileHandler.removeSavedDatafile(context, manager.getDatafileConfig()); OptimizelyClient client = manager.initialize(context, R.raw.datafile); diff --git a/android-sdk/src/main/java/com/optimizely/ab/android/sdk/OptimizelyClientEngine.java b/android-sdk/src/main/java/com/optimizely/ab/android/sdk/OptimizelyClientEngine.java index 485917ae..b0f3c5f0 100644 --- a/android-sdk/src/main/java/com/optimizely/ab/android/sdk/OptimizelyClientEngine.java +++ b/android-sdk/src/main/java/com/optimizely/ab/android/sdk/OptimizelyClientEngine.java @@ -28,12 +28,30 @@ */ public class OptimizelyClientEngine { + /** + * Get client engine name for current UI mode type + * + * @param context any valid Android {@link Context} + * @return client engine name ("android-sdk" or "android-tv-sdk") + */ + public static String getClientEngineNameFromContext(@NonNull Context context) { + UiModeManager uiModeManager = (UiModeManager) context.getSystemService(Context.UI_MODE_SERVICE); + + if (uiModeManager != null && uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) { + return "android-tv-sdk"; + } + + return "android-sdk"; + } + /** * Get client engine value for current UI mode type * * @param context any valid Android {@link Context} * @return String value of client engine + * @deprecated Consider using {@link #getClientEngineNameFromContext(Context)} */ + @Deprecated public static EventBatch.ClientEngine getClientEngineFromContext(@NonNull Context context) { UiModeManager uiModeManager = (UiModeManager) context.getSystemService(Context.UI_MODE_SERVICE); diff --git a/android-sdk/src/main/java/com/optimizely/ab/android/sdk/OptimizelyManager.java b/android-sdk/src/main/java/com/optimizely/ab/android/sdk/OptimizelyManager.java index 286881c2..57e4cdaf 100644 --- a/android-sdk/src/main/java/com/optimizely/ab/android/sdk/OptimizelyManager.java +++ b/android-sdk/src/main/java/com/optimizely/ab/android/sdk/OptimizelyManager.java @@ -100,7 +100,8 @@ public class OptimizelyManager { private boolean returnInMainThreadFromAsyncInit = true; @Nullable private final List defaultDecideOptions; - private String sdkVersion = null; + private String customSdkName = null; + private String customSdkVersion = null; OptimizelyManager(@Nullable String projectId, @Nullable String sdkKey, @@ -116,7 +117,9 @@ public class OptimizelyManager { @NonNull NotificationCenter notificationCenter, @Nullable List defaultDecideOptions, @Nullable ODPManager odpManager, - @Nullable String vuid) { + @Nullable String vuid, + @Nullable String clientEngineName, + @Nullable String clientVersion) { if (projectId == null && sdkKey == null) { logger.error("projectId and sdkKey are both null!"); @@ -142,12 +145,8 @@ public class OptimizelyManager { this.notificationCenter = notificationCenter; this.defaultDecideOptions = defaultDecideOptions; - try { - sdkVersion = BuildConfig.CLIENT_VERSION; - logger.info("SDK Version: {}", sdkVersion); - } catch (Exception e) { - logger.warn("Error getting BuildConfig version"); - } + this.customSdkName = clientEngineName; + this.customSdkVersion = clientVersion; } @VisibleForTesting @@ -537,6 +536,29 @@ public DatafileHandler getDatafileHandler() { return datafileHandler; } + @NonNull + public String getSdkName(Context context) { + String sdkName = customSdkName; + if (sdkName == null) { + sdkName = OptimizelyClientEngine.getClientEngineNameFromContext(context); + } + return sdkName; + } + + @NonNull + public String getSdkVersion() { + String sdkVersion = customSdkVersion; + if (sdkVersion == null) { + try { + sdkVersion = BuildConfig.CLIENT_VERSION; + } catch (Exception e) { + logger.warn("Error getting BuildConfig version"); + sdkVersion = "UNKNOWN"; + } + } + return sdkVersion; + } + private boolean datafileDownloadEnabled() { return datafileDownloadInterval > 0; } @@ -600,7 +622,8 @@ public void onStartComplete(UserProfileService userProfileService) { private OptimizelyClient buildOptimizely(@NonNull Context context, @NonNull String datafile) throws ConfigParseException { EventHandler eventHandler = getEventHandler(context); - EventBatch.ClientEngine clientEngine = OptimizelyClientEngine.getClientEngineFromContext(context); + String sdkName = getSdkName(context); + String sdkVersion = getSdkVersion(); Optimizely.Builder builder = Optimizely.builder(); @@ -617,7 +640,8 @@ private OptimizelyClient buildOptimizely(@NonNull Context context, @NonNull Stri } // override client sdk name/version to be included in events - builder.withClientInfo(clientEngine, sdkVersion); + builder.withClientInfo(sdkName, sdkVersion); + logger.info("SDK name: {} and version: {}", sdkName, sdkVersion); if (errorHandler != null) { builder.withErrorHandler(errorHandler); @@ -770,6 +794,9 @@ public static class Builder { private boolean odpEnabled = true; private String vuid = null; + private String customSdkName = null; + private String customSdkVersion = null; + @Deprecated /** * @deprecated use {@link #Builder()} instead and pass in an SDK Key with {@link #withSDKKey(String)} @@ -1014,6 +1041,18 @@ public Builder withVuid(String vuid) { return this; } + /** + * Override the SDK name and version (for client SDKs like flutter-sdk wrapping the core android-sdk) to be included in events. + * + * @param clientEngineName the client engine name ("flutter/android-sdk", etc.). + * @param clientVersion the client SDK version. + * @return this {@link Builder} instance + */ + public Builder withClientInfo(@Nullable String clientEngineName, @Nullable String clientVersion) { + this.customSdkName = clientEngineName; + this.customSdkVersion = clientVersion; + 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. @@ -1126,7 +1165,10 @@ public OptimizelyManager build(Context context) { notificationCenter, defaultDecideOptions, odpManager, - vuid); + vuid, + customSdkName, + customSdkVersion + ); } } } diff --git a/android-sdk/src/test/java/com/optimizely/ab/android/sdk/OptimizelyManagerBuilderTest.java b/android-sdk/src/test/java/com/optimizely/ab/android/sdk/OptimizelyManagerBuilderTest.java index 63eaf9c0..b9f5f276 100644 --- a/android-sdk/src/test/java/com/optimizely/ab/android/sdk/OptimizelyManagerBuilderTest.java +++ b/android-sdk/src/test/java/com/optimizely/ab/android/sdk/OptimizelyManagerBuilderTest.java @@ -216,18 +216,14 @@ public void testBuildWithDatafileDownloadInterval_workerCancelledWhenIntervalIsN } @Test - public void testBuildWithDatafileDownloadInterval_workerCancelledWhenNoIntervalProvided() throws Exception { + public void testBuildWithCustomSdkNameAndVersion() throws Exception { OptimizelyManager manager = OptimizelyManager.builder() - .withSDKKey(testSdkKey) - .withDatafileHandler(mockDatafileHandler) - .withVuid("any-to-avoid-generate") - .build(mockContext); - OptimizelyManager spyManager = spy(manager); - when(spyManager.isAndroidVersionSupported()).thenReturn(true); - spyManager.initialize(mockContext, ""); - - verify(mockDatafileHandler).stopBackgroundUpdates(any(), any()); - verify(mockDatafileHandler, never()).startBackgroundUpdates(any(), any(), any(), any()); + .withSDKKey(testSdkKey) + .withClientInfo("test-sdk", "test-version") + .withVuid("any-to-avoid-generate") + .build(mockContext); + assertEquals(manager.getSdkName(mockContext), "test-sdk"); + assertEquals(manager.getSdkVersion(), "test-version"); } @Test @@ -254,7 +250,9 @@ public void testBuildWithDefaultODP_defaultEnabled() throws Exception { any(NotificationCenter.class), any(), // nullable (DefaultDecideOptions) any(ODPManager.class), - eq("test-vuid")); + eq("test-vuid"), + any(), + any()); } @Test @@ -282,7 +280,9 @@ public void testBuildWithDefaultODP_disabled() throws Exception { any(NotificationCenter.class), any(), // nullable (DefaultDecideOptions) isNull(), - eq("test-vuid")); + eq("test-vuid"), + any(), + any()); } @Test diff --git a/android-sdk/src/test/java/com/optimizely/ab/android/sdk/OptimizelyManagerIntervalTest.java b/android-sdk/src/test/java/com/optimizely/ab/android/sdk/OptimizelyManagerIntervalTest.java index b82f9857..45fceb29 100644 --- a/android-sdk/src/test/java/com/optimizely/ab/android/sdk/OptimizelyManagerIntervalTest.java +++ b/android-sdk/src/test/java/com/optimizely/ab/android/sdk/OptimizelyManagerIntervalTest.java @@ -104,7 +104,9 @@ public void testBuildWithDatafileDownloadInterval() throws Exception { any(NotificationCenter.class), any(), // nullable (DefaultDecideOptions) any(ODPManager.class), - anyString()); + anyString(), + any(), + any()); } @Test @@ -131,7 +133,9 @@ public void testBuildWithDatafileDownloadIntervalDeprecated() throws Exception { any(NotificationCenter.class), any(), // nullable (DefaultDecideOptions) any(ODPManager.class), - anyString()); + anyString(), + any(), + any()); } @Test @@ -170,7 +174,9 @@ public void testBuildWithEventDispatchInterval() throws Exception { any(NotificationCenter.class), any(), // nullable (DefaultDecideOptions) any(ODPManager.class), - anyString()); + anyString(), + any(), + any()); } @Test @@ -212,7 +218,9 @@ public void testBuildWithEventDispatchRetryInterval() throws Exception { any(NotificationCenter.class), any(), // nullable (DefaultDecideOptions) any(ODPManager.class), - anyString()); + anyString(), + any(), + any()); } @Test @@ -250,7 +258,9 @@ public void testBuildWithEventDispatchIntervalDeprecated() throws Exception { any(NotificationCenter.class), any(), // nullable (DefaultDecideOptions) any(ODPManager.class), - anyString()); + anyString(), + any(), + any()); } } diff --git a/build.gradle b/build.gradle index 3d9908f4..e0479c6e 100644 --- a/build.gradle +++ b/build.gradle @@ -57,6 +57,7 @@ allprojects { mavenCentral() // SNAPSHOT support maven {url "https://oss.sonatype.org/content/repositories/snapshots/" } + maven { url "https://jitpack.io" } } configurations.all { diff --git a/shared/build.gradle b/shared/build.gradle index 4732e5fa..bafad905 100644 --- a/shared/build.gradle +++ b/shared/build.gradle @@ -51,6 +51,7 @@ dependencies { api ("com.optimizely.ab:core-api:$java_core_ver") { exclude group: 'com.google.code.findbugs' } + implementation "androidx.annotation:annotation:$annotations_ver" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "androidx.work:work-runtime:$work_runtime"